Browse Source

引入HJflow,不用npm了。

jiaxiaoqiang 3 months ago
parent
commit
67cab407cb
42 changed files with 2281 additions and 85 deletions
  1. 2 1
      package.json
  2. 147 70
      pnpm-lock.yaml
  3. 14 0
      src/components/hjflow/index.ts
  4. 18 0
      src/components/hjflow/src/Handle/CommonHandle.vue
  5. 13 0
      src/components/hjflow/src/background/DropzoneBackground.vue
  6. 59 0
      src/components/hjflow/src/edges/commonEdge.vue
  7. 97 0
      src/components/hjflow/src/flow.vue
  8. 191 0
      src/components/hjflow/src/hjflow/index.vue
  9. 145 0
      src/components/hjflow/src/hooks/useDnD.ts
  10. 0 0
      src/components/hjflow/src/hooks/useFlow.ts
  11. 11 0
      src/components/hjflow/src/information/index.vue
  12. 167 0
      src/components/hjflow/src/leftDrags/test/configs.ts
  13. 41 0
      src/components/hjflow/src/leftDrags/test/dragData.vue
  14. 47 0
      src/components/hjflow/src/leftDrags/workflow/configs.ts
  15. 41 0
      src/components/hjflow/src/leftDrags/workflow/dragData.vue
  16. 11 0
      src/components/hjflow/src/leftDrags/workflow/editInfo.vue
  17. 24 0
      src/components/hjflow/src/nodes/CommonNode.vue
  18. 18 0
      src/components/hjflow/src/nodes/NestNode.vue
  19. 7 0
      src/components/hjflow/src/nodes/NumberFunNode.vue
  20. 7 0
      src/components/hjflow/src/nodes/NumberInputNode.vue
  21. 7 0
      src/components/hjflow/src/nodes/NumberResultNode.vue
  22. 94 0
      src/components/hjflow/src/nodes/com/basic.vue
  23. 49 0
      src/components/hjflow/src/nodes/com/operationHeader.vue
  24. 40 0
      src/components/hjflow/src/nodes/universal/UniversalNode.vue
  25. 40 0
      src/components/hjflow/src/nodes/workflow/WorkFlowNode.vue
  26. 20 0
      src/components/hjflow/src/panel/btns/Back.vue
  27. 44 0
      src/components/hjflow/src/panel/btns/add-node.vue
  28. 27 0
      src/components/hjflow/src/panel/btns/layout-LR.vue
  29. 29 0
      src/components/hjflow/src/panel/btns/layout-TB.vue
  30. 37 0
      src/components/hjflow/src/panel/btns/reset.vue
  31. 32 0
      src/components/hjflow/src/panel/btns/snake.vue
  32. 79 0
      src/components/hjflow/src/panel/index.vue
  33. 45 0
      src/components/hjflow/src/styles/index.scss
  34. 173 0
      src/components/hjflow/src/testShenpiliu.vue
  35. 105 0
      src/components/hjflow/src/types/comTypes.ts
  36. 103 0
      src/components/hjflow/src/utils/index.ts
  37. 72 0
      src/components/hjflow/src/utils/useLayout.ts
  38. 116 0
      src/components/hjflow/src/utils/useSnake.ts
  39. 0 5
      src/main.ts
  40. 60 0
      src/views/modules/project-config/com/function-col.vue
  41. 4 8
      src/views/modules/project-config/configs/properites.ts
  42. 45 1
      src/views/modules/project-config/project-config.vue

+ 2 - 1
package.json

@@ -40,6 +40,7 @@
     ]
     ]
   },
   },
   "dependencies": {
   "dependencies": {
+    "@dagrejs/dagre": "^1.1.4",
     "@element-plus/icons-vue": "^2.3.1",
     "@element-plus/icons-vue": "^2.3.1",
     "@smallwei/avue": "^3.3.3",
     "@smallwei/avue": "^3.3.3",
     "@types/smallwei__avue": "^3.0.5",
     "@types/smallwei__avue": "^3.0.5",
@@ -51,7 +52,7 @@
     "axios": "^1.6.7",
     "axios": "^1.6.7",
     "echarts": "^5.5.0",
     "echarts": "^5.5.0",
     "element-plus": "2.8.0",
     "element-plus": "2.8.0",
-    "jxq-ui": "^0.0.4",
+    "less": "^4.2.2",
     "lodash-es": "^4.17.21",
     "lodash-es": "^4.17.21",
     "net": "^1.0.2",
     "net": "^1.0.2",
     "nprogress": "^0.2.0",
     "nprogress": "^0.2.0",

+ 147 - 70
pnpm-lock.yaml

@@ -8,6 +8,9 @@ importers:
 
 
   .:
   .:
     dependencies:
     dependencies:
+      '@dagrejs/dagre':
+        specifier: ^1.1.4
+        version: 1.1.4
       '@element-plus/icons-vue':
       '@element-plus/icons-vue':
         specifier: ^2.3.1
         specifier: ^2.3.1
         version: 2.3.1(vue@3.5.13(typescript@5.7.3))
         version: 2.3.1(vue@3.5.13(typescript@5.7.3))
@@ -41,9 +44,9 @@ importers:
       element-plus:
       element-plus:
         specifier: 2.8.0
         specifier: 2.8.0
         version: 2.8.0(vue@3.5.13(typescript@5.7.3))
         version: 2.8.0(vue@3.5.13(typescript@5.7.3))
-      jxq-ui:
-        specifier: ^0.0.4
-        version: 0.0.4(@types/sortablejs@1.15.8)(typescript@5.7.3)
+      less:
+        specifier: ^4.2.2
+        version: 4.2.2
       lodash-es:
       lodash-es:
         specifier: ^4.17.21
         specifier: ^4.17.21
         version: 4.17.21
         version: 4.17.21
@@ -134,10 +137,10 @@ importers:
         version: 7.18.0(eslint@8.57.1)(typescript@5.7.3)
         version: 7.18.0(eslint@8.57.1)(typescript@5.7.3)
       '@vitejs/plugin-vue':
       '@vitejs/plugin-vue':
         specifier: ^5.0.4
         specifier: ^5.0.4
-        version: 5.2.1(vite@5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0))(vue@3.5.13(typescript@5.7.3))
+        version: 5.2.1(vite@5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0))(vue@3.5.13(typescript@5.7.3))
       '@vitejs/plugin-vue-jsx':
       '@vitejs/plugin-vue-jsx':
         specifier: ^3.1.0
         specifier: ^3.1.0
