Эх сурвалжийг харах

feat: JSOn导出,XML导出, 保存,新增

Gaokun Wang 5 өдөр өмнө
parent
commit
50a17128d3

+ 15 - 0
package-lock.json

@@ -21,6 +21,7 @@
         "electron-updater": "^6.6.2",
         "element-plus": "^2.10.4",
         "fast-xml-parser": "^5.2.5",
+        "file-saver": "^2.0.5",
         "insert-css": "^2.0.0",
         "nprogress": "^0.2.0",
         "pinia": "^3.0.3",
@@ -33,6 +34,7 @@
         "@electron-toolkit/eslint-config-ts": "^3.0.0",
         "@electron-toolkit/tsconfig": "^1.0.1",
         "@typed-mxgraph/typed-mxgraph": "^1.0.8",
+        "@types/file-saver": "^2.0.7",
         "@types/insert-css": "^2.0.3",
         "@types/node": "^22.14.1",
         "@types/uuid": "^10.0.0",
@@ -1984,6 +1986,13 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@types/file-saver": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmmirror.com/@types/file-saver/-/file-saver-2.0.7.tgz",
+      "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@types/fs-extra": {
       "version": "9.0.13",
       "resolved": "https://registry.npmmirror.com/@types/fs-extra/-/fs-extra-9.0.13.tgz",
@@ -5335,6 +5344,12 @@
         "node": ">=16.0.0"
       }
     },
