瀏覽代碼

Merge branch 'qingban' of http://maven.jgiot.com:7012/jiaxiaoqiang/JG-ADMIN-TEMP into qingban

# Conflicts:
#	src/api/process/index.ts
lupeng 2 月之前
父節點
當前提交
44a3bc1104
共有 52 個文件被更改,包括 3061 次插入84 次删除
  1. 1 1
      .env.development
  2. 1 0
      package.json
  3. 145 30
      pnpm-lock.yaml
  4. 18 0
      src/api/flow/index.ts
  5. 15 0
      src/api/process/index.ts
  6. 1 0
      src/common/configs/dictDataUtil.ts
  7. 14 0
      src/components/MyFlow/index.ts
  8. 18 0
      src/components/MyFlow/src/Handle/CommonHandle.vue
  9. 13 0
      src/components/MyFlow/src/background/DropzoneBackground.vue
  10. 59 0
      src/components/MyFlow/src/edges/commonEdge.vue
  11. 97 0
      src/components/MyFlow/src/flow.vue
  12. 233 0
      src/components/MyFlow/src/hjflow/index.vue
  13. 113 0
      src/components/MyFlow/src/hooks/snakeHooks.ts
  14. 145 0
      src/components/MyFlow/src/hooks/useDnD.ts
  15. 0 0
      src/components/MyFlow/src/hooks/useFlow.ts
  16. 11 0
      src/components/MyFlow/src/information/index.vue
  17. 167 0
      src/components/MyFlow/src/leftDrags/test/configs.ts
  18. 41 0
      src/components/MyFlow/src/leftDrags/test/dragData.vue
  19. 41 0
      src/components/MyFlow/src/leftDrags/workflow/configs.ts
  20. 41 0
      src/components/MyFlow/src/leftDrags/workflow/dragData.vue
  21. 11 0
      src/components/MyFlow/src/leftDrags/workflow/editInfo.vue
  22. 24 0
      src/components/MyFlow/src/nodes/CommonNode.vue
  23. 18 0
      src/components/MyFlow/src/nodes/NestNode.vue
  24. 7 0
      src/components/MyFlow/src/nodes/NumberFunNode.vue
  25. 7 0
      src/components/MyFlow/src/nodes/NumberInputNode.vue
  26. 7 0
      src/components/MyFlow/src/nodes/NumberResultNode.vue
  27. 94 0
      src/components/MyFlow/src/nodes/com/basic.vue
  28. 49 0
      src/components/MyFlow/src/nodes/com/operationHeader.vue
  29. 42 0
      src/components/MyFlow/src/nodes/universal/UniversalNode.vue
  30. 26 0
      src/components/MyFlow/src/nodes/workflow/WFEndNode.vue
  31. 26 0
      src/components/MyFlow/src/nodes/workflow/WFStartNode.vue
  32. 40 0
      src/components/MyFlow/src/nodes/workflow/WorkFlowNode.vue
  33. 20 0
      src/components/MyFlow/src/panel/btns/Back.vue
  34. 44 0
      src/components/MyFlow/src/panel/btns/add-node.vue
  35. 27 0
      src/components/MyFlow/src/panel/btns/layout-LR.vue
  36. 29 0
      src/components/MyFlow/src/panel/btns/layout-TB.vue
  37. 37 0
      src/components/MyFlow/src/panel/btns/reset.vue
  38. 32 0
      src/components/MyFlow/src/panel/btns/snake.vue
  39. 108 0
      src/components/MyFlow/src/panel/index.vue
  40. 45 0
      src/components/MyFlow/src/styles/index.scss
  41. 184 0
      src/components/MyFlow/src/testShenpiliu.vue
  42. 130 0
      src/components/MyFlow/src/types/comTypes.ts
  43. 103 0
      src/components/MyFlow/src/utils/index.ts
  44. 90 0
      src/components/MyFlow/src/utils/useLayout.ts
  45. 116 0
      src/components/MyFlow/src/utils/useSnake.ts
  46. 16 5
      src/views/base/craftManagement/route/bindProcess.vue
  47. 2 1
      src/views/base/craftManagement/route/components/CustomNode/index.vue
  48. 23 12
      src/views/base/craftManagement/route/components/groupProcess.vue
  49. 87 0
      src/views/base/craftManagement/route/components/groupSort.vue
  50. 274 0
      src/views/flow/definition/com/edit.vue
  51. 135 0
      src/views/flow/definition/index.vue
  52. 34 35
      src/views/pro/traceability/components/mediaCom.vue

+ 1 - 1
.env.development

@@ -10,7 +10,7 @@ VITE_APP_BASE_API = '/dev-api'
 # 上传文件接口地址.
 VITE_APP_UPLOAD_URL = 'http://139.155.176.112:19000'
 # 测试开发接口地址
-VITE_APP_API_URL = 'http://192.168.1.111:7104'
+VITE_APP_API_URL = 'http://192.168.1.69:7104'
 
 #VITE_APP_API_URL = 'http://127.0.0.1:7104'
 

+ 1 - 0
package.json

@@ -127,6 +127,7 @@
     "eslint-plugin-vue": "^9.32.0",
     "globals": "^15.14.0",
     "husky": "^9.1.7",
+    "less": "^4.2.2",
     "lint-staged": "^15.2.11",
     "postcss": "^8.4.49",
     "postcss-html": "^1.7.0",

+ 145 - 30
pnpm-lock.yaml

@@ -25,10 +25,10 @@ importers:
         version: 7.0.0
       '@vitejs/plugin-legacy':
         specifier: ^6.0.0
-        version: 6.0.0(terser@5.37.0)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))
+        version: 6.0.0(terser@5.37.0)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))
       '@vitejs/plugin-vue-jsx':
         specifier: ^4.1.1
-        version: 4.1.1(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))
+        version: 4.1.1(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))
       '@vue-flow/background':
         specifier: ^1.3.2
         version: 1.3.2(@vue-flow/core@1.41.6(vue@3.5.13(typescript@5.5.4)))(vue@3.5.13(typescript@5.5.4))
@@ -236,7 +236,7 @@ importers:
         version: 8.18.2(eslint@9.17.0(jiti@2.4.2))(typescript@5.5.4)
       '@vitejs/plugin-vue':
         specifier: ^5.2.1
-        version: 5.2.1(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))
+        version: 5.2.1(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))
       autoprefixer:
         specifier: ^10.4.20
         version: 10.4.20(postcss@8.4.49)
@@ -264,6 +264,9 @@ importers:
       husky:
         specifier: ^9.1.7
         version: 9.1.7
+      less:
+        specifier: ^4.2.2
+        version: 4.2.2
       lint-staged:
         specifier: ^15.2.11
         version: 15.3.0
@@ -311,7 +314,7 @@ importers:
         version: 8.18.2(eslint@9.17.0(jiti@2.4.2))(typescript@5.5.4)
       unocss:
         specifier: 0.65.1
-        version: 0.65.1(postcss@8.4.49)(rollup@4.29.1)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))
+        version: 0.65.1(postcss@8.4.49)(rollup@4.29.1)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))
       unplugin-auto-import:
         specifier: ^0.18.6
         version: 0.18.6(@vueuse/core@10.11.1(vue@3.5.13(typescript@5.5.4)))(rollup@4.29.1)
@@ -320,13 +323,13 @@ importers:
         version: 0.27.5(@babel/parser@7.26.3)(rollup@4.29.1)(vue@3.5.13(typescript@5.5.4))
       vite:
         specifier: ^6.0.5
-        version: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
+        version: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
       vite-plugin-mock-dev-server:
         specifier: ^1.8.3
-        version: 1.8.3(bufferutil@4.0.9)(esbuild@0.23.1)(rollup@4.29.1)(utf-8-validate@5.0.10)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))
+        version: 1.8.3(bufferutil@4.0.9)(esbuild@0.23.1)(rollup@4.29.1)(utf-8-validate@5.0.10)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))
       vite-plugin-svg-icons:
         specifier: ^2.0.1
-        version: 2.0.1(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))
+        version: 2.0.1(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))
       vue-eslint-parser:
         specifier: ^9.4.3
         version: 9.4.3(eslint@9.17.0(jiti@2.4.2))
@@ -2581,6 +2584,9 @@ packages:
     resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==}
     engines: {node: '>= 0.8'}
 
+  copy-anything@2.0.6:
+    resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==}
+
   copy-descriptor@0.1.1:
     resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==}
     engines: {node: '>=0.10.0'}
@@ -2957,6 +2963,10 @@ packages:
     resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
     engines: {node: '>=18'}
 
+  errno@0.1.8:
+    resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
+    hasBin: true
+
   error-ex@1.3.2:
     resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
 
@@ -3582,6 +3592,10 @@ packages:
     resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
     engines: {node: '>=0.10.0'}
 
+  iconv-lite@0.6.3:
+    resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
+    engines: {node: '>=0.10.0'}
+
   ieee754@1.2.1:
     resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
 
@@ -3841,6 +3855,9 @@ packages:
     resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
     engines: {node: '>= 0.4'}
 
+  is-what@3.14.1:
+    resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==}
+
   is-windows@1.0.2:
     resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}
     engines: {node: '>=0.10.0'}
@@ -3967,6 +3984,11 @@ packages:
     resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==}
     engines: {node: '>= 0.6.3'}
 
+  less@4.2.2:
+    resolution: {integrity: sha512-tkuLHQlvWUTeQ3doAqnHbNn8T6WX1KA8yvbKG9x4VtKtIjHsVKQZCH11zRgAfbDAXC2UNIg/K9BYAAcEzUIrNg==}
+    engines: {node: '>=6'}
+    hasBin: true
+
   levn@0.4.1:
     resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
     engines: {node: '>= 0.8.0'}
@@ -4132,6 +4154,10 @@ packages:
   magic-string@0.30.17:
     resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
 
+  make-dir@2.1.0:
+    resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
+    engines: {node: '>=6'}
+
   make-dir@3.1.0:
     resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
     engines: {node: '>=8'}
@@ -4211,6 +4237,11 @@ packages:
     resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
     engines: {node: '>= 0.6'}
 
+  mime@1.6.0:
+    resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
+    engines: {node: '>=4'}
+    hasBin: true
+
   mimic-fn@2.1.0:
     resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
     engines: {node: '>=6'}
@@ -4314,6 +4345,11 @@ packages:
   natural-compare@1.4.0:
     resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
 
+  needle@3.3.1:
+    resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==}
+    engines: {node: '>= 4.4.x'}
+    hasBin: true
+
   net@1.0.2:
     resolution: {integrity: sha512-kbhcj2SVVR4caaVnGLJKmlk2+f+oLkjqdKeQlmUtz6nGzOpbcobwVIeSURNgraV/v3tlmGIX82OcPCl0K6RbHQ==}
 
@@ -4479,6 +4515,10 @@ packages:
     resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
     engines: {node: '>=8'}
 
+  parse-node-version@1.0.1:
+    resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}
+    engines: {node: '>= 0.10'}
+
   parse-passwd@1.0.0:
     resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==}
     engines: {node: '>=0.10.0'}
@@ -4556,6 +4596,10 @@ packages:
     engines: {node: '>=0.10'}
     hasBin: true
 
+  pify@4.0.1:
+    resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
+    engines: {node: '>=6'}
+
   pinia-plugin-persist@1.0.0:
     resolution: {integrity: sha512-M4hBBd8fz/GgNmUPaaUsC29y1M09lqbXrMAHcusVoU8xlQi1TqgkWnnhvMikZwr7Le/hVyMx8KUcumGGrR6GVw==}
     peerDependencies:
@@ -4693,6 +4737,9 @@ packages:
   proxy-from-env@1.1.0:
     resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
 
+  prr@1.0.1:
+    resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
+
   punycode@2.3.1:
     resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
     engines: {node: '>=6'}
@@ -4909,6 +4956,9 @@ packages:
     engines: {node: '>=14.0.0'}
     hasBin: true
 
+  sax@1.4.1:
+    resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
+
   saxes@5.0.1:
     resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==}
     engines: {node: '>=10'}
@@ -4919,6 +4969,10 @@ packages:
   scule@1.3.0:
     resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
 
+  semver@5.7.2:
+    resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
+    hasBin: true
+
   semver@6.3.1:
     resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
     hasBin: true
@@ -5855,6 +5909,7 @@ packages:
   yaeti@0.0.6:
     resolution: {integrity: sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==}
     engines: {node: '>=0.10.32'}
+    deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
 
   yallist@3.1.1:
     resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -7337,13 +7392,13 @@ snapshots:
       '@typescript-eslint/types': 8.18.2
       eslint-visitor-keys: 4.2.0
 
-  '@unocss/astro@0.65.1(rollup@4.29.1)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))':
+  '@unocss/astro@0.65.1(rollup@4.29.1)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))':
     dependencies:
       '@unocss/core': 0.65.1
       '@unocss/reset': 0.65.1
-      '@unocss/vite': 0.65.1(rollup@4.29.1)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))
+      '@unocss/vite': 0.65.1(rollup@4.29.1)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))
     optionalDependencies:
-      vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
+      vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
     transitivePeerDependencies:
       - rollup
       - supports-color
@@ -7472,7 +7527,7 @@ snapshots:
     dependencies:
       '@unocss/core': 0.65.1
 
-  '@unocss/vite@0.65.1(rollup@4.29.1)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))':
+  '@unocss/vite@0.65.1(rollup@4.29.1)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))':
     dependencies:
       '@ampproject/remapping': 2.3.0
       '@rollup/pluginutils': 5.1.4(rollup@4.29.1)
@@ -7482,7 +7537,7 @@ snapshots:
       chokidar: 3.6.0
       magic-string: 0.30.17
       tinyglobby: 0.2.10
-      vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
+      vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
     transitivePeerDependencies:
       - rollup
       - supports-color
@@ -7517,7 +7572,7 @@ snapshots:
       '@uppy/utils': 4.1.3
       nanoid: 3.3.8
 
-  '@vitejs/plugin-legacy@6.0.0(terser@5.37.0)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))':
+  '@vitejs/plugin-legacy@6.0.0(terser@5.37.0)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))':
     dependencies:
       '@babel/core': 7.26.0
       '@babel/preset-env': 7.26.0(@babel/core@7.26.0)
@@ -7528,23 +7583,23 @@ snapshots:
       regenerator-runtime: 0.14.1
       systemjs: 6.15.1
       terser: 5.37.0
-      vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
+      vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
     transitivePeerDependencies:
       - supports-color
 
-  '@vitejs/plugin-vue-jsx@4.1.1(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))':
+  '@vitejs/plugin-vue-jsx@4.1.1(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))':
     dependencies:
       '@babel/core': 7.26.0
       '@babel/plugin-transform-typescript': 7.26.3(@babel/core@7.26.0)
       '@vue/babel-plugin-jsx': 1.2.5(@babel/core@7.26.0)