-        version: 3.1.0(vite@5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0))(vue@3.5.13(typescript@5.7.3))
+        version: 3.1.0(vite@5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0))(vue@3.5.13(typescript@5.7.3))
       autoprefixer:
       autoprefixer:
         specifier: ^10.4.18
         specifier: ^10.4.18
         version: 10.4.20(postcss@8.5.2)
         version: 10.4.20(postcss@8.5.2)
@@ -209,7 +212,7 @@ importers:
         version: 5.7.3
         version: 5.7.3
       unocss:
       unocss:
         specifier: ^0.58.5
         specifier: ^0.58.5
-        version: 0.58.9(postcss@8.5.2)(rollup@4.34.8)(vite@5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0))
+        version: 0.58.9(postcss@8.5.2)(rollup@4.34.8)(vite@5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0))
       unplugin-auto-import:
       unplugin-auto-import:
         specifier: ^0.17.5
         specifier: ^0.17.5
         version: 0.17.8(@vueuse/core@10.11.1(vue@3.5.13(typescript@5.7.3)))(rollup@4.34.8)
         version: 0.17.8(@vueuse/core@10.11.1(vue@3.5.13(typescript@5.7.3)))(rollup@4.34.8)
@@ -221,13 +224,13 @@ importers:
         version: 0.26.0(@babel/parser@7.26.9)(rollup@4.34.8)(vue@3.5.13(typescript@5.7.3))
         version: 0.26.0(@babel/parser@7.26.9)(rollup@4.34.8)(vue@3.5.13(typescript@5.7.3))
       vite:
       vite:
         specifier: ^5.1.5
         specifier: ^5.1.5
-        version: 5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0)
+        version: 5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0)
       vite-plugin-mock-dev-server:
       vite-plugin-mock-dev-server:
         specifier: ^1.4.7
         specifier: ^1.4.7
-        version: 1.8.4(bufferutil@4.0.9)(esbuild@0.21.5)(rollup@4.34.8)(utf-8-validate@5.0.10)(vite@5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0))
+        version: 1.8.4(bufferutil@4.0.9)(esbuild@0.21.5)(rollup@4.34.8)(utf-8-validate@5.0.10)(vite@5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0))
       vite-plugin-svg-icons:
       vite-plugin-svg-icons:
         specifier: ^2.0.1
         specifier: ^2.0.1
-        version: 2.0.1(vite@5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0))
+        version: 2.0.1(vite@5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0))
       vue-tsc:
       vue-tsc:
         specifier: ^2.0.4
         specifier: ^2.0.4
         version: 2.2.2(typescript@5.7.3)
         version: 2.2.2(typescript@5.7.3)
@@ -495,6 +498,13 @@ packages:
     resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
     resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
     engines: {node: '>=10'}
     engines: {node: '>=10'}
 
 
+  '@dagrejs/dagre@1.1.4':
+    resolution: {integrity: sha512-QUTc54Cg/wvmlEUxB+uvoPVKFazM1H18kVHBQNmK2NbrDR5ihOCR6CXLnDSZzMcSQKJtabPUWridBOlJM3WkDg==}
+
+  '@dagrejs/graphlib@2.2.4':
+    resolution: {integrity: sha512-mepCf/e9+SKYy1d02/UkvSy6+6MoyXhVxP8lLDfA7BPE1X1d4dR0sZznmbM8/XVJ1GPM+Svnx7Xj6ZweByWUkw==}
+    engines: {node: '>17.0.0'}
+
   '@dual-bundle/import-meta-resolve@4.1.0':
   '@dual-bundle/import-meta-resolve@4.1.0':
     resolution: {integrity: sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==}
     resolution: {integrity: sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==}
 
 
@@ -1789,6 +1799,9 @@ packages:
     resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==}
     resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==}
     engines: {node: '>= 0.8'}
     engines: {node: '>= 0.8'}
 
 
+  copy-anything@2.0.6:
+    resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==}
+
   copy-descriptor@0.1.1:
   copy-descriptor@0.1.1:
     resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==}
     resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==}
     engines: {node: '>=0.10.0'}
     engines: {node: '>=0.10.0'}
@@ -2117,11 +2130,6 @@ packages:
     peerDependencies:
     peerDependencies:
       vue: ^3.2.0
       vue: ^3.2.0
 
 
-  element-plus@2.9.5:
-    resolution: {integrity: sha512-r+X79oogLbYq8p9L5f9fHSHhUFNM0AL72aikqiZVxSc2/08mK6m/PotiB9e/D90QmWTIHIaFnFmW65AcXmneig==}
-    peerDependencies:
-      vue: ^3.2.0
-
   emoji-regex@10.4.0:
   emoji-regex@10.4.0:
     resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==}
     resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==}
 
 