+    "node_modules/file-saver": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz",
+      "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
+      "license": "MIT"
+    },
     "node_modules/filelist": {
       "version": "1.0.4",
       "resolved": "https://registry.npmmirror.com/filelist/-/filelist-1.0.4.tgz",

+ 2 - 0
package.json

@@ -33,6 +33,7 @@
     "electron-updater": "^6.6.2",
     "element-plus": "^2.10.4",
     "fast-xml-parser": "^5.2.5",
+    "file-saver": "^2.0.5",
     "insert-css": "^2.0.0",
     "nprogress": "^0.2.0",
     "pinia": "^3.0.3",
@@ -45,6 +46,7 @@
     "@electron-toolkit/eslint-config-ts": "^3.0.0",
     "@electron-toolkit/tsconfig": "^1.0.1",
     "@typed-mxgraph/typed-mxgraph": "^1.0.8",
+    "@types/file-saver": "^2.0.7",
     "@types/insert-css": "^2.0.3",
     "@types/node": "^22.14.1",
     "@types/uuid": "^10.0.0",

+ 12 - 0
src/utils/fileSaver.ts

@@ -0,0 +1,12 @@
+import { saveAs } from 'file-saver'
+
+export const downloadJson = (data: object, filename: string): void => {
+  const jsonString = JSON.stringify(data, null, 2)
+  const blob = new Blob([jsonString], { type: 'application/json;charset=utf-8' })
+  saveAs(blob, filename.endsWith('.json') ? filename : `${filename}.json`)
+}
+
+export const downloadXml = (xmlString: string, filename: string): void => {
+  const blob = new Blob([xmlString], { type: 'application/xml;charset=utf-8' })
+  saveAs(blob, filename.endsWith('.xml') ? filename : `${filename}.xml`)
+}

+ 1 - 1
src/utils/webSocket.ts

@@ -18,7 +18,7 @@ interface WebSocketMessage {
 }
 
 // 定义消息类型
-export type MessageType = 'update' | 'save' | 'fetch' | 'other'
+export type MessageType = 'update' | 'saveFlow' | 'saveCase' | 'fetch' | 'other' | 'delete'
 
 // 定义消息类型
 export type WsID = 'wsClient' | 'wsServer'

+ 48 - 36
src/views/components/ToolBar/index.vue

@@ -1,12 +1,12 @@
 <template>
   <div class="bar">
-    <el-tooltip class="item" effect="dark" content="清除 (Cmd + D)" placement="bottom">
+    <el-tooltip class="item" effect="dark" content="清除 (Ctrl + D)" placement="bottom">
       <el-button name="delete" @click="handleClick" class="item-space" size="small">
         清除
       </el-button>
     </el-tooltip>
 
-    <el-tooltip class="item" effect="dark" content="撤销 (Cmd + Z)" placement="bottom">
+    <el-tooltip class="item" effect="dark" content="撤销 (Ctrl + Z)" placement="bottom">
       <el-button
         :disabled="!canUndo"
         name="undo"
@@ -17,66 +17,64 @@
       </el-button>
     </el-tooltip>
 
-    <el-tooltip class="item" effect="dark" content="Redo (Cmd + Shift + Z)" placement="bottom">
+    <el-tooltip class="item" effect="dark" content="Redo (Ctrl + Shift + Z)" placement="bottom">
       <el-button
         :disabled="!canRedo"
         name="redo"
         @click="handleClick"
         class="item-space"
         size="small">
-        redo
+        恢复
       </el-button>
     </el-tooltip>
 
-    <el-tooltip class="item" effect="dark" content="复制 (Cmd + Shift + Z)" placement="bottom">
+    <el-tooltip class="item" effect="dark" content="复制 (Ctrl + Shift + Z)" placement="bottom">
       <el-button name="copy" @click="handleClick" class="item-space" size="small"> 复制 </el-button>
     </el-tooltip>
 
-    <el-tooltip class="item" effect="dark" content="剪切 (Cmd + X)" placement="bottom">
+    <el-tooltip class="item" effect="dark" content="剪切 (Ctrl + X)" placement="bottom">
       <el-button name="cut" @click="handleClick" class="item-space" size="small"> 剪切 </el-button>
     </el-tooltip>
 
-    <el-tooltip class="item" effect="dark" content="粘贴 (Cmd + V)" placement="bottom">
+    <el-tooltip class="item" effect="dark" content="粘贴 (Ctrl + V)" placement="bottom">
       <el-button name="paste" @click="handleClick" class="item-space" size="small">
         粘贴
       </el-button>
     </el-tooltip>
 
-    <el-tooltip class="item" effect="dark" content="保存PNG (Cmd + S)" placement="bottom">
-      <el-button name="savePNG" @click="handleClick" class="item-space" size="small">
-        保存PNG
-      </el-button>
-    </el-tooltip>
+    <el-button name="print" @click="handleClick" class="item-space" size="small"> 打印 </el-button>
 
-    <el-tooltip class="item" effect="dark" content="保存SVG (Cmd + S)" placement="bottom">
-      <el-button name="saveSVG" @click="handleClick" class="item-space" size="small">
-        保存SVG
-      </el-button>
-    </el-tooltip>
+    <el-button name="savePNG" @click="handleClick" class="item-space" size="small">
+      导出PNG
+    </el-button>
 
-    <el-tooltip class="item" effect="dark" content="打印 (Cmd + P)" placement="bottom">
-      <el-button name="print" @click="handleClick" class="item-space" size="small">
-        打印
-      </el-button>
-    </el-tooltip>
+    <el-button name="saveSVG" @click="handleClick" class="item-space" size="small">
+      导出SVG
+    </el-button>
 
-    <el-tooltip class="item" effect="dark" content="导出 (Cmd + P)" placement="bottom">
-      <el-button name="toJSON" @click="handleClick" class="item-space" size="small">
-        导出JSON
-      </el-button>
-    </el-tooltip>
+    <el-button name="toJSON" @click="handleClick" class="item-space" size="small">
+      导出JSON
+    </el-button>
+
+    <el-button name="toXML" @click="handleClick" class="item-space" size="small">
+      导出XML
+    </el-button>
+    <el-button name="save" @click="handleClick" class="item-space" size="small"> 保存 </el-button>
   </div>
 </template>
 <script setup lang="ts">
 import FlowGraph from '../../graph'
 import { DataUri } from '@antv/x6'
 import { ref } from 'vue'
-import { XMLBuilder, XMLParser } from 'fast-xml-parser'
+import { XMLBuilder } from 'fast-xml-parser'
+import { downloadJson, downloadXml } from '@/utils/fileSaver'
+import { useWebSocketManager, wsClient, wsStatus } from '@/utils/webSocket'
+import { ElMessage } from 'element-plus'
+const { sendMessage } = useWebSocketManager()
 const builder = new XMLBuilder()
-const parser = new XMLParser()
 const { graph } = FlowGraph
 const { history } = graph
-
+const route = useRoute()
 const canUndo = ref(history.canUndo())
 const canRedo = ref(history.canRedo())
 
@@ -192,13 +190,27 @@ function handleClick(event: Event) {
       paste()
       break
     case 'toJSON':
-      window.localStorage.setItem('graphJson', JSON.stringify(graph.toJSON()))
-      // graph.fromJSON({cells:[graph.toJSON().cells[0],graph.toJSON().cells[1]]})
+      downloadJson(graph.toJSON(), 'graph')
+      break
+    case 'toXML':
       const xmlContent = builder.build(graph.toJSON())
-      window.localStorage.setItem('graphXML', xmlContent)
-      // 解析 XML
-      const jsonObj = parser.parse(xmlContent)
-      window.localStorage.setItem('jsonObj', JSON.stringify(jsonObj))
+      downloadXml(xmlContent, 'graph')
+      break
+    case 'save':
+      if (wsClient.value && wsStatus.value === '已连接') {
+        const id = route.params.id as string
+        sendMessage({
+          serverName: 'wsClient',
+          type: 'saveFlow',
+          data: {
+            id,
+            flow: graph.toJSON()
+          }
+        })
+        ElMessage.success('保存成功')
+      } else {
+        console.log('WebSocket 未连接,无法保存')
+      }
       break
     default:
       break

+ 1 - 2
src/views/graph/index.ts

@@ -181,7 +181,7 @@ export default class FlowGraph {
     this.stencil = new Addon.Stencil({
       target: this.graph,
       title: '节点搜索',
-      stencilGraphWidth: 250,
+      stencilGraphWidth: 200,
       stencilGraphHeight: 0,
       placeholder: '请输入节点关键字',
       notFoundText: '未搜索到结果',
@@ -191,7 +191,6 @@ export default class FlowGraph {
         {
           name: 'basic',
           title: '基础节点'
-          // graphHeight: 180
         },
         {
           name: 'logic',

+ 1 - 1
src/views/graph/shape.ts

@@ -83,7 +83,7 @@ export const logicPath = Graph.registerNode('logic-flow-path', {
 // 子流程节点
 export const FlowChartTitleRect = Graph.registerNode('sub-flow-node', {
   inherit: 'rect',
-  width: 200,
+  width: 180,
   height: 68,
   attrs: {
     body: {

+ 14 - 13
src/views/test/components/index.vue

@@ -12,8 +12,7 @@
       label-suffix=" :"
       :rules="rules"
       :model="paramsProps.row"
-      @submit.enter.prevent="handleConfirm"
-      :disabled="paramsProps.title == '编辑'">
+      @submit.enter.prevent="handleConfirm">
       <el-form-item label="用例名称" prop="name">
         <el-input v-model="paramsProps.row.name" placeholder="填写用例名称" clearable />
       </el-form-item>
@@ -33,23 +32,21 @@
 </template>
 
 <script setup lang="ts" name="AddTestCase">
+import { useWebSocketManager, wsClient, wsStatus } from '@/utils/webSocket'
 import { ElMessage } from 'element-plus'
 import { FormInstance } from 'element-plus'
 const rules = reactive({
-  name: [{ required: true, message: '请填写参数名称' }]
+  name: [{ required: true, message: '请填写用例名称' }]
 })
+const { sendMessage } = useWebSocketManager()
 interface DialogProps {
-  api?: (params: any) => Promise<any> // 调用接口
   title: string // 顶部标题
   row: Partial<any>
-  getTableList?: () => void
 }
 const visible = ref(false)
 const paramsProps = ref<DialogProps>({
   title: '',
-  row: {},
-  api: undefined,
-  getTableList: undefined
+  row: {}
 })
 
 // emit
@@ -66,12 +63,16 @@ const handleConfirm = async () => {
   ruleFormRef.value!.validate(async valid => {
     if (!valid) return
     try {
-      const { code } = await paramsProps.value.api!(paramsProps.value.row)
-      if (code == 200) {
-        ElMessage.success({ message: `${paramsProps.value.title}成功!` })
-        paramsProps.value.getTableList!()
-        emit('submit')
+      if (wsClient.value && wsStatus.value === '已连接') {
+        sendMessage({
+          serverName: 'wsClient',
+          type: paramsProps.value.title == '编辑' ? 'update' : 'saveCase',
+          data: paramsProps.value.row
+        })
+        ElMessage.success('操作成功')
         visible.value = false
+      } else {
+        console.log('WebSocket 未连接,无法保存')
       }
     } catch (error) {
       console.log(error)

+ 0 - 66
src/views/test/components/subFlow.vue

@@ -1,66 +0,0 @@
-<template>
-  <el-dialog
-    v-model="visible"
-    :title="`${paramsProps.title}`"
-    :destroy-on-close="true"
-    fullscreen
-    append-to-body>
-    <div>子流程编辑</div>
-    <template #footer>
-      <el-button @click="visible = false"> 取消 </el-button>
-      <el-button type="primary" @click="handleConfirm"> 确定 </el-button>
-    </template>
-  </el-dialog>
-</template>
-
-<script setup lang="ts" name="AddTestCase">
-import { ElMessage } from 'element-plus'
-import { FormInstance } from 'element-plus'
-
-interface DialogProps {
-  api?: (params: any) => Promise<any> // 调用接口
-  title: string // 顶部标题
-  row: Partial<any>
-  getTableList?: () => void
-}
-const visible = ref(false)
-const paramsProps = ref<DialogProps>({
-  title: '',
-  row: {},
-  api: undefined,
-  getTableList: undefined
-})
-
-// emit
-const emit = defineEmits(['submit'])
-
-// 接收父组件传过来的参数
-const acceptParams = (params: DialogProps) => {
-  paramsProps.value = params
-  visible.value = true
-}
-// 提交数据(新增/编辑)
-const ruleFormRef = ref<FormInstance>()
-const handleConfirm = async () => {
-  ruleFormRef.value!.validate(async valid => {
-    if (!valid) return
-    try {
-      const { code } = await paramsProps.value.api!(paramsProps.value.row)
-      if (code == 200) {
-        ElMessage.success({ message: `${paramsProps.value.title}成功!` })
-        paramsProps.value.getTableList!()
-        emit('submit')
-        visible.value = false
-      }
-    } catch (error) {
-      console.log(error)
-    }
-  })
-}
-
-defineExpose({
-  acceptParams
-})
-</script>
-
-<style scoped lang="scss"></style>

+ 0 - 4
src/views/test/flow.vue

@@ -17,14 +17,12 @@
         <ConfigPanel v-if="isReady" />
       </div>
     </div>
-    <SubFlowDialog ref="dialogRef"></SubFlowDialog>
   </div>
 </template>
 <script setup lang="ts">
 import { $, getContainerSize } from '@/utils'
 import FlowGraph from '../graph'
 import ContextMenu from '@imengyu/vue3-context-menu'
-import SubFlowDialog from './components/subFlow.vue'
 import { Graph, type Node } from '@antv/x6'
 
 import { useCaseFlowData } from '@/store/caseFlow'
@@ -170,8 +168,6 @@ const handleContextmenu = (e: { pageX: any; pageY: any }, cell: Node<Node.Proper
   })
 }
 
-// 打开 dialog
-const dialogRef = ref<InstanceType<typeof SubFlowDialog> | null>(null)
 const openEdit = (id: string) => {
   if (id) {
     router.push(`/flow/${id}`)

+ 1 - 16
src/views/test/index.vue

@@ -32,7 +32,6 @@
       <div class="table-header">
         <div class="header-button-lf">
           <el-button type="primary" icon="CirclePlus" @click="openDialog('新增')"> 新增 </el-button>
-          <el-button type="primary" icon="CirclePlus" @click="saveData()"> 保存 </el-button>
           <el-button type="danger" icon="Delete" plain> 删除 </el-button>
         </div>
       </div>
@@ -139,19 +138,6 @@ const handleCurrentChange = (val: number) => {
 // 总数
 const total = computed(() => filteredData.value.length)
 
-// 保存数据时发送消息
-const saveData = () => {
-  if (wsClient.value) {
-    console.log('正在保存数据并发送消息...')
-    sendMessage({
-      serverName: 'wsClient',
-      type: 'save'
-    })
-  } else {
-    console.log('WebSocket 未连接,无法保存数据')
-  }
-}
-
 const router = useRouter()
 const tableRef = ref()
 
@@ -173,8 +159,7 @@ const dialogRef = ref<InstanceType<typeof AddDialog> | null>(null)
 const openDialog = (title: string, row: Partial<CaseVO> = {}) => {
   const params = {
     title,
-    row: { ...row },
-    isView: title === '查看'
+    row: { ...row }
   }
   dialogRef.value?.acceptParams(params)
 }