-      vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
+      vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
       vue: 3.5.13(typescript@5.5.4)
     transitivePeerDependencies:
       - supports-color
 
-  '@vitejs/plugin-vue@5.2.1(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))':
+  '@vitejs/plugin-vue@5.2.1(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))':
     dependencies:
-      vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
+      vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
       vue: 3.5.13(typescript@5.5.4)
 
   '@volar/language-core@2.4.11':
@@ -7729,7 +7784,7 @@ snapshots:
       - '@vue/composition-api'
       - vue
 
-  '@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.0.7)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@5.0.7)(slate@0.72.8)(snabbdom@3.6.2)':
+  '@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@5.0.7)(slate@0.72.8)(snabbdom@3.6.2)':
     dependencies:
       '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.0.7)(slate@0.72.8)(snabbdom@3.6.2)
       dom7: 3.0.0
@@ -7779,7 +7834,7 @@ snapshots:
     dependencies:
       '@uppy/core': 2.3.4
       '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4)
-      '@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.0.7)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@5.0.7)(slate@0.72.8)(snabbdom@3.6.2)
+      '@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@5.0.7)(slate@0.72.8)(snabbdom@3.6.2)
       '@wangeditor/code-highlight': 1.0.3(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.2)
       '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.0.7)(slate@0.72.8)(snabbdom@3.6.2)
       '@wangeditor/list-module': 1.0.5(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.2)
@@ -7820,7 +7875,7 @@ snapshots:
     dependencies:
       '@uppy/core': 2.3.4
       '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4)
-      '@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.0.7)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@5.0.7)(slate@0.72.8)(snabbdom@3.6.2)
+      '@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.8)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@5.0.7)(slate@0.72.8)(snabbdom@3.6.2)
       '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@5.0.7)(slate@0.72.8)(snabbdom@3.6.2)
       dom7: 3.0.0
       lodash.foreach: 4.5.0
@@ -8428,6 +8483,10 @@ snapshots:
       depd: 2.0.0
       keygrip: 1.1.0
 
+  copy-anything@2.0.6:
+    dependencies:
+      is-what: 3.14.1
+
   copy-descriptor@0.1.1: {}
 
   core-js-compat@3.39.0:
@@ -8811,6 +8870,11 @@ snapshots:
 
   environment@1.1.0: {}
 
+  errno@0.1.8:
+    dependencies:
+      prr: 1.0.1
+    optional: true
+
   error-ex@1.3.2:
     dependencies:
       is-arrayish: 0.2.1
@@ -9612,6 +9676,11 @@ snapshots:
     dependencies:
       safer-buffer: 2.1.2
 
+  iconv-lite@0.6.3:
+    dependencies:
+      safer-buffer: 2.1.2
+    optional: true
+
   ieee754@1.2.1: {}
 
   ignore@5.3.2: {}
@@ -9860,6 +9929,8 @@ snapshots:
       call-bound: 1.0.3
       get-intrinsic: 1.2.6
 
+  is-what@3.14.1: {}
+
   is-windows@1.0.2: {}
 
   isarray@1.0.0: {}
@@ -9965,6 +10036,20 @@ snapshots:
     dependencies:
       readable-stream: 2.3.8
 
+  less@4.2.2:
+    dependencies:
+      copy-anything: 2.0.6
+      parse-node-version: 1.0.1
+      tslib: 2.8.1
+    optionalDependencies:
+      errno: 0.1.8
+      graceful-fs: 4.2.11
+      image-size: 0.5.5
+      make-dir: 2.1.0
+      mime: 1.6.0
+      needle: 3.3.1
+      source-map: 0.6.1
+
   levn@0.4.1:
     dependencies:
       prelude-ls: 1.2.1
@@ -10120,6 +10205,12 @@ snapshots:
     dependencies:
       '@jridgewell/sourcemap-codec': 1.5.0
 
+  make-dir@2.1.0:
+    dependencies:
+      pify: 4.0.1
+      semver: 5.7.2
+    optional: true
+
   make-dir@3.1.0:
     dependencies:
       semver: 6.3.1
@@ -10194,6 +10285,9 @@ snapshots:
     dependencies:
       mime-db: 1.52.0
 
+  mime@1.6.0:
+    optional: true
+
   mimic-fn@2.1.0: {}
 
   mimic-fn@4.0.0: {}
@@ -10291,6 +10385,12 @@ snapshots:
 
   natural-compare@1.4.0: {}
 
+  needle@3.3.1:
+    dependencies:
+      iconv-lite: 0.6.3
+      sax: 1.4.1
+    optional: true
+
   net@1.0.2: {}
 
   next-tick@1.1.0: {}
@@ -10461,6 +10561,8 @@ snapshots:
       json-parse-even-better-errors: 2.3.1
       lines-and-columns: 1.2.4
 
+  parse-node-version@1.0.1: {}
+
   parse-passwd@1.0.0: {}
 
   pascalcase@0.1.1: {}
@@ -10511,6 +10613,9 @@ snapshots:
 
   pidtree@0.6.0: {}
 
+  pify@4.0.1:
+    optional: true
+
   pinia-plugin-persist@1.0.0(pinia@2.3.0(typescript@5.5.4)(vue@3.5.13(typescript@5.5.4)))(vue@3.5.13(typescript@5.5.4)):
     dependencies:
       pinia: 2.3.0(typescript@5.5.4)(vue@3.5.13(typescript@5.5.4))
@@ -10636,6 +10741,9 @@ snapshots:
 
   proxy-from-env@1.1.0: {}
 
+  prr@1.0.1:
+    optional: true
+
   punycode@2.3.1: {}
 
   qrcode@1.5.4:
@@ -10895,6 +11003,9 @@ snapshots:
     optionalDependencies:
       '@parcel/watcher': 2.5.0
 
+  sax@1.4.1:
+    optional: true
+
   saxes@5.0.1:
     dependencies:
       xmlchars: 2.2.0
@@ -10905,6 +11016,9 @@ snapshots:
 
   scule@1.3.0: {}
 
+  semver@5.7.2:
+    optional: true
+
   semver@6.3.1: {}
 
   semver@7.6.3: {}
@@ -11583,9 +11697,9 @@ snapshots:
 
   universalify@2.0.1: {}
 
-  unocss@0.65.1(postcss@8.4.49)(rollup@4.29.1)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4)):
+  unocss@0.65.1(postcss@8.4.49)(rollup@4.29.1)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4)):
     dependencies:
-      '@unocss/astro': 0.65.1(rollup@4.29.1)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))
+      '@unocss/astro': 0.65.1(rollup@4.29.1)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))
       '@unocss/cli': 0.65.1(rollup@4.29.1)
       '@unocss/core': 0.65.1
       '@unocss/postcss': 0.65.1(postcss@8.4.49)
@@ -11601,9 +11715,9 @@ snapshots:
       '@unocss/transformer-compile-class': 0.65.1
       '@unocss/transformer-directives': 0.65.1
       '@unocss/transformer-variant-group': 0.65.1
-      '@unocss/vite': 0.65.1(rollup@4.29.1)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))
+      '@unocss/vite': 0.65.1(rollup@4.29.1)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.5.4))
     optionalDependencies:
-      vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
+      vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
     transitivePeerDependencies:
       - postcss
       - rollup
@@ -11733,7 +11847,7 @@ snapshots:
 
   vary@1.1.2: {}
 
-  vite-plugin-mock-dev-server@1.8.3(bufferutil@4.0.9)(esbuild@0.23.1)(rollup@4.29.1)(utf-8-validate@5.0.10)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)):
+  vite-plugin-mock-dev-server@1.8.3(bufferutil@4.0.9)(esbuild@0.23.1)(rollup@4.29.1)(utf-8-validate@5.0.10)(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)):
     dependencies:
       '@pengzhanbo/utils': 1.2.0
       '@rollup/pluginutils': 5.1.4(rollup@4.29.1)
@@ -11750,7 +11864,7 @@ snapshots:
       mime-types: 2.1.35
       path-to-regexp: 6.3.0
       picocolors: 1.1.1
-      vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
+      vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
       ws: 8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)
     optionalDependencies:
       esbuild: 0.23.1
@@ -11760,7 +11874,7 @@ snapshots:
       - supports-color
       - utf-8-validate
 
-  vite-plugin-svg-icons@2.0.1(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)):
+  vite-plugin-svg-icons@2.0.1(vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)):
     dependencies:
       '@types/svgo': 2.6.4
       cors: 2.8.5
@@ -11770,11 +11884,11 @@ snapshots:
       pathe: 0.2.0
       svg-baker: 1.7.0
       svgo: 2.8.0
-      vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
+      vite: 6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1)
     transitivePeerDependencies:
       - supports-color
 
-  vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1):
+  vite@6.0.6(@types/node@22.10.2)(jiti@2.4.2)(less@4.2.2)(sass@1.83.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.1):
     dependencies:
       esbuild: 0.24.2
       postcss: 8.4.49
@@ -11783,6 +11897,7 @@ snapshots:
       '@types/node': 22.10.2
       fsevents: 2.3.3
       jiti: 2.4.2
+      less: 4.2.2
       sass: 1.83.0
       terser: 5.37.0
       tsx: 4.19.2

+ 18 - 0
src/api/flow/index.ts

@@ -0,0 +1,18 @@
+import request from "@/utils/request";
+
+// 保存流程定义
+export function saveFlowData(data: object) {
+  return request({
+    url: "/api/v1/definition/save",
+    method: "post",
+    data: data,
+  });
+}
+
+// 获取流程定义详情
+export function getFlowDataInfo(id: string) {
+  return request({
+    url: `/api/v1/definition/get/${id}`,
+    method: "get",
+  });
+}

+ 15 - 0
src/api/process/index.ts

@@ -126,6 +126,21 @@ export function syncMedias(params: object) {
   });
 }
 