@@ -2150,6 +2158,10 @@ packages:
     resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
     resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
     engines: {node: '>=18'}
     engines: {node: '>=18'}
 
 
+  errno@0.1.8:
+    resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
+    hasBin: true
+
   error-ex@1.3.2:
   error-ex@1.3.2:
     resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
     resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
 
 
@@ -2741,6 +2753,10 @@ packages:
     resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
     resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
     engines: {node: '>=0.10.0'}
     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:
   ieee754@1.2.1:
     resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
     resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
 
 
@@ -3002,6 +3018,9 @@ packages:
     resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
     resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
     engines: {node: '>= 0.4'}
     engines: {node: '>= 0.4'}
 
 
+  is-what@3.14.1:
+    resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==}
+
   is-windows@1.0.2:
   is-windows@1.0.2:
     resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}
     resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}
     engines: {node: '>=0.10.0'}
     engines: {node: '>=0.10.0'}
@@ -3080,9 +3099,6 @@ packages:
     resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
     resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
     engines: {'0': node >= 0.2.0}
     engines: {'0': node >= 0.2.0}
 
 
-  jxq-ui@0.0.4:
-    resolution: {integrity: sha512-WB83T3IyScRCnNOqQTFsAZPdBTyrM/5+znaPQY4kUJ2fEf0Y4Nla56YOz7b/S937QXhu4hLOO2l+yawzbqC4dQ==}
-
   keygrip@1.1.0:
   keygrip@1.1.0:
     resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==}
     resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==}
     engines: {node: '>= 0.6'}
     engines: {node: '>= 0.6'}
@@ -3115,6 +3131,11 @@ packages:
   kolorist@1.8.0:
   kolorist@1.8.0:
     resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
     resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
 
 
+  less@4.2.2:
+    resolution: {integrity: sha512-tkuLHQlvWUTeQ3doAqnHbNn8T6WX1KA8yvbKG9x4VtKtIjHsVKQZCH11zRgAfbDAXC2UNIg/K9BYAAcEzUIrNg==}
+    engines: {node: '>=6'}
+    hasBin: true
+
   levn@0.4.1:
   levn@0.4.1:
     resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
     resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
     engines: {node: '>= 0.8.0'}
     engines: {node: '>= 0.8.0'}
@@ -3249,6 +3270,10 @@ packages:
   magic-string@0.30.17:
   magic-string@0.30.17:
     resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
     resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
 
 
+  make-dir@2.1.0:
+    resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
+    engines: {node: '>=6'}
+
   map-cache@0.2.2:
   map-cache@0.2.2:
     resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==}
     resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==}
     engines: {node: '>=0.10.0'}
     engines: {node: '>=0.10.0'}
@@ -3336,6 +3361,11 @@ packages:
     resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
     resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
     engines: {node: '>= 0.6'}
     engines: {node: '>= 0.6'}
 
 
+  mime@1.6.0:
+    resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
+    engines: {node: '>=4'}
+    hasBin: true
+
   mimic-fn@2.1.0:
   mimic-fn@2.1.0:
     resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
     resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
     engines: {node: '>=6'}
     engines: {node: '>=6'}
@@ -3407,6 +3437,11 @@ packages:
   natural-compare@1.4.0:
   natural-compare@1.4.0:
     resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
     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:
   net@1.0.2:
     resolution: {integrity: sha512-kbhcj2SVVR4caaVnGLJKmlk2+f+oLkjqdKeQlmUtz6nGzOpbcobwVIeSURNgraV/v3tlmGIX82OcPCl0K6RbHQ==}
     resolution: {integrity: sha512-kbhcj2SVVR4caaVnGLJKmlk2+f+oLkjqdKeQlmUtz6nGzOpbcobwVIeSURNgraV/v3tlmGIX82OcPCl0K6RbHQ==}
 
 
@@ -3560,6 +3595,10 @@ packages:
     resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
     resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
     engines: {node: '>=8'}
     engines: {node: '>=8'}
 
 
+  parse-node-version@1.0.1:
+    resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}
+    engines: {node: '>= 0.10'}
+
   parse-passwd@1.0.0:
   parse-passwd@1.0.0:
     resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==}
     resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==}
     engines: {node: '>=0.10.0'}
     engines: {node: '>=0.10.0'}
@@ -3637,6 +3676,10 @@ packages:
     engines: {node: '>=0.10'}
     engines: {node: '>=0.10'}
     hasBin: true
     hasBin: true
 
 
+  pify@4.0.1:
+    resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
+    engines: {node: '>=6'}
+
   pinia@2.3.1:
   pinia@2.3.1:
     resolution: {integrity: sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==}
     resolution: {integrity: sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==}
     peerDependencies:
     peerDependencies:
@@ -3754,6 +3797,9 @@ packages:
   proxy-from-env@1.1.0:
   proxy-from-env@1.1.0:
     resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
     resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
 
 
+  prr@1.0.1:
+    resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
+
   punycode@2.3.1:
   punycode@2.3.1:
     resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
     resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
     engines: {node: '>=6'}
     engines: {node: '>=6'}
@@ -3924,6 +3970,9 @@ packages:
     engines: {node: '>=14.0.0'}
     engines: {node: '>=14.0.0'}
     hasBin: true
     hasBin: true
 
 
+  sax@1.4.1:
+    resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
+
   scroll-into-view-if-needed@2.2.31:
   scroll-into-view-if-needed@2.2.31:
     resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==}
     resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==}
 
 
@@ -5191,6 +5240,12 @@ snapshots:
 
 
   '@ctrl/tinycolor@3.6.1': {}
   '@ctrl/tinycolor@3.6.1': {}
 
 
+  '@dagrejs/dagre@1.1.4':
+    dependencies:
+      '@dagrejs/graphlib': 2.2.4
+
+  '@dagrejs/graphlib@2.2.4': {}
+
   '@dual-bundle/import-meta-resolve@4.1.0': {}
   '@dual-bundle/import-meta-resolve@4.1.0': {}
 
 
   '@element-plus/icons-vue@2.3.1(vue@3.5.13(typescript@5.7.3))':
   '@element-plus/icons-vue@2.3.1(vue@3.5.13(typescript@5.7.3))':
@@ -5652,13 +5707,13 @@ snapshots:
 
 
   '@ungap/structured-clone@1.3.0': {}
   '@ungap/structured-clone@1.3.0': {}
 
 
-  '@unocss/astro@0.58.9(rollup@4.34.8)(vite@5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0))':
+  '@unocss/astro@0.58.9(rollup@4.34.8)(vite@5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0))':
     dependencies:
     dependencies:
       '@unocss/core': 0.58.9
       '@unocss/core': 0.58.9
       '@unocss/reset': 0.58.9
       '@unocss/reset': 0.58.9
-      '@unocss/vite': 0.58.9(rollup@4.34.8)(vite@5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0))
+      '@unocss/vite': 0.58.9(rollup@4.34.8)(vite@5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0))
     optionalDependencies:
     optionalDependencies:
-      vite: 5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0)
+      vite: 5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0)
     transitivePeerDependencies:
     transitivePeerDependencies:
       - rollup
       - rollup
 
 
@@ -5789,7 +5844,7 @@ snapshots:
     dependencies:
     dependencies:
       '@unocss/core': 0.58.9
       '@unocss/core': 0.58.9
 
 
-  '@unocss/vite@0.58.9(rollup@4.34.8)(vite@5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0))':
+  '@unocss/vite@0.58.9(rollup@4.34.8)(vite@5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0))':
     dependencies:
     dependencies:
       '@ampproject/remapping': 2.3.0
       '@ampproject/remapping': 2.3.0
       '@rollup/pluginutils': 5.1.4(rollup@4.34.8)
       '@rollup/pluginutils': 5.1.4(rollup@4.34.8)
@@ -5801,7 +5856,7 @@ snapshots:
       chokidar: 3.6.0
       chokidar: 3.6.0
       fast-glob: 3.3.3
       fast-glob: 3.3.3
       magic-string: 0.30.17
       magic-string: 0.30.17
-      vite: 5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0)
+      vite: 5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0)
     transitivePeerDependencies:
     transitivePeerDependencies:
       - rollup
       - rollup
 
 
@@ -5834,19 +5889,19 @@ snapshots:
       '@uppy/utils': 4.1.3
       '@uppy/utils': 4.1.3
       nanoid: 3.3.8
       nanoid: 3.3.8
 
 
-  '@vitejs/plugin-vue-jsx@3.1.0(vite@5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0))(vue@3.5.13(typescript@5.7.3))':
+  '@vitejs/plugin-vue-jsx@3.1.0(vite@5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0))(vue@3.5.13(typescript@5.7.3))':
     dependencies:
     dependencies:
       '@babel/core': 7.26.9
       '@babel/core': 7.26.9
       '@babel/plugin-transform-typescript': 7.26.8(@babel/core@7.26.9)
       '@babel/plugin-transform-typescript': 7.26.8(@babel/core@7.26.9)
       '@vue/babel-plugin-jsx': 1.2.5(@babel/core@7.26.9)
       '@vue/babel-plugin-jsx': 1.2.5(@babel/core@7.26.9)
-      vite: 5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0)
+      vite: 5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0)
       vue: 3.5.13(typescript@5.7.3)
       vue: 3.5.13(typescript@5.7.3)
     transitivePeerDependencies:
     transitivePeerDependencies:
       - supports-color
       - supports-color
 
 
-  '@vitejs/plugin-vue@5.2.1(vite@5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0))(vue@3.5.13(typescript@5.7.3))':
+  '@vitejs/plugin-vue@5.2.1(vite@5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0))(vue@3.5.13(typescript@5.7.3))':
     dependencies:
     dependencies:
-      vite: 5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0)
+      vite: 5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0)
       vue: 3.5.13(typescript@5.7.3)
       vue: 3.5.13(typescript@5.7.3)
 
 
   '@volar/language-core@2.4.11':
   '@volar/language-core@2.4.11':
@@ -6597,6 +6652,10 @@ snapshots:
       depd: 2.0.0
       depd: 2.0.0
       keygrip: 1.1.0
       keygrip: 1.1.0
 
 
+  copy-anything@2.0.6:
+    dependencies:
+      is-what: 3.14.1
+
   copy-descriptor@0.1.1: {}
   copy-descriptor@0.1.1: {}
 
 
   cors@2.8.5:
   cors@2.8.5:
@@ -6942,27 +7001,6 @@ snapshots:
     transitivePeerDependencies:
     transitivePeerDependencies:
       - '@vue/composition-api'
       - '@vue/composition-api'
 
 
-  element-plus@2.9.5(vue@3.5.13(typescript@5.7.3)):
-    dependencies:
-      '@ctrl/tinycolor': 3.6.1
-      '@element-plus/icons-vue': 2.3.1(vue@3.5.13(typescript@5.7.3))
-      '@floating-ui/dom': 1.6.13
-      '@popperjs/core': '@sxzz/popperjs-es@2.11.7'
-      '@types/lodash': 4.17.15
-      '@types/lodash-es': 4.17.12
-      '@vueuse/core': 9.13.0(vue@3.5.13(typescript@5.7.3))
-      async-validator: 4.2.5
-      dayjs: 1.11.13
-      escape-html: 1.0.3
-      lodash: 4.17.21
-      lodash-es: 4.17.21
-      lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21)
-      memoize-one: 6.0.0
-      normalize-wheel-es: 1.2.0
-      vue: 3.5.13(typescript@5.7.3)
-    transitivePeerDependencies:
-      - '@vue/composition-api'
-
   emoji-regex@10.4.0: {}
   emoji-regex@10.4.0: {}
 
 
   emoji-regex@8.0.0: {}
   emoji-regex@8.0.0: {}