+export function getLineSortList(ID: any) {
+  return request({
+    url: `/api/v1/op/routeOperation/lineSortList/${ID}`,
+    method: "get",
+  });
+}
+
+export function setLineSortList(params: any) {
+  return request({
+    url: `/api/v1/op/routeOperation/lineTagSortSetting`,
+    method: "post",
+    data: params,
+  });
+}
+
 export function testData(processId: string,deviceNo: string) {
   return request({
     url: `/api/v1/testData/syncSeqNo/get/${processId}/${deviceNo}`,

+ 1 - 0
src/common/configs/dictDataUtil.ts

@@ -71,6 +71,7 @@ const DictDataUtil = {
     process_type: "process_type",
     filter_order: "filter_order",
     quality_grade: "quality_grade",
+    flow_type: "flow_type",
   },
   EXPAND_FIELD_TABLE: {
     //字段类型

+ 14 - 0
src/components/MyFlow/index.ts

@@ -0,0 +1,14 @@
+// 导入组件,组件必须声明 name
+// import JXQFlow from "./src/flow.vue";
+import { App} from "vue";
+
+import HJFlow from './src/hjflow/index.vue'
+
+
+HJFlow.install = function (app: App) {
+    // app.component(JXQFlow.name!, JXQFlow)
+    app.component(HJFlow.name!, HJFlow)
+    return app
+}
+
+export default HJFlow

+ 18 - 0
src/components/MyFlow/src/Handle/CommonHandle.vue

@@ -0,0 +1,18 @@
+<script setup lang="ts"></script>
+
+<template>
+  <div class="common-handle" ></div>
+</template>
+
+<style scoped lang="less">
+//定义一个中空的样式
+.common-handle {
+  position: absolute;
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  background-color: #fff;
+  border: 1px solid #ccc;
+  cursor: pointer;
+}
+</style>

+ 13 - 0
src/components/MyFlow/src/background/DropzoneBackground.vue

@@ -0,0 +1,13 @@
+<script lang="ts" setup>
+import { Background } from '@vue-flow/background'
+</script>
+
+<template>
+  <div class="dropzone-background">
+    <Background :size="2" :gap="20" pattern-color="#BDBDBD" />
+
+    <div class="overlay">
+      <slot />
+    </div>
+  </div>
+</template>

+ 59 - 0
src/components/MyFlow/src/edges/commonEdge.vue

@@ -0,0 +1,59 @@
+<template>
+  <g>
+    <!-- 圆形markStart -->
+    <circle
+        :cx="props.sourceX"
+        :cy="props.sourceY - 4"
+        r="4"
+        :fill="status ? 'gray' : 'black'"
+    />
+    <!-- path -->
+    <path
+        :d="path"
+        fill="none"
+        :stroke="selectLine?.id === id ? 'blue' : 'gray'"
+        stroke-width="3"
+    />
+    <!-- 箭头markEnd -->
+    <path
+        :d="`M ${targetX} ${targetY + 2} L ${targetX - 5} ${targetY - 10} L ${targetX + 5} ${targetY - 10} Z`"
+        :fill="selectLine?.id === id ? 'blue' : 'gray'"
+        stroke="none"
+    />
+  </g>
+</template>
+
+<script lang="ts" setup>
+import { getSmoothStepPath, SmoothStepEdgeProps } from "@vue-flow/core";
+import { computed, inject, ref, watch } from "vue";
+import {CurrentSelectedEdgeProvideName} from "../types/comTypes";
+
+const selectLine = inject(CurrentSelectedEdgeProvideName);
+const status = ref(true);
+const props = defineProps<SmoothStepEdgeProps>();
+const path = computed(() => getSmoothStepPath(props)[0]);
+
+
+
+const getArrowTransform = (props: SmoothStepEdgeProps, hhh, dd ) => {
+  console.log(props, 'getArrowTransform');
+  const { targetPosition } = props;
+  if (targetPosition === "top") {
+    return `rotate(0 ${props.targetX} ${props.targetY})`;
+  }
+  if (targetPosition === "bottom") {
+    return `rotate(180 ${props.targetX} ${props.targetY})`;
+  }
+  if (targetPosition === "left") {
+    return `rotate(-90 ${props.targetX} ${props.targetY})`;
+  }
+  if (targetPosition === "right") {
+    return `rotate(90 ${props.targetX} ${props.targetY})`;
+  }
+}
+</script>
+<script lang="ts">
+export default {
+  name: "Custom",
+};
+</script>

+ 97 - 0
src/components/MyFlow/src/flow.vue

@@ -0,0 +1,97 @@
+<script setup lang="ts">
+import {ref, onMounted, watch} from 'vue';
+
+import type {Node, Edge} from '@vue-flow/core';
+import {HJMethodName, HJNodeData} from './types/comTypes';
+import DragData from "../src/leftDrags/test/dragData.vue"
+import WorkFlowDrag from '../src/leftDrags/workflow/dragData.vue'
+import Infomation from './information/index.vue'
+import {CircleCloseFilled} from '@element-plus/icons-vue'
+
+import {initialEdges, initialNodes} from './utils/index'
+
+import HJFlow from './hjflow/index.vue'
+
+defineOptions({
+  name: 'JXQFlow'
+})
+
+const nodes = ref<HJNodeData[]>([]);
+const edges = ref<Edge[]>([]);
+
+
+onMounted(() => {
+  // nodes.value = initialNodes;
+  // edges.value = initialEdges;
+});
+
+watch(nodes, (newVal, oldVal) => {
+  console.log('nodes change', newVal, oldVal);
+});
+
+watch(edges, (newVal, oldVal) => {
+  console.log('edges change', newVal, oldVal);
+});
+
+// vue 的相关操作
+const onNodeOperation = (name: HJMethodName, node: HJNodeData) : void => {
+  console.log('onNodeOperation', name, node);
+}
+
+// ======= 信息展示 =======
+const infoVisible = ref(false)
+const doubleCNode = ref<HJNodeData | null>(null) //双击的节点数据
+const closeInfo = () => {
+  infoVisible.value = false
+  doubleCNode.value = null
+}
+</script>
+
+<template>
+
+  <div v-if="nodes.length > 0"> {{ nodes[0]?.jsonData ?? "==" }}</div>
+  <div class="dnd-flow out-box">
+    <WorkFlowDrag></WorkFlowDrag>
+    <HJFlow v-model:nodes-data="nodes" v-model:edges-data="edges" @hjMethod="onNodeOperation"></HJFlow>
+    <!--    双击node节点展示的弹窗-->
+    <el-drawer v-model="infoVisible" :show-close="false" :destroy-on-close="true" :close-on-click-modal="false"
+               :append-to-body="true">
+      <template #header="{ close, titleId, titleClass }">
+        <h4 :id="titleId" :class="titleClass">编辑信息</h4>
+        <el-button type="danger" @click="closeInfo">
+          <el-icon class="el-icon--left">
+            <CircleCloseFilled/>
+          </el-icon>
+          Close
+        </el-button>
+      </template>
+
+      <Infomation></Infomation>
+    </el-drawer>
+  </div>
+
+</template>
+
+<style scoped lang="less">
+
+.out-box {
+  width: 100%;
+  height: 80vh;
+  border: 1px solid #ccc;
+  padding: 10px;
+  display: flex;
+}
+
+.vue-flow-box {
+  flex: 1;
+  flex-shrink: 0;
+  height: 100%;
+  border: 3px solid red;
+}
+</style>
+
+<!--<style>-->
+<!--.vue-flow__node {-->
+<!--  display: inline-block !important;-->
+<!--}-->
+<!--</style>-->

+ 233 - 0
src/components/MyFlow/src/hjflow/index.vue

@@ -0,0 +1,233 @@
+<script setup lang="ts">
+import {
+  ref,
+  onMounted,
+  InjectionKey,
+  Ref,
+  provide,
+  inject,
+  nextTick,
+  markRaw,
+} from "vue";
+import {
+  HJMethodName,
+  HJNodeData,
+  HJNodeType,
+  nodeTypes,
+  edgeTypes,
+  HJMethodProvideName,
+  CurrentHeaderOperationProvideName,
+  CurrentSelectedEdgeProvideName,
+  HJFlowProps,
+} from "../types/comTypes";
+import Panel from "../panel/index.vue";
+import CommonEdge from "../edges/commonEdge.vue";
+import DropzoneBackground from "../background/DropzoneBackground.vue";
+import { useVueFlow, VueFlow, Edge } from "@vue-flow/core";
+import useDragAndDrop from "../hooks/useDnD";
+import { sortNodesByPosition, useLayout } from "../utils/useLayout";
+import { useSnakeLayoutHook } from "../hooks/snakeHooks";
+import { ElMessage } from "element-plus";
+
+defineOptions({
+  name: "HJFlow",
+});
+
+const { onConnect, addEdges, fitView, updateNode, findNode } = useVueFlow();
+const { onDragOver, onDrop, onDragLeave, isDragOver } = useDragAndDrop();
+
+onConnect(addEdges);
+
+// ========== 外面传进来的参数  ===========
+const nodes = defineModel<HJNodeData[]>("nodesData");
+const edges = defineModel("edgesData");
+const props = defineProps<HJFlowProps>();
+
+// ==========vueflow 本身的方法操作 ===========
+const doubleClick = ({ node }) => {
+  // 如果通过双击弹出额外信息编辑是可以拿到jsonData的,但是如果在自定义node中是拿不到的,非要在自定义中拿就得把额外的信息放到data中。
+  // 还要看 是否要根据 dragData中的数据生成具体 avue页面。 之后可以尝试通过useVueFlow的find能否找到
+  console.log("doubleClick", node);
+};
+
+const currentClickEdgeData = ref<Edge | null>(null);
+provide(CurrentSelectedEdgeProvideName, currentClickEdgeData);
+const onClickEdge = ({ edge }) => {
+  currentClickEdgeData.value = JSON.parse(JSON.stringify(edge));
+};
+
+// ======= Emits =======
+const emits = defineEmits<{
+  hjMethod: [name: HJMethodName, node: HJNodeData];
+}>();
+
+// 提供一个孙子组件里面(比如自定义node里面) 调用本组件的方法
+// ======= Node操作 =======
+const currentHeaderOperationNodeData = ref<HJNodeData | null>(null); // 当前选中的header操作节点
+provide(CurrentHeaderOperationProvideName, currentHeaderOperationNodeData);
+provide(HJMethodProvideName, (name: HJMethodName, node: HJNodeData) => {
+  currentHeaderOperationNodeData.value = JSON.parse(JSON.stringify(node)); // 复制一份数据
+  console.log("provide", name, node);
+  emits("hjMethod", name, node);
+});
+
+///// === 数据历史纪录 用于回退或者重置按钮 === /////
+let originData: any = null;
+let historyList: any[] = [];
+
+///// === 所有的node 和edge change 调用的地方 === /////
+const onFlowNodesChange = (data: any) => {
+  if (
+    data.length > 0 &&
+    data[0].type &&
+    (data[0].type === "add" || data[0].type === "remove")
+  ) {
+    historyList.unshift(
+      JSON.stringify({
+        nodes: markRaw(nodes.value),
+        edges: markRaw(edges.value),
+      })
+    );
+    console.log("onFlowNodesChange historyList", historyList);
+  }
+};
+const onFlowEdgesChange = (data: any) => {
+  if (
+    data.length > 0 &&
+    data[0].type &&
+    (data[0].type === "add" || data[0].type === "remove")
+  ) {
+    historyList.unshift(
+      JSON.stringify({
+        nodes: markRaw(nodes.value),
+        edges: markRaw(edges.value),
+      })
+    );
+  }
+};
+///// === 所有的node 和edge change 调用的地方 === /////
+
+///// === 初始化   === /////
+const initFlow = (nodes: HJNodeData[], edges: any[]) => {
+  originData = JSON.stringify({ nodes, edges }); // 复制一份数据
+  historyList = []; // 初始化历史纪录
+};
+
+// 为父组件提供更新node中的data数据的操作 一般只更新information就行了 更新一个node的值
+const updateNodeData = (node: HJNodeData) => {
+  console.log("updateNodeData", node);
+
+  if (node.id) {
+    const findNodeData = findNode(node.id) as HJNodeData;
+    findNodeData.data.information = node.data.information;
+    console.log("findNodeData", findNodeData);
+    // updateNode(node.id, node)
+  }
+};
+
+// 清空状态 比如选中某一个节点的状态
+const clearStatus = () => {
+  currentHeaderOperationNodeData.value = null;
+  currentClickEdgeData.value = null;
+  historyList = [];
+};
+// 获取是否是正在编辑的状态 目前只判断节点的新增和删除 外面保存之后要清空状态
+const getChangedStatus = () => {
+  return historyList.length > 0;
+};
+defineExpose({
+  updateNodeData,
+  initFlow,
+  clearStatus,
+  getChangedStatus
+});
+
+// ======= Panel =======
+// 测试layout  点击切换布局时候的操作
+const { layout } = useLayout();
+
+async function layoutGraph(direction: "LR" | "TB") {
+  nodes.value = layout(nodes.value, edges.value, direction);
+
+  console.log("排序完", nodes.value);
+
+  nextTick(() => {
+    fitView();
+    nodes.value = sortNodesByPosition(nodes.value);
+  });
+}
+
+const pannelOperations = {
+  addNode: (nodeType: HJNodeType) => {
+    console.log("addNode", nodeType);
+  },
+  layout: (direction: "LR" | "TB") => {
+    layoutGraph(direction);
+  },
+  toSnake: () => {
+    console.log("toSnake");
+    nodes.value = useSnakeLayoutHook(nodes.value, edges.value);
+    nextTick(() => {
+      fitView();
+    });
+  },
+  reset: () => {
+    console.log("reset");
+    if (originData && JSON.parse(originData)) {
+      let data = JSON.parse(originData);
+      nodes.value = data.nodes;
+      edges.value = data.edges;
+      historyList = [];
+    }
+  },
+  back: () => {
+    console.log("back");
+    let firstHistory = historyList.shift();
+    if (firstHistory) {
+      let historyData = JSON.parse(firstHistory);
+      nodes.value = historyData.nodes;
+      edges.value = historyData.edges;
+    } else {
+      ElMessage.warning("已是初始线路");
+    }
+  },
+  saveTemplate: () => {
+    emits("hjMethod", HJMethodName.SaveTemplate, nodes.value[0]);
+  },
+};
+// ======= Panel ======= ↑↑↑↑↑↑↑↑↑↑↑↑↑↑
+</script>
+
+<template>
+  <VueFlow
+    v-model:nodes="nodes"
+    v-model:edges="edges"
+    :node-types="nodeTypes"
+    :edgeTypes="edgeTypes"
+    @dragover="onDragOver"
+    @dragleave="onDragLeave"
+    @drop="onDrop"
+    @nodeDoubleClick="doubleClick"
+    @edge-click="onClickEdge"
+    @nodes-change="onFlowNodesChange"
+    @edges-change="onFlowEdgesChange"
+  >
+    <DropzoneBackground
+      :style="{
+        backgroundColor: isDragOver ? '#e7f3ff' : 'transparent',
+        transition: 'background-color 0.2s ease',
+      }"
+    >
+      <!--      <p v-if="isDragOver">Drop here</p>-->
+    </DropzoneBackground>
+
+    <!--        右面上工具栏操作 -->
+    <Panel
+      v-if="showPanel"
+      v-on="pannelOperations"
+      :fun-names="panelBtns"
+    ></Panel>
+  </VueFlow>
+</template>
+
+<style scoped lang="less"></style>

+ 113 - 0
src/components/MyFlow/src/hooks/snakeHooks.ts

@@ -0,0 +1,113 @@
+export const isStraightFlow = (nodes: any[], edges: any[]) => {
+  let isSerial = false;
+  let message = "非标准直线型流程图";
+
+  // 如果nodes的数量小于1 说明没有节点 直接返回false
+  if (nodes && nodes.length < 2) {
+    return [isSerial, "节点数量少于2"];
+  }
+
+  // 如果edges的数量小于1 说明没有边 直接返回false
+  if (edges && edges.length < 1) {
+    return [isSerial, "边数量少于1"];
+  }
+
+  // 如果nodes的数量不等于edges的数量+1 说明没有完全连线或者是不是直线
+  if (nodes?.length !== edges?.length + 1) {
+    return [isSerial, message];
+  }
+
+  // 每一个边的源节点id组成一个set,如果set的数量和edges的数量不相等,说明不是标准直线型流程图
+  let sourceNodeIdsSet = new Set(
+    edges.map((edge) => {
+      return edge.source;
+    })
+  );
+  let targetNodeIdsSet = new Set(
+    edges.map((edge) => {
+      return edge.target;
+    })
+  );
+  if (
+    sourceNodeIdsSet.size !== edges.length ||
+    targetNodeIdsSet.size !== edges.length
+  ) {
+    return [isSerial, message];
+  }
+
+  // 能到这里说明是直线
+  return [true, "是直线"];
+};
+
+// 按照edges的顺序重新给nodes排序
+export const sortNodesByEdges = (nodes: any[], edges: any[]) => {
+  // 先获取所有边的node的id
+  let edgeIds = [edges[0].sourceNode.id];
+  for (let i = 1; i < edges.length; i++) {
+    edgeIds.push(edges[i].targetNode.id);
+  }
+
+  // 创建一个ids到对应的索引的映射
+  const idIndexMap = edgeIds.reduce((map, id, index) => {
+    map[id] = index;
+    return map;
+  }, {});
+
+  // 根据ids数组中的顺序对对象数组进行排序
+  return nodes.sort((a, b) => {
+    return idIndexMap[a.id] - idIndexMap[b.id];
+  });
+};
+
+export const useSnakeLayoutHook = (
+  nodes: any[],
+  edges: any[],
+  spacingY: number = 150,
+  spacingX: number = 230
+) => {
+  let [isStraight, msg] = isStraightFlow(nodes, edges);
+  if (!isStraight) {
+    ElMessage.warning(msg);
+    return nodes; //这里要返回nodes, 因为外面要用到
+  }
+
+  console.log("dddd", nodes, edges);
+
+  // nodes = sortNodesByEdges(nodes, edges);
+
+  const snakeNodes = [];
+
+  // 内边距
+  const paddingTop = 35;
+  const paddingLeft = 15;
+
+  // 这是每一个个列的数量  列数量
+  const nodesPerColumn = 6; //Math.floor(canvasHeight / spacingY);
+
+  // console.log(canvasHeight, canvasHeight / spacingY);
+
+  nodes.forEach((node, index) => {
+    const col = Math.floor(index / nodesPerColumn);
+    const row = index % nodesPerColumn;
+
+    // 根据行号计算位置,奇数列从下到上,偶数列从上到下
+    const adjustedRow = row;
+    // const adjustedRow = col % 2 === 0 ? row : nodesPerColumn - 1 - row;
+
+    node.position = {
+      x: col * spacingX + paddingLeft,
+      y: adjustedRow * spacingY + paddingTop,
+    };
+
+    // 更新奇数列的 handle 处理
+    // if (col % 2 !== 0) {
+    //   // 奇数列的节点调整 handle 位置
+    //   node.data.handleDirection = 'toTop'; //如果是第二列 也就是奇数列 调整箭头向上
+    // } else {
+    //   node.data.handleDirection = 'toDown';
+    // }
+
+    snakeNodes.push(node);
+  });
+  return snakeNodes;
+};

+ 145 - 0
src/components/MyFlow/src/hooks/useDnD.ts

@@ -0,0 +1,145 @@
+import {Position, useVueFlow} from '@vue-flow/core'
+import { ref, watch } from 'vue'
+import type { Node, Edge } from '@vue-flow/core';
+import {HJInterNodeData, HJPosition} from "../types/comTypes";
+
+import { v4 as uuidv4 } from "uuid";
+
+/**
+ * @returns {string} - A unique id.
+ */
+function getId(): string {
+    return uuidv4();
+}
+
+/**
+ * In a real world scenario you'd want to avoid creating refs in a global scope like this as they might not be cleaned up properly.
+ * @type {{draggedType: Ref<string|null>, isDragOver: Ref<boolean>, isDragging: Ref<boolean>}}
+ */
+const state = {
+    /**
+     * The type of the node being dragged.
+     */
+    dragData: ref<HJInterNodeData | null>(null),
+    draggedType: ref<string | null>(null),
+    isDragOver: ref(false),
+    isDragging: ref(false),
+}
+
+export default function useDragAndDrop() {
+    const { draggedType, isDragOver, isDragging , dragData} = state
+
+    const { addNodes, screenToFlowCoordinate, onNodesInitialized, updateNode } = useVueFlow()
+
+    watch(isDragging, (dragging) => {
+        document.body.style.userSelect = dragging ? 'none' : ''
+    })
+
+    // 第二个类型是HJInterNodeData
+    function onDragStart(event: DragEvent, data: HJInterNodeData) {
+        if (event.dataTransfer) {
+            event.dataTransfer.setData('application/vueflow', data.type!)
+            event.dataTransfer.effectAllowed = 'move'
+        }
+
+        draggedType.value = data.type!
+        dragData.value = data
+        isDragging.value = true
+
+        document.addEventListener('drop', onDragEnd)
+    }
+
+    /**
+     * Handles the drag over event.
+     *
+     * @param {DragEvent} event
+     */
+    function onDragOver(event: DragEvent) {
+        event.preventDefault()
+
+        if (draggedType.value) {
+            isDragOver.value = true
+
+            if (event.dataTransfer) {
+                event.dataTransfer.dropEffect = 'move'
+            }
+        }
+    }
+
+    function onDragLeave() {
+        isDragOver.value = false
+    }
+
+    function onDragEnd() {
+        isDragging.value = false
+        isDragOver.value = false
+        draggedType.value = null
+        dragData.value = null
+        document.removeEventListener('drop', onDragEnd)
+    }
+
+    /**
+     * Handles the drop event.
+     *
+     * @param {DragEvent} event
+     */
+    function onDrop(event: DragEvent) {
+        const position = screenToFlowCoordinate({
+            x: event.clientX,
+            y: event.clientY,
+        })
+
+        const nodeId = getId()
+
+        const newNode: Node = {
+            id: nodeId,
+            position,
+            data: dragData.value!.data,
+            type: dragData.value!.type,
+            targetPosition: Position.Top,
+            sourcePosition:  Position.Bottom,
+        }
+
+        console.log("从左侧拖拽的添加操作",newNode)
+        // 如果没有给node的data设置handles属性,这默认上下结构
+        if (!newNode.data.handles) {
+            newNode.data.handles = [
+                {
+                    type: 'source',
+                    position: HJPosition.Bottom,
+
+                },
+                {
+                    type: 'target',
+                    position: HJPosition.Top,
+                }
+            ]
+        }
+
+
+        /**
+         * Align node position after drop, so it's centered to the mouse
+         *
+         * We can hook into events even in a callback, and we can remove the event listener after it's been called.
+         */
+        const { off } = onNodesInitialized(() => {
+            updateNode(nodeId, (node) => ({
+                position: { x: node.position.x - 100, y: node.position.y - node.dimensions.height / 2 },
+            }))
+
+            off()
+        })
+
+        addNodes(newNode)
+    }
+
+    return {
+        draggedType,
+        isDragOver,
+        isDragging,
+        onDragStart,
+        onDragLeave,
+        onDragOver,
+        onDrop,
+    }
+}

+ 0 - 0
src/components/MyFlow/src/hooks/useFlow.ts


+ 11 - 0
src/components/MyFlow/src/information/index.vue

@@ -0,0 +1,11 @@
+<script setup lang="ts">
+
+</script>
+
+<template>
+  <div>这里是额外信息,需要具体拟定需求之后在决定怎么写</div>
+</template>
+
+<style scoped lang="less">
+
+</style>

+ 167 - 0
src/components/MyFlow/src/leftDrags/test/configs.ts

@@ -0,0 +1,167 @@
+import {HJInterNodeData, HJPosition} from "../../types/comTypes";
+import { ref } from 'vue'
+import { HJNodeType} from '../../types/comTypes'
+
+
+export const dragNodes = ref<HJInterNodeData[]>(
+    [
+        {
+            type: HJNodeType.custom,
+            data: {
+                label: "普通上1下1",
+                handles: [
+                    {
+                        type: 'source',
+                        position: HJPosition.Bottom,
+
+                    },
+                    {
+                        type: 'target',
+                        position: HJPosition.Top,
+                    }
+                ]
+            }
+        },
+        {
+            type: HJNodeType.nest,
+            data: {
+                label: "左2 右1",
+                handles: [
+                    {
+                        type: 'source',
+                        position: HJPosition.Left,
+                        style: {
+                            top: '10px'
+                        }
+                    },
+                    {
+                        type: 'source',
+                        position: HJPosition.Left,
+                        style: {
+                            top: 'auto',
+                            bottom: '10px'
+                        }
+                    },
+                    {
+                        type: 'target',
+                        position: HJPosition.Right,
+                    }
+                ]
+            }
+        },
+        {
+            type: HJNodeType.custom,
+            data: {
+                label: "上2 下3",
+                handles: [
+                    {
+                        type: 'source',
+                        position: HJPosition.Top,
+                        style: {
+                            left: '10px'
+                        }
+                    },
+                    {
+                        type: 'source',
+                        position: HJPosition.Top,
+                        style: {
+                            left: 'auto',
+                            right: '10px'
+                        }
+                    },
+                    {
+                        type: 'target',
+                        position: HJPosition.Bottom,
+                        style: {
+
+                             left: '10px'
+                        }
+                    },
+                    {
+                        type: 'target',
+                        position: HJPosition.Bottom,
+                        style: {
+                            left: '50%'
+                        }
+                    },
+                    {
+                        type: 'target',
+                        position: HJPosition.Bottom,
+                        style: {
+                            left: 'auto',
+                             right: '10px'
+                        }
+                    }
+
+
+                ]
+            }
+        },
+        {
+            type: HJNodeType.nest,
+            data: {
+                label: "左三右二",
+                handles: [
+                    {
+                        type: 'source',
+                        position: HJPosition.Left,
+                        style: {
+                            top: '10px'
+                        }
+                    },
+                    {
+                        type: 'source',
+                        position: HJPosition.Left,
+                        style: {
+                            top: 'auto',
+                            bottom: '10px'
+                        }
+                    },
+                    {
+                        type: 'source',
+                        position: HJPosition.Left,
+                        style: {
+                            top: '50%',
+                            // bottom: '10px'
+                        }
+                    },
+                    {
+                        type: 'target',
+                        position: HJPosition.Right,
+                        style: {
+                            top: '10px'
+                        }
+                    },
+                    {
+                        type: 'target',
+                        position: HJPosition.Right,
+                        style: {
+                            top: '50%',
+                            // bottom: '10px'
+                        }
+                    }
+
+
+                ]
+            }
+        },
+        {
+            type: HJNodeType.numberInput,
+            data: {
+                label: "Node 3",
+            }
+        },
+        {
+            type: HJNodeType.numberFunction,
+            data: {
+                label: "Node 4",
+            }
+        },
+        {
+            type: HJNodeType.numberResult,
+            data: {
+                label: "Node 5",
+            }
+        },
+    ]
+)

+ 41 - 0
src/components/MyFlow/src/leftDrags/test/dragData.vue

@@ -0,0 +1,41 @@
+<script setup lang="ts">
+
+import {dragNodes} from "./configs";
+import { VueDraggable } from 'vue-draggable-plus'
+
+import useDragAndDrop from '../../hooks/useDnD'
+
+const { onDragStart } = useDragAndDrop()
+</script>
+
+<template>
+<div class="drag-container" >
+  <div class="drag-item" v-for="(dragData, index) in dragNodes" :key="index" v-text="dragData.data.label" :draggable="true" @dragstart="onDragStart($event, dragData)" />
+
+</div>
+</template>
+
+<style scoped lang="less">
+.drag-container {
+  display: flex;
+  flex-direction: column;
+  justify-content: start;
+  align-items: center;
+  height: 100%;
+  width: 200px;
+  background: #ccc;
+  padding: 8px 10px;
+  .drag-item {
+    background: #fff;
+    width: 100%;
+    font-size: 14px;
+    text-align: center;
+    height: 20px;
+    line-height: 20px;
+    border-radius: 4px;
+    border: 1px solid rgba(204, 204, 204, 0.9);
+    margin-bottom: 10px;
+    cursor: pointer;
+  }
+}
+</style>

+ 41 - 0
src/components/MyFlow/src/leftDrags/workflow/configs.ts

@@ -0,0 +1,41 @@
+import { HJInterNodeData, HJPosition } from "../../types/comTypes";
+import { ref } from "vue";
+import { HJNodeType } from "../../types/comTypes";
+
+export const dragNodes = ref<HJInterNodeData[]>([
+  {
+    type: HJNodeType.workFlow,
+    data: {
+      label: "发起人",
+      // handles: [
+      //     {
+      //         type: 'source',
+      //         position: HJPosition.Bottom,
+      //
+      //     },
+      //     {
+      //         type: 'target',
+      //         position: HJPosition.Top,
+      //     }
+      // ]
+    },
+  },
+  {
+    type: HJNodeType.workFlow,
+    data: {
+      label: "审核人",
+    },
+  },
+  {
+    type: HJNodeType.wfStart,
+    data: {
+      label: "开始",
+    },
+  },
+  {
+    type: HJNodeType.wfEnd,
+    data: {
+      label: "结束",
+    },
+  },
+]);

+ 41 - 0
src/components/MyFlow/src/leftDrags/workflow/dragData.vue

@@ -0,0 +1,41 @@
+<script setup lang="ts">
+
+import {dragNodes} from "./configs";
+import { VueDraggable } from 'vue-draggable-plus'
+
+import useDragAndDrop from '../../hooks/useDnD'
+
+const { onDragStart } = useDragAndDrop()
+</script>
+
+<template>
+<div class="drag-container" >
+  <div class="drag-item" v-for="(dragData, index) in dragNodes" :key="index" v-text="dragData.data.label" :draggable="true" @dragstart="onDragStart($event, dragData)" />
+
+</div>
+</template>
+
+<style scoped lang="less">
+.drag-container {
+  display: flex;
+  flex-direction: column;
+  justify-content: start;
+  align-items: center;
+  height: 100%;
+  width: 150px;
+  background: #ccc;
+  padding: 8px 10px;
+  .drag-item {
+    background: #fff;
+    width: 100%;
+    font-size: 14px;
+    text-align: center;
+    height: 20px;
+    line-height: 20px;
+    border-radius: 4px;
+    border: 1px solid rgba(204, 204, 204, 0.9);
+    margin-bottom: 10px;
+    cursor: pointer;
+  }
+}
+</style>

+ 11 - 0
src/components/MyFlow/src/leftDrags/workflow/editInfo.vue

@@ -0,0 +1,11 @@
+<script setup lang="ts">
+
+</script>
+
+<template>
+
+</template>
+
+<style scoped lang="less">
+
+</style>

+ 24 - 0
src/components/MyFlow/src/nodes/CommonNode.vue

@@ -0,0 +1,24 @@
+<script setup lang="ts">
+import type { Node } from '@vue-flow/core';
+import { onMounted } from 'vue';
+import { HJInterNodeData, HJNodeData } from '../types/comTypes';
+import Basic from './com/basic.vue';
+
+// 即使外部定义的类型数据再多,传进来的也是Node类型。
+const props = defineProps<HJInterNodeData>();
+
+onMounted(() => {
+  console.log('mounted', props);
+});
+</script>
+
+<template>
+    <Basic v-bind="props" >
+      default-4
+      <template #header>
+        header-1
+      </template>
+    </Basic>
+</template>
+
+<style scoped lang="less"></style>

+ 18 - 0
src/components/MyFlow/src/nodes/NestNode.vue

@@ -0,0 +1,18 @@
+<script setup lang="ts">
+import type { Node } from '@vue-flow/core';
+import { onMounted } from 'vue';
+import { HJInterNodeData, HJNodeData } from '../types/comTypes';
+import Basic from './com/basic.vue';
+
+const props = defineProps<HJInterNodeData>();
+
+onMounted(() => {
+  console.log('mounted', props);
+});
+</script>
+
+<template>
+  <Basic v-bind="props"> </Basic>
+</template>
+
+<style scoped lang="less"></style>

+ 7 - 0
src/components/MyFlow/src/nodes/NumberFunNode.vue

@@ -0,0 +1,7 @@
+<script setup lang="ts"></script>
+
+<template>
+  <div>NumberFunNode.vue</div>
+</template>
+
+<style scoped lang="less"></style>

+ 7 - 0
src/components/MyFlow/src/nodes/NumberInputNode.vue

@@ -0,0 +1,7 @@
+<script setup lang="ts"></script>
+
+<template>
+  <div>number input node</div>
+</template>
+
+<style scoped lang="less"></style>

+ 7 - 0
src/components/MyFlow/src/nodes/NumberResultNode.vue

@@ -0,0 +1,7 @@
+<script setup lang="ts"></script>
+
+<template>
+  <div>NumberResultNode.vue</div>
+</template>
+
+<style scoped lang="less"></style>

+ 94 - 0
src/components/MyFlow/src/nodes/com/basic.vue

@@ -0,0 +1,94 @@
+<script setup lang="ts">
+import {
+  CurrentHeaderOperationProvideName,
+  GrandparentMethod,
+  HJInterNodeData,
+  HJMethodName, HJNodeData,
+  HJPosition
+} from '../../types/comTypes';
+import {Handle} from '@vue-flow/core'
+import {Position} from '@vue-flow/core';
+import {inject, watch} from "vue";
+const props = defineProps<HJInterNodeData>();
+
+//  'basic-box--selected': props.data.isSelected || (currentHeaderOperationNode && currentHeaderOperationNode.id === props.id) 如果是选中或者正在通过header编辑节点信息的样式
+const currentHeaderOperationNode = inject<HJNodeData | null>(CurrentHeaderOperationProvideName)
+
+
+</script>
+
+<template>
+  <div
+      class="basic-box"
+      :class="{
+      [`basic-box--${props.type}`]: props.type,
+      'basic-box--selected': props.data.isSelected || (currentHeaderOperationNode && currentHeaderOperationNode.id === props.id),
+      'basic-box--dragging': props.data.isDragging,
+    }"
+  >
+
+    <slot name="header"></slot>
+
+    <slot name="default">
+    </slot>
+
+    <!--    这里是handle的操作-->
+    <div v-if="props.sourcePosition && props.targetPosition">
+      <Handle class="handle-style"  type="source" :position="props.sourcePosition"/>
+      <Handle class="handle-style"  type="target" :position="props.targetPosition"/>
+    </div>
+    <div v-else-if="props.data.handles && props.data.handles.length">
+      <Handle class="handle-style" v-for="(handle, index) in props.data.handles"  :type="handle.type"
+              :position="handle.position" :style="handle.style"/>
+    </div>
+    <div v-else>
+      <Handle class="handle-style"  type="target" :position="Position.Top"/>
+      <Handle class="handle-style"  type="source" :position="Position.Bottom"/>
+    </div>
+
+  </div>
+</template>
+
+<style scoped lang="less">
+.basic-box {
+  width: 200px;
+  height: 60px;
+  border: 2px solid #625454;
+  border-radius: 5px;
+  background-color: rgba(255, 255, 255);
+  overflow: hidden;
+}
+
+.handle-style {
+  width: 10px;
+  height: 10px;
+}
+
+
+//可以根据type类型来设置不同的Node的样式
+.basic-box--custom {
+  border-style: dashed;
+}
+
+.basic-box--nest {
+  border-style: double;
+  width: 300px;
+  height: 100px;
+  background: transparent;
+}
+
+.basic-box:hover {
+  //background-color: #30bfcb;
+  border-color: #2b48dd;
+}
+
+.basic-box--selected {
+  //background-color: #388d25;
+  border-color: rgb(184, 129.6, 48);
+  color: rgb(184, 129.6, 48);
+}
+
+.basic-box--dragging {
+  background-color: #da5565;
+}
+</style>

+ 49 - 0
src/components/MyFlow/src/nodes/com/operationHeader.vue

@@ -0,0 +1,49 @@
+<script setup lang="ts">
+
+import {Edit} from "@element-plus/icons-vue";
+import {GrandparentMethod, HJInterNodeData, HJMethodName, HJMethodProvideName} from "../../types/comTypes";
+import {inject, markRaw} from "vue";
+const props = defineProps<HJInterNodeData>();
+
+
+const editMethod = inject<GrandparentMethod>(HJMethodProvideName)
+
+const editClick = () => {
+  editMethod && editMethod( HJMethodName.EditNode,JSON.parse(JSON.stringify(markRaw(props))))
+}
+
+</script>
+
+<template>
+  <div class="box-header">
+    <div> {{ props?.data?.label ?? "" }}</div>
+    <div class="right-btns">
+      <el-icon :size="18" class="right-icon">
+        <Edit  @click.prevent="editClick"/>
+      </el-icon>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="less">
+.box-header {
+  height: 25px;
+  line-height: 25px;
+  font-size: 12px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0 8px;
+  background-color: #ccc;
+
+  .right-btns {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .right-icon {
+    cursor: pointer;
+  }
+}
+</style>

+ 42 - 0
src/components/MyFlow/src/nodes/universal/UniversalNode.vue

@@ -0,0 +1,42 @@
+<script setup lang="ts">
+import type { Node } from "@vue-flow/core";
+import { inject, markRaw, onMounted } from "vue";
+import {
+  GrandparentMethod,
+  HJInterNodeData,
+  HJMethodName,
+  HJNodeData,
+} from "../../types/comTypes";
+import Basic from "../com/basic.vue";
+import { CirclePlus } from "@element-plus/icons-vue";
+import OperationHeader from "../com/operationHeader.vue";
+
+// 即使外部定义的类型数据再多,传进来的也是Node类型。
+const props = defineProps<HJInterNodeData>();
+
+onMounted(() => {
+  console.log("mounted", props);
+});
+</script>
+
+<template>
+  <Basic v-bind="props">
+    <template #header>
+      <OperationHeader v-bind="props"></OperationHeader>
+    </template>
+    <template #default>
+      <div class="content-text">
+        {{ props.data?.information?.functionName ?? "请配置名称" }}
+      </div>
+    </template>
+  </Basic>
+</template>
+
+<style scoped lang="less">
+.content-text {
+  height: 36px;
+  line-height: 36px;
+  font-size: 14px;
+  text-align: center;
+}
+</style>

+ 26 - 0
src/components/MyFlow/src/nodes/workflow/WFEndNode.vue

@@ -0,0 +1,26 @@
+<script setup lang="ts">
+import { Handle, Position } from "@vue-flow/core";
+</script>
+
+<template>
+  <div class="tip-node-box">
+    结束
+    <Handle class="handle-style" type="source" :position="Position.Top" />
+  </div>
+</template>
+
+<style scoped lang="less">
+.tip-node-box {
+  width: 120px;
+  height: 50px;
+  border-radius: 15px;
+  border: 2px solid #ccc;
+  background-color: white;
+  text-align: center;
+  line-height: 50px;
+}
+.handle-style {
+  width: 10px;
+  height: 10px;
+}
+</style>

+ 26 - 0
src/components/MyFlow/src/nodes/workflow/WFStartNode.vue

@@ -0,0 +1,26 @@
+<script setup lang="ts">
+import { Handle, Position } from "@vue-flow/core";
+</script>
+
+<template>
+  <div class="tip-node-box">
+    开始
+    <Handle class="handle-style" type="source" :position="Position.Bottom" />
+  </div>
+</template>
+
+<style scoped lang="less">
+.tip-node-box {
+  width: 120px;
+  height: 50px;
+  border-radius: 15px;
+  border: 2px solid #ccc;
+  background-color: white;
+  text-align: center;
+  line-height: 50px;
+}
+.handle-style {
+  width: 10px;
+  height: 10px;
+}
+</style>

+ 40 - 0
src/components/MyFlow/src/nodes/workflow/WorkFlowNode.vue

@@ -0,0 +1,40 @@
+<script setup lang="ts">
+import type {Node} from '@vue-flow/core';
+import {inject, markRaw, onMounted} from 'vue';
+import {GrandparentMethod, HJInterNodeData, HJMethodName, HJNodeData} from '../../types/comTypes';
+import Basic from '../com/basic.vue';
+import {CirclePlus} from "@element-plus/icons-vue";
+import OperationHeader from '../com/operationHeader.vue'
+
+// 即使外部定义的类型数据再多,传进来的也是Node类型。
+const props = defineProps<HJInterNodeData>();
+
+onMounted(() => {
+  console.log('mounted', props);
+});
+
+</script>
+
+<template>
+  <Basic v-bind="props">
+    <template #header>
+      <OperationHeader v-bind="props"></OperationHeader>
+    </template>
+    <template #default>
+      <div class="content-text">
+        {{ props.data?.information?.desc ?? '暂无描述' }}
+      </div>
+    </template>
+
+  </Basic>
+
+</template>
+
+<style scoped lang="less">
+.content-text {
+  height: 36px;
+  line-height: 36px;
+  font-size: 14px;
+  text-align: center;
+}
+</style>

文件差異過大導致無法顯示
+ 20 - 0
src/components/MyFlow/src/panel/btns/Back.vue


+ 44 - 0
src/components/MyFlow/src/panel/btns/add-node.vue

@@ -0,0 +1,44 @@
+<script setup lang="ts">
+import { HJNodeType } from '../../types/comTypes';
+import {
+    Plus,
+} from '@element-plus/icons-vue'
+import { ArrowDown } from '@element-plus/icons-vue'
+
+const emits = defineEmits(['addNode']);
+
+const options = [
+  { content: '数字输入', value: HJNodeType.numberInput },
+  { content: '数字计算', value: HJNodeType.numberFunction },
+  { content: '数字输出', value: HJNodeType.numberResult },
+];
+
+const clickHandler = (data) => {
+  console.log('clickHandler');
+  emits('addNode', data.value);
+};
+
+const changeHandler = (data) => {
+  console.log('changeHandler');
+//   这个不起作用 也不打印 不知道为什么
+
+}
+
+</script>
+
+<template>
+  <el-dropdown  @click="clickHandler" >
+    <el-button type="primary" :icon="Plus" circle />
+    <template #dropdown>
+      <el-dropdown-menu @change="changeHandler">
+        <el-dropdown-item>Action 1</el-dropdown-item>
+        <el-dropdown-item>Action 2</el-dropdown-item>
+        <el-dropdown-item>Action 3</el-dropdown-item>
+        <el-dropdown-item>Action 4</el-dropdown-item>
+        <el-dropdown-item>Action 5</el-dropdown-item>
+      </el-dropdown-menu>
+    </template>
+  </el-dropdown>
+</template>
+
+<style scoped lang="less"></style>

+ 27 - 0
src/components/MyFlow/src/panel/btns/layout-LR.vue

@@ -0,0 +1,27 @@
+<script setup lang="ts">
+</script>
+
+<template>
+  <button title="LR">
+    <svg
+        viewBox="0 0 24 24"
+        xmlns="http://www.w3.org/2000/svg"
+    >
+      <path d="M2,12 L22,12" stroke="currentColor" stroke-width="2" />
+      <path
+          d="M7,7 L2,12 L7,17"
+          stroke="currentColor"
+          stroke-width="2"
+          fill="none"
+      />
+      <path
+          d="M17,7 L22,12 L17,17"
+          stroke="currentColor"
+          stroke-width="2"
+          fill="none"
+      />
+    </svg>
+  </button>
+</template>
+
+<style scoped lang="less"></style>

+ 29 - 0
src/components/MyFlow/src/panel/btns/layout-TB.vue

@@ -0,0 +1,29 @@
+<script setup lang="ts">
+</script>
+
+<template>
+  <button title="TB">
+    <svg
+        width="24"
+        height="24"
+        viewBox="0 0 24 24"
+        xmlns="http://www.w3.org/2000/svg"
+    >
+      <path d="M12,2 L12,22" stroke="currentColor" stroke-width="2" />
+      <path
+          d="M7,7 L12,2 L17,7"
+          stroke="currentColor"
+          stroke-width="2"
+          fill="none"
+      />
+      <path
+          d="M7,17 L12,22 L17,17"
+          stroke="currentColor"
+          stroke-width="2"
+          fill="none"
+      />
+    </svg>
+  </button>
+</template>
+
+<style scoped lang="less"></style>

+ 37 - 0
src/components/MyFlow/src/panel/btns/reset.vue

@@ -0,0 +1,37 @@
+<script setup lang="ts"></script>
+
+<template>
+  <button title="重置">
+    <svg
+        width="48"
+        height="48"
+        viewBox="0 0 48 48"
+        fill="none"
+        xmlns="http://www.w3.org/2000/svg"
+    >
+      <path
+          d="M4 12C4 7.58172 7.58172 4 12 4H36C40.4183 4 44 7.58172 44 12V36C44 40.4183 40.4183 44 36 44H12C7.58172 44 4 40.4183 4 36V12Z"
+      />
+      <path
+          fill-rule="evenodd"
+          clip-rule="evenodd"
+          d="M36.75 12.5V25.5H33.75V12.5H36.75Z"
+          fill="white"
+      />
+      <path
+          fill-rule="evenodd"
+          clip-rule="evenodd"
+          d="M14.25 22.5V35.5H11.25V22.5H14.25Z"
+          fill="white"
+      />
+      <path
+          fill-rule="evenodd"
+          clip-rule="evenodd"
+          d="M14.8259 15.1457C17.1428 12.7456 20.398 11.25 24 11.25C31.0416 11.25 36.75 16.9584 36.75 24V25.5H33.75V24C33.75 18.6152 29.3848 14.25 24 14.25C21.2451 14.25 18.7592 15.3906 16.9843 17.2293L15.9425 18.3085L13.7841 16.2249L14.8259 15.1457ZM14.25 22.5V24C14.25 29.3848 18.6152 33.75 24 33.75C26.6307 33.75 29.0155 32.7101 30.7707 31.0157L31.8499 29.9739L33.9335 32.1324L32.8543 33.1741C30.5623 35.3866 27.4388 36.75 24 36.75C16.9584 36.75 11.25 31.0416 11.25 24V22.5H14.25Z"
+          fill="white"
+      />
+    </svg>
+  </button>
+</template>
+
+<style scoped lang="less"></style>

文件差異過大導致無法顯示
+ 32 - 0
src/components/MyFlow/src/panel/btns/snake.vue


+ 108 - 0
src/components/MyFlow/src/panel/index.vue

@@ -0,0 +1,108 @@
+<script setup lang="ts">
+import { Panel } from "@vue-flow/core";
+import Back from "./btns/Back.vue";
+import AddNode from "../panel/btns/add-node.vue";
+import LayoutLR from "./btns/layout-LR.vue";
+import LayoutTB from "./btns/layout-TB.vue";
+import SnakeBtn from "./btns/snake.vue";
+import ResetBtn from "./btns/reset.vue";
+import { HJPanelFunName } from "../types/comTypes";
+
+// 定义一个props,参数是数组,而且数组里面的值只能是 HJPannelFunName 的值
+const props = defineProps<{
+  funNames: HJPanelFunName[];
+}>();
+
+const emits = defineEmits([
+  "addNode",
+  "reset",
+  "layout",
+  "toSnake",
+  "back",
+  "saveTemplate",
+]);
+
+const onAddNode = (type: string) => {
+  emits("addNode", type);
+};
+</script>
+
+<template>
+  <Panel position="top-right" class="process-panel">
+    <el-button
+      type="primary"
+      style="width: 80px"
+      @click="() => emits('saveTemplate')"
+      v-if="props.funNames.includes('saveTemplate')"
+      >保存模版</el-button
+    >
+    <Back @click="() => emits('back')" v-if="props.funNames.includes('back')" />
+    <add-node
+      @addNode="onAddNode"
+      v-if="props.funNames.includes('addNode')"
+    ></add-node>
+    <LayoutLR
+      @click="() => emits('layout', 'LR')"
+      v-if="props.funNames.includes('layoutLR')"
+    />
+    <LayoutTB
+      @click="() => emits('layout', 'TB')"
+      v-if="props.funNames.includes('layoutTB')"
+    />
+    <SnakeBtn
+      @click="() => emits('toSnake')"
+      v-if="props.funNames.includes('toSnake')"
+    />
+    <ResetBtn
+      @click="() => emits('reset')"
+      v-if="props.funNames.includes('reset')"
+    />
+  </Panel>
+</template>
+
+<style scoped lang="less">
+.process-panel {
+  background-color: #2d3748;
+  padding: 4px;
+  border-radius: 8px;
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
+  display: flex;
+  flex-direction: row-reverse;
+  gap: 8px;
+}
+
+.process-panel button {
+  border: none;
+  cursor: pointer;
+  background-color: #4a5568;
+  border-radius: 8px;
+  color: white;
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
+  font-size: 16px;
+  width: 30px;
+  height: 30px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.checkbox-panel {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.process-panel button:hover,
+.layout-panel button:hover {
+  background-color: #2563eb;
+  transition: background-color 0.2s;
+}
+
+.process-panel label {
+  color: white;
+  font-size: 12px;
+}
+</style>

+ 45 - 0
src/components/MyFlow/src/styles/index.scss

@@ -0,0 +1,45 @@
+
+
+.common-node {
+  background-color: #c86c6c;
+}
+
+.nest-node {
+  min-width: 200px;
+  min-height: 200px;
+  background-color: rgba(8, 169, 117, 0.1);
+}
+
+
+.vue-flow__minimap {
+  transform: scale(75%);
+  transform-origin: bottom right;
+}
+
+
+
+.dnd-flow .vue-flow-wrapper {
+  flex-grow:1;
+  height:100%
+}
+
+
+
+.dropzone-background {
+  position:relative;
+  height:100%;
+  width:100%
+}
+
+.dropzone-background .overlay {
+  position:absolute;
+  top:0;
+  left:0;
+  height:100%;
+  width:100%;
+  display:flex;
+  align-items:center;
+  justify-content:center;
+  z-index:1;
+  pointer-events:none
+}

+ 184 - 0
src/components/MyFlow/src/testShenpiliu.vue

@@ -0,0 +1,184 @@
+<script setup lang="ts">
+import { ref, onMounted, watch, reactive, markRaw } from "vue";
+
+import type { Node, Edge } from "@vue-flow/core";
+import {
+  HJMethodName,
+  HJNodeData,
+  HJPanelFunNameArray,
+} from "./types/comTypes";
+import WorkFlowDrag from "../src/leftDrags/workflow/dragData.vue";
+import HJFlow from "./hjflow/index.vue";
+
+import type { ComponentSize, FormInstance, FormRules } from "element-plus";
+
+interface HJShenPiliuData {
+  name: string; //节点名称
+  desc: string; //节点描述
+}
+
+const formSize = ref<ComponentSize>("default");
+const ruleFormRef = ref<FormInstance>();
+const formData = ref<HJShenPiliuData>({
+  name: "",
+  desc: "",
+});
+
+const rules = reactive<FormRules<HJShenPiliuData>>({
+  name: [
+    { required: true, message: "Please input Activity name", trigger: "blur" },
+  ],
+  desc: [
+    { required: true, message: "Please input activity form", trigger: "blur" },
+  ],
+});
+
+const submitForm = async (formEl: FormInstance | undefined) => {
+  // if (!formEl) return
+  // await formEl.validate((valid, fields) => {
+  //   if (valid) {
+  //     console.log('submit!')
+  //   } else {
+  //     console.log('error submit!', fields)
+  //   }
+  // })
+
+  if (flowRef.value && flowRef.value.updateNodeData && selectedNode.value) {
+    selectedNode.value.data.information = JSON.parse(
+      JSON.stringify(formData.value)
+    );
+    flowRef.value.updateNodeData(
+      JSON.parse(JSON.stringify(markRaw(selectedNode.value)))
+    );
+    ruleFormRef.value && ruleFormRef.value.resetFields();
+    selectedNode.value = null;
+  }
+};
+
+const cancel = () => {
+  ruleFormRef.value && ruleFormRef.value.resetFields();
+};
+
+const flowRef = ref();
+const nodes = ref<HJNodeData[]>([]);
+const edges = ref<Edge[]>([]);
+
+onMounted(() => {
+  // 接口调用完成要初始化 ,为的是保存一个最初的数据
+  flowRef.value && flowRef.value.initFlow([], []);
+});
+
+// ======= 信息展示 =======
+const infoVisible = ref(false);
+const selectedNode = ref<HJNodeData | null>(null); //双击的节点数据
+const closeInfo = () => {
+  infoVisible.value = false;
+  selectedNode.value = null;
+};
+
+// vue 的相关操作
+const onNodeOperation = (name: HJMethodName, node: HJNodeData): void => {
+  if (name === HJMethodName.EditNode) {
+    selectedNode.value = null;
+    infoVisible.value = true;
+    selectedNode.value = JSON.parse(JSON.stringify(node));
+    ruleFormRef.value && ruleFormRef.value.resetFields();
+
+    if (selectedNode.value && selectedNode.value.data.information) {
+      formData.value = JSON.parse(
+        JSON.stringify(selectedNode.value.data.information)
+      );
+    }
+    console.log("selectedNode", selectedNode.value);
+  } else if (name === HJMethodName.SaveTemplate) {
+    console.log("保存模板");
+  }
+};
+
+const testGetCurrentData = () => {
+  let p = {
+    nodes: markRaw(nodes.value),
+    edges: markRaw(edges.value),
+  };
+  console.log("=========当前数据======", JSON.stringify(p), p);
+};
+</script>
+
+<template>
+  <div class="out-box">
+    <WorkFlowDrag></WorkFlowDrag>
+    <HJFlow
+      class="vue-flow-box"
+      ref="flowRef"
+      v-model:nodes-data="nodes"
+      v-model:edges-data="edges"
+      @hjMethod="onNodeOperation"
+      :show-panel="true"
+      :panel-btns="['layoutTB', 'saveTemplate']"
+    ></HJFlow>
+    <div class="right-message">
+      <div class="flow-header-name" @click="testGetCurrentData">
+        当前工作流名称
+      </div>
+      <el-form
+        ref="ruleFormRef"
+        style="max-width: 600px"
+        :model="formData"
+        label-width="auto"
+        class="demo-ruleForm"
+        :size="formSize"
+        status-icon
+      >
+        <el-form-item label="Activity name" prop="name">
+          <el-input v-model="formData.name" />
+        </el-form-item>
+        <el-form-item label="Activity form" prop="desc">
+          <el-input v-model="formData.desc" type="textarea" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="submitForm(ruleFormRef)">
+            保存
+          </el-button>
+          <el-button @click="cancel">取消</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="less">
+.out-box {
+  width: 100%;
+  height: 80vh;
+  border: 1px solid #ccc;
+  display: flex;
+}
+
+.vue-flow-box {
+  flex: 1;
+  flex-shrink: 0;
+  height: 100%;
+  border: 3px solid red;
+  //width: 300px;
+  //height: 500px;
+}
+
+.right-message {
+  width: 400px;
+  height: 100%;
+  border: 1px solid #ccc;
+  background-color: white;
+  padding: 10px;
+  display: flex;
+  flex-direction: column;
+  justify-content: start;
+  align-items: start;
+
+  .flow-header-name {
+    font-size: 18px;
+    font-weight: bold;
+    margin-top: 10px;
+    margin-bottom: 10px;
+  }
+}
+</style>

+ 130 - 0
src/components/MyFlow/src/types/comTypes.ts

@@ -0,0 +1,130 @@
+// 这里是 vue-flow 自定义的组件的types集合
+import { markRaw } from "vue";
+import type { Node, Edge } from "@vue-flow/core";
+
+import CommonEdge from "../edges/commonEdge.vue";
+
+import CommonNode from "../nodes/CommonNode.vue";
+import NestNode from "../nodes/NestNode.vue";
+import NumberInputNode from "../nodes/NumberInputNode.vue";
+import NumberFunctionNode from "../nodes/NumberFunNode.vue";
+import NumberResultNode from "../nodes/NumberResultNode.vue";
+
+import UniversalNode from "../nodes/universal/UniversalNode.vue";
+// 工作流相关
+import WorkFlowNode from "../nodes/workflow/WorkFlowNode.vue";
+import WFStartNode from "../nodes/workflow/WFStartNode.vue";
+import WFEndNode from "../nodes/workflow/WFEndNode.vue";
+
+export enum HJNodeType {
+  custom = "custom",
+  nest = "nest",
+  //   算数相关
+  numberInput = "number-input",
+  numberFunction = "number-function",
+  numberResult = "number-result",
+  //   工作流相关
+  workFlow = "work-flow",
+  wfStart = "wf-start",
+  wfEnd = "wf-end",
+  //   公共 通用 上面是header 下面是content
+  universal = "universal",
+}
+
+export const nodeTypes = {
+  [HJNodeType.custom]: markRaw(CommonNode),
+  [HJNodeType.nest]: markRaw(NestNode),
+  //   算数相关
+  [HJNodeType.numberInput]: markRaw(NumberInputNode),
+  [HJNodeType.numberFunction]: markRaw(NumberFunctionNode),
+  [HJNodeType.numberResult]: markRaw(NumberResultNode),
+
+  [HJNodeType.workFlow]: markRaw(WorkFlowNode),
+  [HJNodeType.wfStart]: markRaw(WFStartNode),
+  [HJNodeType.wfEnd]: markRaw(WFEndNode),
+
+  [HJNodeType.universal]: markRaw(UniversalNode),
+};
+
+export const edgeTypes = {
+  default: markRaw(CommonEdge),
+};
+
+// 用于Handle
+export enum HJPosition {
+  Left = "left",
+  Top = "top",
+  Right = "right",
+  Bottom = "bottom",
+}
+
+export interface HJHandle {
+  type: "source" | "target";
+  position: HJPosition;
+  style?: any; //如果同一侧有多个handle,可以用style调整位置
+  other?: string; //预留字段,以后看可能不同的node类型需要不同的handle样式,通过这个引入不同的Handlevue 文件
+}
+
+// 用于Node 类型中的data,方便拓展选中 拖拽等数据
+export interface HJNodeDataModel {
+  label: string;
+  isSelected?: boolean;
+  isDragging?: boolean;
+  handles?: HJHandle[];
+  information?: any; //点击node节点 弹出drawer编辑的数据
+}
+
+// 用于<VueFlow>组件的nodes属性的类型定义 从外部导入数据需要使用
+export interface HJNodeData extends Partial<Node> {
+  jsonData?: string; //经实验发现nodeTypes这种方法无法传递 Node之外的key,给props扩展也没有用, 也就是自定义的nodes里面拿不到这个key
+  data: HJNodeDataModel;
+}
+
+// 这个类型用于自定义node节点内部的数据结构,忽略一些数据能提升渲染时候的性能,
+export type HJInterNodeData = Omit<HJNodeData, "jsonData">;
+
+// 在顶层组件订一个方法,通过provide方法提供给其他组件使用
+export const HJMethodProvideName = "hjMethodProvideName";
+export const CurrentHeaderOperationProvideName =
+  "hjCurrentHeaderOperationProvideName";
+export const CurrentSelectedEdgeProvideName =
+  "hjCurrentSelectedEdgeProvideName";
+export class HJMethodName {
+  static AddNode = "hjAddNode";
+  static EditNode = "hjEditNode";
+  static SaveTemplate = "hjSaveTemplate";
+}
+export type GrandparentMethod = (name: HJMethodName, param: HJNodeData) => void;
+
+// Panel相关 HJPanelFunName添加一个  就在数组种写一个
+export type HJPanelFunName =
+  | "addNode"
+  | "reset"
+  | "layoutLR"
+  | "layoutTB"
+  | "toSnake"
+  | "back"
+  | "saveTemplate";
+export const HJPanelFunNameArray = [
+  "addNode",
+  "reset",
+  "layoutLR",
+  "layoutTB",
+  "toSnake",
+  "back",
+  "saveTemplate",
+];
+
+// 外面会传给HJFlow的props
+export interface HJFlowProps {
+  // Pannel相关
+  panelBtns?: HJPanelFunName[]; //自定义面板按钮 默认空
+  showPanel?: boolean; // 默认为false,不显示面板
+}
+
+export interface HJFlowInstance {
+  updateNodeData: (node: HJNodeData) => void;
+  initFlow: (nodes: HJNodeData[], edges: any[]) => void;
+  clearStatus: () => void;
+  getChangedStatus: () => boolean;
+}

+ 103 - 0
src/components/MyFlow/src/utils/index.ts

@@ -0,0 +1,103 @@
+const position = { x: 0, y: 0 }
+
+export const initialNodes = [
+    {
+        id: '1',
+        position,
+        type: 'custom',
+        data: {
+            label: 'Node 1',
+        },
+    },
+    {
+        id: '2',
+        type: 'custom',
+        position,
+        data: {
+            label: 'Node 2',
+        },
+    },
+    {
+        id: '2a',
+        type: 'custom',
+        position,
+        data: {
+            label: 'Node 2a',
+        },
+    },
+    {
+        id: '2b',
+        type: 'custom',
+        position,
+        data: {
+            label: 'Node 2b',
+        },
+    },
+    {
+        id: '2c',
+        position,
+        type: 'custom',
+        data: {
+            label: 'Node 2c',
+        },
+    },
+    {
+        id: '2d',
+        type: 'custom',
+        position,
+        data: {
+            label: 'Node 2d',
+        },
+    },
+    {
+        id: '3',
+        type: 'custom',
+        position,
+        data: {
+            label: 'Node 3',
+        },
+    },
+    {
+        id: '4',
+        type: 'custom',
+        position,
+        data: {
+            label: 'Node 4',
+        },
+    },
+    {
+        id: '5',
+        type: 'custom',
+        position,
+        data: {
+            label: 'Node 5',
+        },
+    },
+    {
+        id: '6',
+        position,
+        data: {
+            label: 'Node 6',
+        },
+    },
+    {
+        id: '7',
+        position,
+        data: {
+            label: 'Node 7',
+        },
+    },
+]
+
+export const initialEdges = [
+    { id: 'e1-2', source: '1', target: '2' },
+    { id: 'e1-3', source: '1', target: '3' },
+    { id: 'e2-2a', source: '2', target: '2a' },
+    { id: 'e2-2b', source: '2', target: '2b' },
+    { id: 'e2-2c', source: '2', target: '2c' },
+    { id: 'e2c-2d', source: '2c', target: '2d' },
+    { id: 'e3-7', source: '3', target: '4' },
+    { id: 'e4-5', source: '4', target: '5' },
+    { id: 'e5-6', source: '5', target: '6' },
+    { id: 'e5-7', source: '5', target: '7' },
+]

+ 90 - 0
src/components/MyFlow/src/utils/useLayout.ts

@@ -0,0 +1,90 @@
+import dagre from "@dagrejs/dagre";
+import { Position, useVueFlow, Node, Edge } from "@vue-flow/core";
+import { ref } from "vue";
+import { HJNodeData } from "@/packages/components/flow/src/types/comTypes";
+
+/**
+ * Composable to run the layout algorithm on the graph.
+ * It uses the `dagre` library to calculate the layout of the nodes and edges.
+ */
+export function useLayout() {
+  const { findNode } = useVueFlow();
+
+  const graph = ref(new dagre.graphlib.Graph());
+
+  const previousDirection = ref("LR");
+
+  function layout(
+    nodes: HJNodeData[],
+    edges: Edge[],
+    direction: "LR" | "TB" = "LR"
+  ) {
+    // we create a new graph instance, in case some nodes/edges were removed, otherwise dagre would act as if they were still there
+    const dagreGraph = new dagre.graphlib.Graph();
+
+    graph.value = dagreGraph;
+
+    dagreGraph.setDefaultEdgeLabel(() => ({}));
+
+    const isHorizontal = direction === "LR";
+    dagreGraph.setGraph({ rankdir: direction });
+
+    previousDirection.value = direction;
+
+    for (const node of nodes) {
+      // if you need width+height of nodes for your layout, you can use the dimensions property of the internal node (`GraphNode` type)
+      const graphNode: any = findNode(node.id);
+
+      dagreGraph.setNode(node?.id ?? "", {
+        width: graphNode.dimensions.width || 150,
+        height: graphNode.dimensions.height || 50,
+      });
+    }
+
+    for (const edge of edges) {
+      dagreGraph.setEdge(edge.source, edge.target);
+    }
+
+    dagre.layout(dagreGraph);
+
+    // set nodes with updated positions
+    return nodes.map((node) => {
+      const nodeWithPosition = dagreGraph.node(node?.id ?? "");
+
+      return {
+        ...node,
+        targetPosition: isHorizontal ? Position.Left : Position.Top,
+        sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
+        position: { x: nodeWithPosition.x, y: nodeWithPosition.y },
+        data: {
+          ...node.data,
+          handles: [
+            {
+              type: "target",
+              position: isHorizontal ? Position.Left : Position.Top,
+            },
+            {
+              type: "source",
+              position: isHorizontal ? Position.Right : Position.Bottom,
+            },
+          ],
+        },
+      };
+    });
+  }
+
+  return { graph, layout, previousDirection };
+}
+
+// 竖形排列完之后, 根据node节点的上下位置进行重新排序
+export function sortNodesByPosition(nodes: HJNodeData[]): HJNodeData[] {
+  return nodes.sort((a, b) => {
+    if (a.position.y < b.position.y) {
+      return -1;
+    } else if (a.position.y > b.position.y) {
+      return 1;
+    } else {
+      return 0;
+    }
+  });
+}

+ 116 - 0
src/components/MyFlow/src/utils/useSnake.ts

@@ -0,0 +1,116 @@
+import {ElMessage} from "element-plus";
+
+export const isStraightFlow = (nodes: any[], edges: any[]) => {
+    let isSerial = false;
+    let message = "非标准直线型流程图";
+
+    // 如果nodes的数量小于1 说明没有节点 直接返回false
+    if (nodes && nodes.length < 2) {
+        return [isSerial, "节点数量少于2"];
+    }
+
+    // 如果edges的数量小于1 说明没有边 直接返回false
+    if (edges && edges.length < 1) {
+        return [isSerial, "边数量少于1"];
+    }
+
+    // 如果nodes的数量不等于edges的数量+1 说明没有完全连线或者是不是直线
+    if (nodes?.length !== edges?.length + 1) {
+        return [isSerial, message];
+    }
+
+    // 每一个边的源节点id组成一个set,如果set的数量和edges的数量不相等,说明不是标准直线型流程图
+    let sourceNodeIdsSet = new Set(
+        edges.map((edge) => {
+            return edge.source;
+        })
+    );
+    let targetNodeIdsSet = new Set(
+        edges.map((edge) => {
+            return edge.target;
+        })
+    );
+    if (
+        sourceNodeIdsSet.size !== edges.length ||
+        targetNodeIdsSet.size !== edges.length
+    ) {
+        return [isSerial, message];
+    }
+
+    // 能到这里说明是直线
+    return [true, "是直线"];
+};
+
+// 按照edges的顺序重新给nodes排序
+export const sortNodesByEdges = (nodes: any[], edges: any[]) => {
+    // 先获取所有边的node的id
+    let edgeIds = [edges[0].sourceNode.id];
+    for (let i = 1; i < edges.length; i++) {
+        edgeIds.push(edges[i].targetNode.id);
+    }
+
+    // 创建一个ids到对应的索引的映射
+    const idIndexMap = edgeIds.reduce((map, id, index) => {
+        map[id] = index;
+        return map;
+    }, {});
+
+    // 根据ids数组中的顺序对对象数组进行排序
+    return nodes.sort((a, b) => {
+        return idIndexMap[a.id] - idIndexMap[b.id];
+    });
+};
+
+export const useSnakeLayoutHook = (
+    nodes: any[],
+    edges: any[],
+    spacingY: number = 150,
+    spacingX: number = 230
+) => {
+    let [isStraight, msg] = isStraightFlow(nodes, edges);
+    if (!isStraight) {
+        ElMessage.warning(msg);
+        return nodes; //这里要返回nodes, 因为外面要用到
+    }
+
+    console.log("dddd", nodes, edges);
+
+    // 后端数据做了排序了  就不用排序了
+    // nodes = sortNodesByEdges(nodes, edges);
+
+    const snakeNodes = [];
+
+    // 内边距
+    const paddingTop = 35;
+    const paddingLeft = 15;
+
+    // 这是每一个个列的数量  列数量
+    const nodesPerColumn = 6; //Math.floor(canvasHeight / spacingY);
+
+    // console.log(canvasHeight, canvasHeight / spacingY);
+
+    nodes.forEach((node, index) => {
+        const col = Math.floor(index / nodesPerColumn);
+        const row = index % nodesPerColumn;
+
+        // 根据行号计算位置,奇数列从下到上,偶数列从上到下
+        const adjustedRow = row;
+        // const adjustedRow = col % 2 === 0 ? row : nodesPerColumn - 1 - row;
+
+        node.position = {
+            x: col * spacingX + paddingLeft,
+            y: adjustedRow * spacingY + paddingTop,
+        };
+
+        // 更新奇数列的 handle 处理
+        // if (col % 2 !== 0) {
+        //   // 奇数列的节点调整 handle 位置
+        //   node.data.handleDirection = 'toTop'; //如果是第二列 也就是奇数列 调整箭头向上
+        // } else {
+        //   node.data.handleDirection = 'toDown';
+        // }
+
+        snakeNodes.push(node);
+    });
+    return snakeNodes;
+};

+ 16 - 5
src/views/base/craftManagement/route/bindProcess.vue

@@ -132,7 +132,9 @@
       <div class="detailInfo">
         <el-scrollbar>
           <div class="opBox">
-            <el-text class="mx-1" type="warning">切换之前记得先保存</el-text>
+            <el-text class="mx-1" type="warning"
+              >切换和排序之前记得先保存</el-text
+            >
             <el-button type="primary" @click="changeEditStatus">{{
               !editStatus ? "切换为工序信息编辑模式" : "切换为工序路线编辑模式"
             }}</el-button>
@@ -141,7 +143,13 @@
             <!-- <el-button type="success" @click="changeStyle">
               按钮风格切换
             </el-button> -->
-            <el-button type="success" @click="startGroup"> 进行分组 </el-button>
+            <div style="display: flex">
+              <el-button type="success" @click="startGroup">
+                进行分组
+              </el-button>
+
+              <el-button @click="toSort"> 分支排序 </el-button>
+            </div>
           </div>
 
           <!-- 工艺路线编辑模式 -->
@@ -234,6 +242,7 @@
       </div>
     </div>
     <GroupProcess ref="GroupProcessRef" @success="groupFinish"></GroupProcess>
+    <GroupSort ref="GroupSortRef" />
   </div>
 </template>
 
@@ -267,13 +276,17 @@ import { useScreenshot } from "./screenshot.ts";
 import { useLayout } from "@/hooks/useLayout";
 import { useSnakeLayoutHook } from "@/hooks/vueflowHooks";
 import GroupProcess from "./components/groupProcess.vue";
+import GroupSort from "./components/groupSort.vue";
 
 defineOptions({
   name: "bindProcess/:id/:prodtCode",
 });
 
 const styleStatus = ref(false);
-
+const GroupSortRef = ref(null);
+const toSort = () => {
+  GroupSortRef.value.drawer = true;
+};
 const changeStyle = () => {
   styleStatus.value = !styleStatus.value;
 };
@@ -354,7 +367,6 @@ const edgeClick = (event) => {
 // 工序分组功能模块
 const GroupProcessRef = ref(null);
 const startGroup = () => {
-  console.log("startGroup", flowData.nodes);
   GroupProcessRef.value &&
     GroupProcessRef.value.startGroup(
       flowData.nodes,
@@ -362,7 +374,6 @@ const startGroup = () => {
     );
 };
 const groupFinish = () => {
-  console.log("groupFinish");
   loadProcessesFlow();
 };
 

+ 2 - 1
src/views/base/craftManagement/route/components/CustomNode/index.vue

@@ -42,6 +42,7 @@ watch(
   },
   { deep: true }
 );
+console.log(props.data);
 </script>
 
 <template>
@@ -62,7 +63,7 @@ watch(
         margin: 10px;
       "
     >
-      {{ data.label }}
+      {{ data.lineTag ? data.label + "(" + data.lineTag + ")" : data.label }}
     </div>
 
     <Handle

+ 23 - 12
src/views/base/craftManagement/route/components/groupProcess.vue

@@ -36,6 +36,12 @@ function confirmClick() {
 
 const startGroup = async (nodes: any[], routeId: string) => {
   nodesData.value = markRaw(nodes);
+  if(nodesData.value){
+    try{
+      nodesData.value = nodesData.value.slice().sort((a, b) => a.lineTag.localeCompare(b.lineTag));
+      nodesData.value = nodesData.value.slice().sort((a, b) => a.lineTag.length - b.lineTag.length);
+    }catch (e){}
+  }
   routeIdA.value = routeId;
 
   drawer2.value = true;
@@ -85,6 +91,10 @@ const handleCheckAll = (val: CheckboxValueType) => {
 // 新增分组相关
 const input3 = ref("");
 const addGroup = () => {
+  if (!input3.value) {
+    ElMessage.error("请输入分组名称!");
+    return;
+  }
   if (groupData.value.hasOwnProperty(input3.value)) {
     ElMessage.error("分组名称重复");
   } else {
@@ -92,27 +102,26 @@ const addGroup = () => {
   }
 };
 
-
 // 新增全选相关
-const checked = ref<boolean>()
+const checked = ref<boolean>();
 const selectAll = (key: string) => {
   if (checked.value) {
-    groupData.value[key] = []
+    groupData.value[key] = [];
     nodesData.value.map((item) => {
-      groupData.value[key].push(item.operationId)
-    })
+      groupData.value[key].push(item.operationId);
+    });
   } else {
-    groupData.value[key] = []
+    groupData.value[key] = [];
   }
-}
+};
 
 const changeChecked = (key: string) => {
   if (groupData.value[key].length === nodesData.value.length) {
-    checked.value = true
+    checked.value = true;
   } else {
-    checked.value = false
+    checked.value = false;
   }
-}
+};
 </script>
 
 <template>
@@ -159,12 +168,14 @@ const changeChecked = (key: string) => {
           <!--            </el-checkbox>-->
           <!--          </template> -->
           <template #header>
-            <el-checkbox v-model="checked" @change="selectAll(key)">全选</el-checkbox>
+            <el-checkbox v-model="checked" @change="selectAll(key)"
+              >全选</el-checkbox
+            >
           </template>
           <el-option
             v-for="item in nodesData"
             :key="item.operationId"
-            :label="item.operationName + '(' + item.operationId + ')'"
+            :label="item.operationName + '(' + item.lineTag + ')'"
             :value="item.operationId"
           />
         </el-select>

+ 87 - 0
src/views/base/craftManagement/route/components/groupSort.vue

@@ -0,0 +1,87 @@
+<script setup>
+import { getLineSortList, setLineSortList } from "@/api/process";
+const route = useRoute();
+const editState = ref(false);
+const drawer = ref(false);
+const lineArray = ref([]);
+const getLineArray = async () => {
+  const { data } = await getLineSortList(route.params.id);
+  //设置form
+  form.value = data;
+  if(form.value){
+    try{
+      form.value = form.value.slice().sort((a, b) => a.lineTag.localeCompare(b.lineTag));
+      form.value = form.value.slice().sort((a, b) => a.lineTag.length - b.lineTag.length);
+    }catch (e){}
+  }
+};
+const reset = () => {
+  drawer.value = false;
+  editState.value = false;
+};
+const confirmClick = async () => {
+  await setLineSortList([...form.value]);
+  ElMessage.success("操作成功!");
+  reset();
+};
+// 新增分组相关
+const input3 = ref("");
+const formRef = ref(null);
+const form = ref([]);
+const rules = {
+  productManager: [
+    { required: true, message: "产品管理员不能为空", trigger: "blur" },
+  ],
+};
+defineExpose({
+  drawer,
+});
+watch(
+  () => drawer.value,
+  (val) => {
+    if (val == true) {
+      getLineArray();
+    }
+  }
+);
+</script>
+
+<template>
+  <el-drawer v-model="drawer">
+    <template #header>
+      <h4>分支排序</h4>
+      <el-button @click="editState = !editState">{{
+        editState ? "取消编辑" : "编辑"
+      }}</el-button>
+    </template>
+    <template #default>
+      <el-form ref="formRef" :model="form" label-width="auto" :rules="rules">
+        <template v-for="(item, index) in form" :key="index">
+          <el-form-item :label="index + 1 + '.分支标识:' + item.lineTag">
+          </el-form-item>
+          <el-form-item :label="'排序值:'">
+            <div class="formLine">
+              <template v-if="editState">
+                <el-input v-model="form[index].lineTageSort" />
+              </template>
+              <template v-else> {{ item.lineTageSort }} </template>
+            </div>
+          </el-form-item>
+        </template>
+      </el-form>
+    </template>
+    <template #footer>
+      <div style="flex: auto">
+        <el-button @click="drawer = false">关闭</el-button>
+        <el-button type="primary" @click="confirmClick">保存</el-button>
+      </div>
+    </template>
+  </el-drawer>
+</template>
+
+<style scoped lang="scss">
+.formLine {
+  display: flex;
+  align-items: center;
+}
+</style>

+ 274 - 0
src/views/flow/definition/com/edit.vue

@@ -0,0 +1,274 @@
+<script setup lang="ts">
+import { ref, onMounted, watch, reactive, markRaw } from "vue";
+
+import type { Node, Edge } from "@vue-flow/core";
+import {
+  HJMethodName,
+  HJNodeData,
+  HJPanelFunNameArray,
+} from "@/components/MyFlow/src/types/comTypes";
+import WorkFlowDrag from "@/components/MyFlow/src/leftDrags/workflow/dragData.vue";
+import HJFlow from "@/components/MyFlow";
+
+import type { ComponentSize, FormInstance, FormRules } from "element-plus";
+import { getUserTree } from "@/api/system/user";
+import { getFlowDataInfo, saveFlowData } from "@/api/flow";
+
+const formSize = ref<ComponentSize>("default");
+const ruleFormRef = ref<FormInstance>();
+const formData = ref<any>({
+  name: "",
+  desc: "",
+  userType: "",
+  copyUsers: "",
+  assignee: "",
+  candidateUsers: "",
+  candidateGroups: "",
+});
+const candidateGroupsOptions = ref([]); //指定角色选项
+const usersOptions = ref([]);
+const userTypeOption = ref([
+  { label: "指定人员", value: "assignee" },
+  { label: "候选人员", value: "candidateUsers" },
+  { label: "指定角色", value: "candidateGroups" },
+]);
+
+const rules = reactive<FormRules<any>>({
+  name: [
+    { required: true, message: "Please input Activity name", trigger: "blur" },
+  ],
+  desc: [
+    { required: true, message: "Please input activity form", trigger: "blur" },
+  ],
+});
+
+const submitForm = async (formEl: FormInstance | undefined) => {
+  // if (!formEl) return
+  // await formEl.validate((valid, fields) => {
+  //   if (valid) {
+  //     console.log('submit!')
+  //   } else {
+  //     console.log('error submit!', fields)
+  //   }
+  // })
+
+  if (flowRef.value && flowRef.value.updateNodeData && selectedNode.value) {
+    selectedNode.value.data.information = JSON.parse(
+      JSON.stringify(formData.value)
+    );
+    flowRef.value.updateNodeData(
+      JSON.parse(JSON.stringify(markRaw(selectedNode.value)))
+    );
+    cancel();
+  }
+};
+
+const cancel = () => {
+  ruleFormRef.value && ruleFormRef.value.resetFields();
+  selectedNode.value = null;
+  infoVisible.value = false;
+};
+
+const flowRef = ref();
+const nodes = ref<HJNodeData[]>([]);
+const edges = ref<Edge[]>([]);
+
+onMounted(() => {
+  // 接口调用完成要初始化 ,为的是保存一个最初的数据
+
+  getUserTree().then((res) => {
+    usersOptions.value = res.data;
+  });
+  getFlowDataInfo(route.params.flowId).then((res) => {
+    try {
+      let json = JSON.parse(res.data.flowContent);
+      nodes.value = json.nodes;
+      edges.value = json.edges;
+      flowRef.value && flowRef.value.initFlow(nodes.value, edges.value);
+    } catch (e) {}
+  });
+});
+
+// ======= 信息展示 =======
+const infoVisible = ref(false);
+const selectedNode = ref<HJNodeData | null>(null); //双击的节点数据
+const closeInfo = () => {
+  infoVisible.value = false;
+  selectedNode.value = null;
+};
+
+const route = useRoute();
+// vue 的相关操作
+const onNodeOperation = async (name: HJMethodName, node: HJNodeData): void => {
+  if (name === HJMethodName.EditNode) {
+    selectedNode.value = null;
+    infoVisible.value = true;
+
+    selectedNode.value = JSON.parse(JSON.stringify(node));
+
+    if (selectedNode.value && selectedNode.value.data.information) {
+      formData.value = JSON.parse(
+        JSON.stringify(selectedNode.value.data.information)
+      );
+    }
+
+    console.log("selectedNode", selectedNode.value);
+  } else if (name === HJMethodName.SaveTemplate) {
+    console.log("保存模板");
+    let p = {
+      nodes: markRaw(nodes.value),
+      edges: markRaw(edges.value),
+    };
+    await saveFlowData({
+      flowContent: JSON.stringify(p),
+      id: route.params.flowId,
+    });
+    ElMessage.success("保存成功");
+    cancel();
+  }
+};
+
+const testGetCurrentData = () => {
+  let p = {
+    nodes: markRaw(nodes.value),
+    edges: markRaw(edges.value),
+  };
+  console.log("=========当前数据======", JSON.stringify(p), p);
+};
+</script>
+
+<template>
+  <div class="out-box">
+    <WorkFlowDrag></WorkFlowDrag>
+    <HJFlow
+      class="vue-flow-box"
+      ref="flowRef"
+      v-model:nodes-data="nodes"
+      v-model:edges-data="edges"
+      @hjMethod="onNodeOperation"
+      :show-panel="true"
+      :panel-btns="['layoutTB', 'saveTemplate']"
+    ></HJFlow>
+    <div class="right-message">
+      <div class="flow-header-name" @click="testGetCurrentData">
+        编辑节点信息
+      </div>
+      <el-form
+        ref="ruleFormRef"
+        style="max-width: 600px"
+        :model="formData"
+        label-width="auto"
+        class="demo-ruleForm"
+        :size="formSize"
+        status-icon
+        v-show="infoVisible"
+      >
+        <el-form-item label="节点名称" prop="name">
+          <el-input v-model="formData.name" />
+        </el-form-item>
+        <el-form-item label="节点描述" prop="desc">
+          <el-input v-model="formData.desc" type="textarea" />
+        </el-form-item>
+        <el-form-item label="抄送人员" prop="copyUsers">
+          <el-tree-select
+            v-model="formData.copyUsers"
+            :data="usersOptions"
+            filterable
+            multiple
+          />
+        </el-form-item>
+        <el-form-item label="人员类型" prop="userType">
+          <el-select v-model="formData.userType" placeholder="请选择人员类型">
+            <el-option
+              v-for="item in userTypeOption"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item
+          label="指定人员"
+          prop="assignee"
+          v-if="formData.userType === 'assignee'"
+        >
+          <el-tree-select
+            v-model="formData.assignee"
+            :data="usersOptions"
+            filterable
+            multiple
+          />
+        </el-form-item>
+        <el-form-item
+          label="候选人员"
+          prop="candidateUsers"
+          v-if="formData.userType === 'candidateUsers'"
+        >
+          <el-tree-select
+            v-model="formData.candidateUsers"
+            :data="usersOptions"
+            filterable
+            multiple
+          />
+        </el-form-item>
+        <el-form-item
+          label="指定角色"
+          prop="candidateGroups"
+          v-if="formData.userType === 'candidateGroups'"
+        >
+          <el-tree-select
+            v-model="formData.candidateGroups"
+            :data="candidateGroupsOptions"
+            filterable
+            multiple
+          />
+        </el-form-item>
+
+        <el-form-item>
+          <el-button type="primary" @click="submitForm(ruleFormRef)">
+            保存
+          </el-button>
+          <el-button @click="cancel">取消</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="less">
+.out-box {
+  width: 100%;
+  height: calc(100vh - 120px);
+  border: 1px solid #ccc;
+  display: flex;
+}
+
+.vue-flow-box {
+  flex: 1;
+  flex-shrink: 0;
+  height: 100%;
+  border: 3px solid red;
+  //width: 300px;
+  //height: 500px;
+}
+
+.right-message {
+  width: 400px;
+  height: 100%;
+  border: 1px solid #ccc;
+  background-color: white;
+  padding: 10px;
+  display: flex;
+  flex-direction: column;
+  justify-content: start;
+  align-items: start;
+
+  .flow-header-name {
+    font-size: 18px;
+    font-weight: bold;
+    margin-top: 10px;
+    margin-bottom: 10px;
+  }
+}
+</style>

+ 135 - 0
src/views/flow/definition/index.vue

@@ -0,0 +1,135 @@
+<template>
+  <div class="mainContentBox">
+    <avue-crud
+      ref="crudRef"
+      v-model:search="search"
+      v-model="form"
+      :data="data"
+      :option="option"
+      v-model:page="page"
+      @row-save="createRow"
+      @row-update="updateRow"
+      @row-del="deleteRow"
+      @search-change="searchChange"
+      @search-reset="resetChange"
+      @size-change="dataList"
+      @current-change="dataList"
+      @selection-change="selectionChange1"
+    >
+      <template #menu="{ size, row }">
+        <el-button icon="el-icon-setting" text type="primary" :size="size"
+          >编辑</el-button
+        >
+        <el-button
+          icon="el-icon-setting"
+          text
+          type="primary"
+          :size="size"
+          @click="defineFlow(row)"
+          >定义</el-button
+        >
+      </template>
+    </avue-crud>
+  </div>
+</template>
+<script setup>
+import { ref, getCurrentInstance } from "vue";
+import { useCrud } from "@/hooks/userCrud";
+import { useCommonStoreHook } from "@/store/index";
+import dictDataUtil from "@/common/configs/dictDataUtil";
+const { isShowTable, tableType } = toRefs(useCommonStoreHook());
+const test = () => {
+  isShowTable.value = true;
+  tableType.value = tableType.value == 1 ? 2 : 1;
+};
+const clickRows = ref([]);
+// 传入一个url,后面不带/
+const { form, data, option, search, page, toDeleteIds, Methords, Utils } =
+  useCrud({
+    src: "/api/v1/definition",
+  });
+const { dataList, createRow, updateRow, deleteRow, searchChange, resetChange } =
+  Methords; //增删改查
+const { selectionChange, multipleDelete } = Methords; //选中和批量删除事件
+const { checkBtnPerm, downloadTemplate, exportData } = Utils; //按钮权限等工具
+
+const crudRef = ref(null); //crudRef.value 获取avue-crud对象
+
+// 设置表格列或者其他自定义的option
+option.value = Object.assign(option.value, {
+  searchEnter: true,
+  delBtn: false,
+  selection: false,
+  addBtn: true,
+  editBtn: false,
+  viewBtn: false,
+  column: [
+    /*{
+      label: "流程编码",
+      prop: "flowCode",
+    },*/
+    {
+      label: "流程名称",
+      prop: "flowName",
+      search: true,
+    },
+    {
+      label: "流程类型",
+      prop: "flowType",
+      type: "select",
+      search: true,
+      dicUrl: dictDataUtil.request_url + dictDataUtil.TYPE_CODE.flow_type,
+      props: {
+        label: "dictLabel",
+        value: "dictValue",
+      },
+    },
+    {
+      label: "流程版本",
+      prop: "flowVersion",
+      // html: true,
+      // formatter: (val) => {
+      //   return (
+      //     '<b class="el-tag el-tag--success el-tag--light">V' +
+      //     val.flowVersion +
+      //     "</b>"
+      //   );
+      // },
+    },
+    {
+      label: "状态",
+      prop: "enable",
+      type: "select",
+      search: true,
+      dicData: [
+        { label: "启用", value: "0" },
+        { label: "禁用", value: "1" },
+      ],
+    },
+    {
+      label: "创建时间",
+      prop: "created",
+      display: false,
+    },
+    {
+      label: "创建人",
+      prop: "creator",
+      display: false,
+    },
+  ],
+});
+
+onMounted(() => {
+  // console.log("crudRef", crudRef)
+  dataList();
+});
+
+const router = useRouter();
+// 定义流程
+const defineFlow = (row) => {
+  console.log("row", row.id);
+  router.push({
+    path: `/flow/definition-edit/${row.id}`,
+  });
+};
+</script>

+ 34 - 35
src/views/pro/traceability/components/mediaCom.vue

@@ -1,6 +1,8 @@
 <!-- 图片采集 -->
 <template>
-  <el-button v-if="seqNo" :loading="loading" type="primary" @click="syncMedia">同步多媒体采集</el-button>
+  <el-button v-if="seqNo" :loading="loading" type="primary" @click="syncMedia"
+    >同步多媒体采集</el-button
+  >
   <div class="mainContentBox">
     <img src="" />
     <avue-crud
@@ -25,25 +27,25 @@
         <single-upload v-model="form.filePath" />
       </template>
     </avue-crud>
-    <el-dialog
-      v-model="dialog.visible"
-      :title="dialog.title"
-      width="60%"
-      @close="dialog.visible = false"
-      ><el-image :src="url" fit="none" />
-    </el-dialog>
     <CommonTable
       ref="ctableRef"
       tableTitle="人员选择"
       tableType="USERS"
       @selected-sure="onSelectedFinish"
     />
+    <el-image-viewer
+      v-if="showPreview"
+      :url-list="preSrcList"
+      show-progress
+      :initial-index="0"
+      @close="showPreview = false"
+    />
   </div>
 </template>
 <script setup>
 import { ref, getCurrentInstance } from "vue";
 import { useCrud } from "@/hooks/userCrud";
-import {syncMedias} from "@/api/process";
+import { syncMedias } from "@/api/process";
 import dictDataUtil from "@/common/configs/dictDataUtil";
 import ButtonPermKeys from "@/common/configs/buttonPermission";
 import {
@@ -55,13 +57,8 @@ import SingleUpload from "@/components/Upload/SingleUpload.vue";
 const ctableRef = ref(null);
 // 数据字典相关
 const { dicts } = useDictionaryStore();
-const dialog = reactive({
-  title: "图片预览",
-  visible: false,
-});
-const loading=ref(false);
-const url = ref(null);
-// 传入一个url,后面不带/
+
+const loading = ref(false);
 const {
   form,
   data,
@@ -80,17 +77,21 @@ const { dataList, createRow, updateRow, deleteRow, searchChange, resetChange } =
 const { selectionChange, multipleDelete } = Methords; //选中和批量删除事件
 const { checkBtnPerm, downloadTemplate, exportData } = Utils; //按钮权限等工具
 
+// 图片查看器相关
+const showPreview = ref(false);
+const preSrcList = ref([]);
+
 const crudRef = ref(null); //crudRef.value 获取avue-crud对象
 const openDialog = (row) => {
-  dialog.visible = true;
-  url.value = import.meta.env.VITE_APP_UPLOAD_URL + row.filePath;
+  preSrcList.value = [import.meta.env.VITE_APP_UPLOAD_URL + row.filePath];
+  showPreview.value = true;
 };
-const seqNo=ref(null);
+const seqNo = ref(null);
 const syncData = ref({});
 const refreshTra = (row) => {
-  seqNo.value=row.seqNo;
-  syncData.value.seqNo=row.seqNo;
-  syncData.value.workOrderCode=row.workOrderCode;
+  seqNo.value = row.seqNo;
+  syncData.value.seqNo = row.seqNo;
+  syncData.value.workOrderCode = row.workOrderCode;
   commonConfig.value.params = {
     seqNo: row.seqNo,
     workOrderCode: row.workOrderCode,
@@ -105,20 +106,18 @@ onMounted(() => {
   }
 });
 
-const syncMedia=()=>{
-  loading.value=true;
-   if(syncData.value){
-     syncMedias(syncData.value).then(
-       (data)=>{
-         if(data.code==="200"){
-            ElMessage.success(data.msg);
-         }
-       }
-     ).finally(
-       loading.value=false
-    )
+const syncMedia = () => {
+  loading.value = true;
+  if (syncData.value) {
+    syncMedias(syncData.value)
+      .then((data) => {
+        if (data.code === "200") {
+          ElMessage.success(data.msg);
+        }
+      })
+      .finally((loading.value = false));
   }
-}
+};
 const onSelectedFinish = (selectValue) => {
   form.value.creator = selectValue.userName;
 };