@@ -6979,6 +7017,11 @@ snapshots:
 
 
   environment@1.1.0: {}
   environment@1.1.0: {}
 
 
+  errno@0.1.8:
+    dependencies:
+      prr: 1.0.1
+    optional: true
+
   error-ex@1.3.2:
   error-ex@1.3.2:
     dependencies:
     dependencies:
       is-arrayish: 0.2.1
       is-arrayish: 0.2.1
@@ -7743,6 +7786,11 @@ snapshots:
     dependencies:
     dependencies:
       safer-buffer: 2.1.2
       safer-buffer: 2.1.2
 
 
+  iconv-lite@0.6.3:
+    dependencies:
+      safer-buffer: 2.1.2
+    optional: true
+
   ieee754@1.2.1: {}
   ieee754@1.2.1: {}
 
 
   ignore@5.3.2: {}
   ignore@5.3.2: {}
@@ -7987,6 +8035,8 @@ snapshots:
       call-bound: 1.0.3
       call-bound: 1.0.3
       get-intrinsic: 1.2.7
       get-intrinsic: 1.2.7
 
 
+  is-what@3.14.1: {}
+
   is-windows@1.0.2: {}
   is-windows@1.0.2: {}
 
 
   isarray@1.0.0: {}
   isarray@1.0.0: {}
@@ -8042,20 +8092,6 @@ snapshots:
 
 
   jsonparse@1.3.1: {}
   jsonparse@1.3.1: {}
 
 
-  jxq-ui@0.0.4(@types/sortablejs@1.15.8)(typescript@5.7.3):
-    dependencies:
-      '@element-plus/icons-vue': 2.3.1(vue@3.5.13(typescript@5.7.3))
-      '@vue-flow/background': 1.3.2(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))
-      '@vue-flow/core': 1.42.1(vue@3.5.13(typescript@5.7.3))
-      element-plus: 2.9.5(vue@3.5.13(typescript@5.7.3))
-      uuid: 11.1.0
-      vue: 3.5.13(typescript@5.7.3)
-      vue-draggable-plus: 0.6.0(@types/sortablejs@1.15.8)
-    transitivePeerDependencies:
-      - '@types/sortablejs'
-      - '@vue/composition-api'
-      - typescript
-
   keygrip@1.1.0:
   keygrip@1.1.0:
     dependencies:
     dependencies:
       tsscmp: 1.0.6
       tsscmp: 1.0.6
@@ -8084,6 +8120,20 @@ snapshots:
 
 
   kolorist@1.8.0: {}
   kolorist@1.8.0: {}
 
 
+  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:
   levn@0.4.1:
     dependencies:
     dependencies:
       prelude-ls: 1.2.1
       prelude-ls: 1.2.1
@@ -8216,6 +8266,12 @@ snapshots:
     dependencies:
     dependencies:
       '@jridgewell/sourcemap-codec': 1.5.0
       '@jridgewell/sourcemap-codec': 1.5.0
 
 
+  make-dir@2.1.0:
+    dependencies:
+      pify: 4.0.1
+      semver: 5.7.2
+    optional: true
+
   map-cache@0.2.2: {}
   map-cache@0.2.2: {}
 
 
   map-obj@1.0.1: {}
   map-obj@1.0.1: {}
@@ -8303,6 +8359,9 @@ snapshots:
     dependencies:
     dependencies:
       mime-db: 1.52.0
       mime-db: 1.52.0
 
 
+  mime@1.6.0:
+    optional: true
+
   mimic-fn@2.1.0: {}
   mimic-fn@2.1.0: {}
 
 
   mimic-fn@4.0.0: {}
   mimic-fn@4.0.0: {}
@@ -8373,6 +8432,12 @@ snapshots:
 
 
   natural-compare@1.4.0: {}
   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: {}
   net@1.0.2: {}
 
 
   next-tick@1.1.0: {}
   next-tick@1.1.0: {}
@@ -8549,6 +8614,8 @@ snapshots:
       json-parse-even-better-errors: 2.3.1
       json-parse-even-better-errors: 2.3.1
       lines-and-columns: 1.2.4
       lines-and-columns: 1.2.4
 
 
+  parse-node-version@1.0.1: {}
+
   parse-passwd@1.0.0: {}
   parse-passwd@1.0.0: {}
 
 
   pascalcase@0.1.1: {}
   pascalcase@0.1.1: {}
@@ -8594,6 +8661,9 @@ snapshots:
 
 
   pidtree@0.6.0: {}
   pidtree@0.6.0: {}
 
 
+  pify@4.0.1:
+    optional: true
+
   pinia@2.3.1(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)):
   pinia@2.3.1(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)):
     dependencies:
     dependencies:
       '@vue/devtools-api': 6.6.4
       '@vue/devtools-api': 6.6.4
@@ -8707,6 +8777,9 @@ snapshots:
 
 
   proxy-from-env@1.1.0: {}
   proxy-from-env@1.1.0: {}
 
 
+  prr@1.0.1:
+    optional: true
+
   punycode@2.3.1: {}
   punycode@2.3.1: {}
 
 
   qs@6.14.0:
   qs@6.14.0:
@@ -8905,6 +8978,9 @@ snapshots:
       immutable: 4.3.7
       immutable: 4.3.7
       source-map-js: 1.2.1
       source-map-js: 1.2.1
 
 
+  sax@1.4.1:
+    optional: true
+
   scroll-into-view-if-needed@2.2.31:
   scroll-into-view-if-needed@2.2.31:
     dependencies:
     dependencies:
       compute-scroll-into-view: 1.0.20
       compute-scroll-into-view: 1.0.20
@@ -9560,9 +9636,9 @@ snapshots:
 
 
   universalify@2.0.1: {}
   universalify@2.0.1: {}
 
 
-  unocss@0.58.9(postcss@8.5.2)(rollup@4.34.8)(vite@5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0)):
+  unocss@0.58.9(postcss@8.5.2)(rollup@4.34.8)(vite@5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0)):
     dependencies:
     dependencies:
-      '@unocss/astro': 0.58.9(rollup@4.34.8)(vite@5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0))
+      '@unocss/astro': 0.58.9(rollup@4.34.8)(vite@5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0))
       '@unocss/cli': 0.58.9(rollup@4.34.8)
       '@unocss/cli': 0.58.9(rollup@4.34.8)
       '@unocss/core': 0.58.9
       '@unocss/core': 0.58.9
       '@unocss/extractor-arbitrary-variants': 0.58.9
       '@unocss/extractor-arbitrary-variants': 0.58.9
@@ -9581,9 +9657,9 @@ snapshots:
       '@unocss/transformer-compile-class': 0.58.9
       '@unocss/transformer-compile-class': 0.58.9
       '@unocss/transformer-directives': 0.58.9
       '@unocss/transformer-directives': 0.58.9
       '@unocss/transformer-variant-group': 0.58.9
       '@unocss/transformer-variant-group': 0.58.9
-      '@unocss/vite': 0.58.9(rollup@4.34.8)(vite@5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0))
+      '@unocss/vite': 0.58.9(rollup@4.34.8)(vite@5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0))
     optionalDependencies:
     optionalDependencies:
-      vite: 5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0)
+      vite: 5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0)
     transitivePeerDependencies:
     transitivePeerDependencies:
       - postcss
       - postcss
       - rollup
       - rollup
@@ -9684,7 +9760,7 @@ snapshots:
 
 
   vary@1.1.2: {}
   vary@1.1.2: {}
 
 
-  vite-plugin-mock-dev-server@1.8.4(bufferutil@4.0.9)(esbuild@0.21.5)(rollup@4.34.8)(utf-8-validate@5.0.10)(vite@5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0)):
+  vite-plugin-mock-dev-server@1.8.4(bufferutil@4.0.9)(esbuild@0.21.5)(rollup@4.34.8)(utf-8-validate@5.0.10)(vite@5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0)):
     dependencies:
     dependencies:
       '@pengzhanbo/utils': 1.2.0
       '@pengzhanbo/utils': 1.2.0
       '@rollup/pluginutils': 5.1.4(rollup@4.34.8)
       '@rollup/pluginutils': 5.1.4(rollup@4.34.8)
@@ -9701,7 +9777,7 @@ snapshots:
       mime-types: 2.1.35
       mime-types: 2.1.35
       path-to-regexp: 6.3.0
       path-to-regexp: 6.3.0
       picocolors: 1.1.1
       picocolors: 1.1.1
-      vite: 5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0)
+      vite: 5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0)
       ws: 8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)
       ws: 8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)
     optionalDependencies:
     optionalDependencies:
       esbuild: 0.21.5
       esbuild: 0.21.5
@@ -9711,7 +9787,7 @@ snapshots:
       - supports-color
       - supports-color
       - utf-8-validate
       - utf-8-validate
 
 
-  vite-plugin-svg-icons@2.0.1(vite@5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0)):
+  vite-plugin-svg-icons@2.0.1(vite@5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0)):
     dependencies:
     dependencies:
       '@types/svgo': 2.6.4
       '@types/svgo': 2.6.4
       cors: 2.8.5
       cors: 2.8.5
@@ -9721,11 +9797,11 @@ snapshots:
       pathe: 0.2.0
       pathe: 0.2.0
       svg-baker: 1.7.0
       svg-baker: 1.7.0
       svgo: 2.8.0
       svgo: 2.8.0
-      vite: 5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0)
+      vite: 5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0)
     transitivePeerDependencies:
     transitivePeerDependencies:
       - supports-color
       - supports-color
 
 
-  vite@5.4.14(@types/node@20.17.19)(sass@1.77.0)(terser@5.39.0):
+  vite@5.4.14(@types/node@20.17.19)(less@4.2.2)(sass@1.77.0)(terser@5.39.0):
     dependencies:
     dependencies:
       esbuild: 0.21.5
       esbuild: 0.21.5
       postcss: 8.5.2
       postcss: 8.5.2
@@ -9733,6 +9809,7 @@ snapshots:
     optionalDependencies:
     optionalDependencies:
       '@types/node': 20.17.19
       '@types/node': 20.17.19
       fsevents: 2.3.3
       fsevents: 2.3.3
+      less: 4.2.2
       sass: 1.77.0
       sass: 1.77.0
       terser: 5.39.0
       terser: 5.39.0
 
 

+ 14 - 0
src/components/hjflow/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/hjflow/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/hjflow/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/hjflow/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/hjflow/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>-->

+ 191 - 0
src/components/hjflow/src/hjflow/index.vue

@@ -0,0 +1,191 @@
+<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 {useLayout} from '../utils/useLayout'
+import {useSnakeLayoutHook} from "../utils/useSnake";
+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)
+  }
+}
+
+defineExpose({
+  updateNodeData,
+  initFlow
+})
+
+// ======= Panel =======
+// 测试layout  点击切换布局时候的操作
+const {layout} = useLayout()
+
+async function layoutGraph(direction: 'LR' | 'TB') {
+  nodes.value = layout(nodes.value, edges.value, direction)
+
+  nextTick(() => {
+    fitView()
+  })
+}
+
+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)
+  },
+  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("已是初始线路");
+    }
+  }
+}
+// ======= 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>

+ 145 - 0
src/components/hjflow/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/hjflow/src/hooks/useFlow.ts


+ 11 - 0
src/components/hjflow/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/hjflow/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/hjflow/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>

+ 47 - 0
src/components/hjflow/src/leftDrags/workflow/configs.ts

@@ -0,0 +1,47 @@
+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.workFlow,
+            data: {
+                label: "抄送人",
+            }
+        },
+        {
+            type: HJNodeType.universal,
+            data: {
+                label: "测试系统",
+                information: {
+                    configName: 'ddd'
+                }
+            }
+        },
+    ]
+)

+ 41 - 0
src/components/hjflow/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/hjflow/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/hjflow/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/hjflow/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/hjflow/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/hjflow/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/hjflow/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/hjflow/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/hjflow/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>

+ 40 - 0
src/components/hjflow/src/nodes/universal/UniversalNode.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?.configName ?? '请配置名称' }}
+      </div>
+    </template>
+
+  </Basic>
+
+</template>
+
+<style scoped lang="less">
+.content-text {
+  height: 36px;
+  line-height: 36px;
+  font-size: 14px;
+  text-align: center;
+}
+</style>

+ 40 - 0
src/components/hjflow/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>

File diff suppressed because it is too large
+ 20 - 0
src/components/hjflow/src/panel/btns/Back.vue


+ 44 - 0
src/components/hjflow/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/hjflow/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/hjflow/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/hjflow/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>

File diff suppressed because it is too large
+ 32 - 0
src/components/hjflow/src/panel/btns/snake.vue


+ 79 - 0
src/components/hjflow/src/panel/index.vue

@@ -0,0 +1,79 @@
+<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']);
+
+const onAddNode = (type: string) => {
+  emits('addNode', type);
+};
+</script>
+
+<template>
+  <Panel position="top-right" class="process-panel">
+    <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/hjflow/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
+}

+ 173 - 0
src/components/hjflow/src/testShenpiliu.vue

@@ -0,0 +1,173 @@
+<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 => {
+  selectedNode.value = null
+  ruleFormRef.value && ruleFormRef.value.resetFields()
+  selectedNode.value = JSON.parse(JSON.stringify(node))
+  console.log("selectedNode",selectedNode.value)
+  if (selectedNode.value && selectedNode.value.data.information) {
+    formData.value = JSON.parse(JSON.stringify(selectedNode.value.data.information))
+  }
+
+}
+
+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="HJPanelFunNameArray"></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>
+
+

+ 105 - 0
src/components/hjflow/src/types/comTypes.ts

@@ -0,0 +1,105 @@
+// 这里是 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';
+
+
+export  enum HJNodeType {
+  custom = 'custom',
+  nest = 'nest',
+  //   算数相关
+  numberInput = 'number-input',
+  numberFunction = 'number-function',
+  numberResult = 'number-result',
+//   工作流相关
+  workFlow = 'work-flow',
+//   公共 通用 上面是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.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'
+}
+export type GrandparentMethod = (name: HJMethodName, param: HJNodeData) => void;
+
+// Panel相关 HJPanelFunName添加一个  就在数组种写一个
+export type HJPanelFunName = 'addNode' | 'reset' | 'layoutLR' | 'layoutTB' | 'toSnake' | 'back';
+export const HJPanelFunNameArray = ['addNode','reset', 'layoutLR', 'layoutTB', 'toSnake', 'back'];
+
+// 外面会传给HJFlow的props
+export interface HJFlowProps {
+  // Pannel相关
+  panelBtns?: HJPanelFunName[]; //自定义面板按钮 默认空
+  showPanel?: boolean;  // 默认为false,不显示面板
+}
+
+export interface HJFlowInstance {
+  updateNodeData: (node: HJNodeData) => void;
+  initFlow: (nodes: HJNodeData[], edges: any[]) => void;
+}

+ 103 - 0
src/components/hjflow/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' },
+]

+ 72 - 0
src/components/hjflow/src/utils/useLayout.ts

@@ -0,0 +1,72 @@
+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 }
+}

+ 116 - 0
src/components/hjflow/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;
+};

+ 0 - 5
src/main.ts

@@ -14,9 +14,6 @@ import "element-plus/dist/index.css";
 import "@/styles/index.scss";
 import "@/styles/index.scss";
 import "uno.css";
 import "uno.css";
 
 
-import jxqui from 'jxq-ui'
-import 'jxq-ui/dist/style.css'
-
 // avue
 // avue
 import { setupEleAvue } from "@/plugins";
 import { setupEleAvue } from "@/plugins";
 
 
@@ -34,6 +31,4 @@ setupPermission();
 
 
 setupEleAvue(app);
 setupEleAvue(app);
 
 
-app.use(jxqui)
-
 app.use(router).mount("#app");
 app.use(router).mount("#app");

+ 60 - 0
src/views/modules/project-config/com/function-col.vue

@@ -1,12 +1,50 @@
 <script setup lang="ts">
 <script setup lang="ts">
+// import useDragAndDrop from "../configs/useDnD";
+
+// import useDragAndDrop from "jxq-ui";
+
+import useDragAndDrop from "@/components/hjflow/src/hooks/useDnD";
+
+const { onDragStart } = useDragAndDrop();
+
 import TitleHeader from "./titleHeader.vue";
 import TitleHeader from "./titleHeader.vue";
 import { propertyData } from "../configs/properites";
 import { propertyData } from "../configs/properites";
+
+const activeNames = ref(["1"]);
+const handleChange = (val: any) => {
+  console.log(val);
+};
+
+const test = (data) => {
+  console.log("test", data);
+};
 </script>
 </script>
 
 
 <template>
 <template>
   <div class="function-col">
   <div class="function-col">
     <TitleHeader> 添加功能模块</TitleHeader>
     <TitleHeader> 添加功能模块</TitleHeader>
     {{ propertyData }}
     {{ propertyData }}
+    <el-collapse v-model="activeNames" @change="handleChange">
+      <el-collapse-item
+        title="Consistency"
+        name="1"
+        v-for="(funType, index) in propertyData"
+        :key="funType.id"
+      >
+        <div class="drag-container">
+          <div
+            class="drag-item"
+            v-for="(dragData, index) in funType.functions"
+            :key="index"
+            :draggable="true"
+            @dragstart="onDragStart($event, dragData)"
+            @click="test(dragData)"
+          >
+            {{ dragData?.data?.information?.functionName ?? "-" }}
+          </div>
+        </div>
+      </el-collapse-item>
+    </el-collapse>
   </div>
   </div>
 </template>
 </template>
 
 
@@ -16,4 +54,26 @@ import { propertyData } from "../configs/properites";
   height: calc(100vh - $main-header-height);
   height: calc(100vh - $main-header-height);
   background-color: $hj-black-2;
   background-color: $hj-black-2;
 }
 }
+.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>
 </style>

+ 4 - 8
src/views/modules/project-config/configs/properites.ts

@@ -1,9 +1,5 @@
 import { Node } from "@vue-flow/core";
 import { Node } from "@vue-flow/core";
-import type {
-  HJHandle,
-  HJNodeData,
-  HJNodeDataModel,
-} from "jxq-ui/dist/components/flow/src/types/comTypes";
+import { HJNodeDataModel } from "@/components/hjflow/src/types/comTypes";
 
 
 // 功能块名称	属性名称	初始值	数值类型	下限	上限	输入/输出
 // 功能块名称	属性名称	初始值	数值类型	下限	上限	输入/输出
 
 
@@ -31,7 +27,7 @@ export interface AutoTestNodeData extends Partial<Node> {
   data: HJNodeData2;
   data: HJNodeData2;
 }
 }
 
 
-export const propertyData: FunctionTypeModel[] = [
+export const propertyData = ref<FunctionTypeModel[]>([
   {
   {
     id: "1",
     id: "1",
     name: "信号源",
     name: "信号源",
@@ -39,7 +35,7 @@ export const propertyData: FunctionTypeModel[] = [
       {
       {
         type: "universal",
         type: "universal",
         data: {
         data: {
-          label: "",
+          label: "信号源",
           information: {
           information: {
             functionType: "功能块类别",
             functionType: "功能块类别",
             functionTypeId: "功能块类别ID",
             functionTypeId: "功能块类别ID",
@@ -49,4 +45,4 @@ export const propertyData: FunctionTypeModel[] = [
       },
       },
     ],
     ],
   },
   },
-];
+]);

+ 45 - 1
src/views/modules/project-config/project-config.vue

@@ -1,13 +1,57 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import ProjectMessage from "./com/project-message.vue";
 import ProjectMessage from "./com/project-message.vue";
 import FunctionModule from "./com/function-col.vue";
 import FunctionModule from "./com/function-col.vue";
+import HJFlow from "@/components/hjflow/src/hjflow/index.vue";
+import {
+  HJFlowInstance,
+  HJMethodName,
+  HJNodeData,
+} from "@/components/hjflow/src/types/comTypes";
+import { Edge } from "@vue-flow/core";
+
+const flowRef = ref<HJFlowInstance>();
+const nodes = ref<HJNodeData[]>([]);
+const edges = ref<Edge[]>([]);
+// vue 的相关操作
+const onNodeOperation = (name: HJMethodName, node: HJNodeData): void => {
+  console.log("onNodeOperation", name, node);
+  // selectedNode.value = null;
+  // ruleFormRef.value && ruleFormRef.value.resetFields();
+  // selectedNode.value = JSON.parse(JSON.stringify(node));
+  console.log("selectedNode", selectedNode.value);
+  // if (selectedNode.value && selectedNode.value.data.information) {
+  //   formData.value = JSON.parse(
+  //     JSON.stringify(selectedNode.value.data.information)
+  //   );
+  // }
+};
+
+// 右侧信息
+// ======= 信息展示 =======
+const infoVisible = ref(false);
+const selectedNode = ref<HJNodeData | null>(null); //双击的节点数据
+const closeInfo = () => {
+  infoVisible.value = false;
+  selectedNode.value = null;
+};
+
+onMounted(() => {
+  // 接口调用完成要初始化 ,为的是保存一个最初的数据
+  flowRef.value && flowRef.value.initFlow([], []);
+});
 </script>
 </script>
 
 
 <template>
 <template>
   <div class="project-config-container">
   <div class="project-config-container">
     <ProjectMessage />
     <ProjectMessage />
     <FunctionModule />
     <FunctionModule />
-    <HJFlow class="hjflow-box" />
+    <HJFlow
+      class="hjflow-box"
+      ref="flowRef"
+      v-model:nodes-data="nodes"
+      v-model:edges-data="edges"
+      @hjMethod="onNodeOperation"
+    />
   </div>
   </div>
 </template>
 </template>