Browse Source

feat: 流程首次嵌入

liuc 5 months ago
parent
commit
6f37f42fea
100 changed files with 12802 additions and 129 deletions
  1. 329 19
      package-lock.json
  2. 2 0
      package.json
  3. 31 2
      src/api/flow/flowApi.js
  4. 148 100
      src/api/flow/formApi.js
  5. 128 0
      src/api/flow/settingApi.js
  6. BIN
      src/assets/back.png
  7. BIN
      src/assets/bgAdmin.png
  8. BIN
      src/assets/file.png
  9. BIN
      src/assets/flow/title.png
  10. 0 0
      src/assets/flow/title.svg
  11. BIN
      src/assets/form/ask.png
  12. BIN
      src/assets/form/filling1.png
  13. BIN
      src/assets/form/filling2.png
  14. BIN
      src/assets/form/finish.png
  15. 1 0
      src/assets/form/finish.svg
  16. BIN
      src/assets/form/going.png
  17. 1 0
      src/assets/form/right.svg
  18. BIN
      src/assets/form/state_0.png
  19. BIN
      src/assets/form/state_1.png
  20. BIN
      src/assets/form/state_2.png
  21. BIN
      src/assets/form/state_3.png
  22. BIN
      src/assets/form/state_4.png
  23. BIN
      src/assets/form/state_5.png
  24. BIN
      src/assets/place.png
  25. BIN
      src/assets/switch.png
  26. BIN
      src/assets/time.png
  27. 24 0
      src/components/BsUi/MonacoEditor/EditorWorker.vue
  28. 122 0
      src/components/BsUi/MonacoEditor/index.hook.js
  29. 4 0
      src/components/BsUi/MonacoEditor/index.js
  30. 98 0
      src/components/BsUi/MonacoEditor/index.vue
  31. 24 0
      src/components/MonacoEditor/EditorWorker.vue
  32. 6 5
      src/components/MonacoEditor/index.hook.js
  33. 4 0
      src/components/MonacoEditor/index.js
  34. 98 0
      src/components/MonacoEditor/index.vue
  35. 18 0
      src/components/StFormModal/index.js
  36. 124 0
      src/components/StFormModal/stFormModal.vue
  37. 93 0
      src/components/StMobile/StPopup/index.jsx
  38. 0 0
      src/components/StModal/direct.js
  39. 54 0
      src/components/StModal/index.jsx
  40. 26 0
      src/components/StModal/index.vue
  41. 16 0
      src/components/StModal/install.js
  42. 792 0
      src/components/StSelectUser/index.vue
  43. 129 0
      src/components/common/common-table/components/search.vue
  44. 514 0
      src/components/common/common-table/components/table.vue
  45. 130 0
      src/components/common/common-table/example.js
  46. 135 0
      src/components/common/common-table/index.vue
  47. 14 2
      src/main.js
  48. 27 1
      src/router/flow/index.js
  49. 222 0
      src/views/flow/aigc/chat/index.vue
  50. 145 0
      src/views/flow/aigc/knowledge/index.vue
  51. 100 0
      src/views/flow/aigc/project/index.vue
  52. 224 0
      src/views/flow/ragflow/chat/index.vue
  53. 145 0
      src/views/flow/ragflow/knowledge/index.vue
  54. 100 0
      src/views/flow/ragflow/project/index.vue
  55. 164 0
      src/views/flow/setting/eventService.vue
  56. 118 0
      src/views/flow/setting/flowAdministrator.vue
  57. 286 0
      src/views/flow/setting/flowAgent.vue
  58. 43 0
      src/views/flow/setting/flowOperation.vue
  59. 68 0
      src/views/flow/setting/mainSetting.vue
  60. 237 0
      src/views/flow/setting/queryService.vue
  61. 192 0
      src/views/flow/stDataModel/components/SmartFieldDialog.vue
  62. 143 0
      src/views/flow/stDataModel/dataJsonData.js
  63. 472 0
      src/views/flow/stDataModel/index.vue
  64. 115 0
      src/views/flow/stDesignMain/index.vue
  65. 235 0
      src/views/flow/stFlowDesign/components/ConfigPanel/ConfigEdge/index.vue
  66. 183 0
      src/views/flow/stFlowDesign/components/ConfigPanel/ConfigGrid/index.vue
  67. 60 0
      src/views/flow/stFlowDesign/components/ConfigPanel/ConfigGrid/method.js
  68. 593 0
      src/views/flow/stFlowDesign/components/ConfigPanel/ConfigNode/index.vue
  69. 124 0
      src/views/flow/stFlowDesign/components/ConfigPanel/ConfigNode/method.js
  70. 36 0
      src/views/flow/stFlowDesign/components/ConfigPanel/index.less
  71. 48 0
      src/views/flow/stFlowDesign/components/ConfigPanel/index.vue
  72. 257 0
      src/views/flow/stFlowDesign/components/ConfigPanel/selectParticipant.vue
  73. 367 0
      src/views/flow/stFlowDesign/components/ToolBar/index.vue
  74. 0 0
      src/views/flow/stFlowDesign/data.js
  75. 702 0
      src/views/flow/stFlowDesign/graph/index.js
  76. 127 0
      src/views/flow/stFlowDesign/graph/shape.js
  77. 91 0
      src/views/flow/stFlowDesign/index.less
  78. 118 0
      src/views/flow/stFlowDesign/index.vue
  79. 67 0
      src/views/flow/stFlowDesign/models/global.js
  80. 5 0
      src/views/flow/stFlowSetting/index.vue
  81. 199 0
      src/views/flow/stFlowView/adjustFlow.vue
  82. 700 0
      src/views/flow/stFlowView/flowView.vue
  83. 23 0
      src/views/flow/stFlowView/index.less
  84. 71 0
      src/views/flow/stFlowView/log.vue
  85. 117 0
      src/views/flow/stFlowView/pressDialog.vue
  86. 153 0
      src/views/flow/stFlowView/script/index.js
  87. 210 0
      src/views/flow/stFormDesign/packages/PopUpMapping/index.vue
  88. 346 0
      src/views/flow/stFormDesign/packages/PopUpQuery/index.vue
  89. 291 0
      src/views/flow/stFormDesign/packages/PopUpQueryChild/index.vue
  90. 214 0
      src/views/flow/stFormDesign/packages/PopUpQueryGlobal/index.vue
  91. 81 0
      src/views/flow/stFormDesign/packages/PreviewCode/index.vue
  92. 606 0
      src/views/flow/stFormDesign/packages/StBatch/batch.vue
  93. 2 0
      src/views/flow/stFormDesign/packages/StBatch/index.js
  94. 299 0
      src/views/flow/stFormDesign/packages/StBatch/module/StFormModelItem.vue
  95. 225 0
      src/views/flow/stFormDesign/packages/StChangeOption/index.vue
  96. 35 0
      src/views/flow/stFormDesign/packages/StCheckbox/index.vue
  97. 60 0
      src/views/flow/stFormDesign/packages/StDatePicker/datePicker.vue
  98. 2 0
      src/views/flow/stFormDesign/packages/StDatePicker/index.js
  99. 153 0
      src/views/flow/stFormDesign/packages/StDeptSelector/index.vue
  100. 136 0
      src/views/flow/stFormDesign/packages/StEditor/StEditor.vue

+ 329 - 19
package-lock.json

@@ -41,6 +41,7 @@
         "diff2html": "3.4.47",
         "echarts": "5.4.3",
         "highlight.js": "11.8.0",
+        "js-base64": "^3.7.7",
         "js-cookie": "^3.0.5",
         "jslint": "^0.12.1",
         "lodash": "^4.17.21",
@@ -62,12 +63,13 @@
         "vue": "3.4.27",
         "vue-codemirror-lite": "^1.0.4",
         "vue-i18n": "9.13.1",
+        "vue-plugin-hiprint": "^0.0.60",
         "vue-quill-editor": "^3.0.6",
         "vue-router": "4.3.2",
         "vue3-json-viewer": "2.2.2",
         "vue3-tabs-chrome": "^0.3.3",
         "vuedraggable": "^4.1.0",
-        "vxe-table": "^4.7.80"
+        "vxe-table": "^4.12.5"
       },
       "devDependencies": {
         "@vitejs/plugin-vue": "5.0.4",
@@ -863,6 +865,14 @@
         "node": ">=6.9.0"
       }
     },
+    "node_modules/@claviska/jquery-minicolors": {
+      "version": "2.3.6",
+      "resolved": "https://registry.npmmirror.com/@claviska/jquery-minicolors/-/jquery-minicolors-2.3.6.tgz",
+      "integrity": "sha512-8Ro6D4GCrmOl41+6w4NFhEOpx8vjxwVRI69bulXsFDt49uVRKhLU5TnzEV7AmOJrylkVq+ugnYNMiGHBieeKUQ==",
+      "peerDependencies": {
+        "jquery": ">= 1.7.x"
+      }
+    },
     "node_modules/@ctrl/tinycolor": {
       "version": "3.6.1",
       "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
@@ -2322,6 +2332,11 @@
         "nanopop": "^2.1.0"
       }
     },
+    "node_modules/@socket.io/component-emitter": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmmirror.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
+      "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
+    },
     "node_modules/@transloadit/prettier-bytes": {
       "version": "0.0.7",
       "resolved": "https://registry.npmmirror.com/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz",
@@ -2395,6 +2410,11 @@
       "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
       "dev": true
     },
+    "node_modules/@types/raf": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmmirror.com/@types/raf/-/raf-3.4.3.tgz",
+      "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw=="
+    },
     "node_modules/@types/web-bluetooth": {
       "version": "0.0.16",
       "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
@@ -2779,12 +2799,15 @@
       }
     },
     "node_modules/@vxe-ui/core": {
-      "version": "4.0.12",
-      "resolved": "https://registry.npmmirror.com/@vxe-ui/core/-/core-4.0.12.tgz",
-      "integrity": "sha512-ft8f874eQSv4N9+oulFKeg8APgd8RMHeFeUUUTNckIRJ/cNi0dbR0Fe2+ZZpRl3BwRtbE2hHb2FKWmL2oyZkfw==",
+      "version": "4.1.5",
+      "resolved": "https://registry.npmmirror.com/@vxe-ui/core/-/core-4.1.5.tgz",
+      "integrity": "sha512-IgRwVueejOGC5t+bVmBAUkoUplvp1R77pfYX6bb4fcLEPUdBGOdm4I0LCKTDWQ24Mj3Bki7wNpt3sdtEZEzdoA==",
       "dependencies": {
         "dom-zindex": "^1.0.6",
-        "xe-utils": "^3.5.30"
+        "xe-utils": "^3.7.5"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
       }
     },
     "node_modules/@wangeditor/basic-modules": {
@@ -3098,6 +3121,18 @@
         "@xtuc/long": "4.2.2"
       }
     },
+    "node_modules/@wtto00/html2canvas": {
+      "version": "1.4.3",
+      "resolved": "https://registry.npmmirror.com/@wtto00/html2canvas/-/html2canvas-1.4.3.tgz",
+      "integrity": "sha512-jwsb+xL8N+gjrSNABSaFdxmWtE4c7RNFjP20lo1G7gs63Qqo1phhxVBTzxc/apDVh6LgXsU2l5bwKtXd9uz65w==",
+      "dependencies": {
+        "css-line-break": "^2.1.0",
+        "text-segmentation": "^1.0.3"
+      },
+      "engines": {
+        "node": ">=8.0.0"
+      }
+    },
     "node_modules/@xtuc/ieee754": {
       "version": "1.2.0",
       "resolved": "https://registry.npmmirror.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@@ -3443,6 +3478,17 @@
       "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
       "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
     },
+    "node_modules/atob": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/atob/-/atob-2.1.2.tgz",
+      "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
+      "bin": {
+        "atob": "bin/atob.js"
+      },
+      "engines": {
+        "node": ">= 4.5.0"
+      }
+    },
     "node_modules/axios": {
       "version": "1.6.8",
       "resolved": "https://registry.npmmirror.com/axios/-/axios-1.6.8.tgz",
@@ -3458,6 +3504,14 @@
       "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
     },
+    "node_modules/base64-arraybuffer": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+      "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
+      "engines": {
+        "node": ">= 0.6.0"
+      }
+    },
     "node_modules/boolbase": {
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz",
@@ -3517,6 +3571,17 @@
         "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
       }
     },
+    "node_modules/btoa": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/btoa/-/btoa-1.2.1.tgz",
+      "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==",
+      "bin": {
+        "btoa": "bin/btoa.js"
+      },
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
     "node_modules/buffer-from": {
       "version": "1.1.2",
       "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -3528,6 +3593,14 @@
       "resolved": "https://registry.npmmirror.com/buffer-shims/-/buffer-shims-1.0.0.tgz",
       "integrity": "sha512-Zy8ZXMyxIT6RMTeY7OP/bDndfj6bwCan7SS98CEndS6deHwWPpseeHlwarNcBim+etXnF9HBc1non5JgDaJU1g=="
     },
+    "node_modules/bwip-js": {
+      "version": "4.6.0",
+      "resolved": "https://registry.npmmirror.com/bwip-js/-/bwip-js-4.6.0.tgz",
+      "integrity": "sha512-Djr1aQ3d1N8rpLz5XgbpNW/yrP4owC+rk5/pZTSzkqXY0WvqzJ0yJTxA8JJA6WDxOAP1hP70AcnTxiDvthy+/g==",
+      "bin": {
+        "bwip-js": "bin/bwip-js.js"
+      }
+    },
     "node_modules/call-bind": {
       "version": "1.0.7",
       "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.7.tgz",
@@ -3601,6 +3674,29 @@
         }
       ]
     },
+    "node_modules/canvg": {
+      "version": "3.0.11",
+      "resolved": "https://registry.npmmirror.com/canvg/-/canvg-3.0.11.tgz",
+      "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
+      "dependencies": {
+        "@babel/runtime": "^7.12.5",
+        "@types/raf": "^3.4.0",
+        "core-js": "^3.8.3",
+        "raf": "^3.4.1",
+        "regenerator-runtime": "^0.13.7",
+        "rgbcolor": "^1.0.1",
+        "stackblur-canvas": "^2.0.0",
+        "svg-pathdata": "^6.0.3"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/canvg/node_modules/regenerator-runtime": {
+      "version": "0.13.11",
+      "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+      "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
+    },
     "node_modules/chalk": {
       "version": "2.4.2",
       "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz",
@@ -3833,6 +3929,14 @@
         "node": ">=12 || >=16"
       }
     },
+    "node_modules/css-line-break": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz",
+      "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+      "dependencies": {
+        "utrie": "^1.0.2"
+      }
+    },
     "node_modules/cssesc": {
       "version": "3.0.0",
       "resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz",
@@ -3871,7 +3975,6 @@
       "version": "4.3.7",
       "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz",
       "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
-      "dev": true,
       "dependencies": {
         "ms": "^2.1.3"
       },
@@ -4103,6 +4206,12 @@
         "ssr-window": "^3.0.0-alpha.1"
       }
     },
+    "node_modules/dompurify": {
+      "version": "2.5.8",
+      "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-2.5.8.tgz",
+      "integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==",
+      "optional": true
+    },
     "node_modules/draggabilly": {
       "version": "2.4.1",
       "resolved": "https://registry.npmmirror.com/draggabilly/-/draggabilly-2.4.1.tgz",
@@ -4139,6 +4248,26 @@
       "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
       "dev": true
     },
+    "node_modules/engine.io-client": {
+      "version": "6.6.3",
+      "resolved": "https://registry.npmmirror.com/engine.io-client/-/engine.io-client-6.6.3.tgz",
+      "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
+      "dependencies": {
+        "@socket.io/component-emitter": "~3.1.0",
+        "debug": "~4.3.1",
+        "engine.io-parser": "~5.2.1",
+        "ws": "~8.17.1",
+        "xmlhttprequest-ssl": "~2.1.1"
+      }
+    },
+    "node_modules/engine.io-parser": {
+      "version": "5.2.3",
+      "resolved": "https://registry.npmmirror.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+      "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
     "node_modules/enhanced-resolve": {
       "version": "5.18.1",
       "resolved": "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
@@ -4775,6 +4904,11 @@
         "reusify": "^1.0.4"
       }
     },
+    "node_modules/fflate": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.8.2.tgz",
+      "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
+    },
     "node_modules/file-entry-cache": {
       "version": "6.0.1",
       "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -5268,6 +5402,19 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
+    "node_modules/html2canvas": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz",
+      "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+      "optional": true,
+      "dependencies": {
+        "css-line-break": "^2.1.0",
+        "text-segmentation": "^1.0.3"
+      },
+      "engines": {
+        "node": ">=8.0.0"
+      }
+    },
     "node_modules/i18next": {
       "version": "20.6.1",
       "resolved": "https://registry.npmmirror.com/i18next/-/i18next-20.6.1.tgz",
@@ -5612,6 +5759,16 @@
         "url": "https://github.com/chalk/supports-color?sponsor=1"
       }
     },
+    "node_modules/jquery": {
+      "version": "3.7.1",
+      "resolved": "https://registry.npmmirror.com/jquery/-/jquery-3.7.1.tgz",
+      "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
+    },
+    "node_modules/js-base64": {
+      "version": "3.7.7",
+      "resolved": "https://registry.npmmirror.com/js-base64/-/js-base64-3.7.7.tgz",
+      "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw=="
+    },
     "node_modules/js-cookie": {
       "version": "3.0.5",
       "resolved": "https://registry.npmmirror.com/js-cookie/-/js-cookie-3.0.5.tgz",
@@ -5637,6 +5794,11 @@
         "js-yaml": "bin/js-yaml.js"
       }
     },
+    "node_modules/jsbarcode": {
+      "version": "3.11.6",
+      "resolved": "https://registry.npmmirror.com/jsbarcode/-/jsbarcode-3.11.6.tgz",
+      "integrity": "sha512-G5TKGyKY1zJo0ZQKFM1IIMfy0nF2rs92BLlCz+cU4/TazIc4ZH+X1GYeDRt7TKjrYqmPfTjwTBkU/QnQlsYiuA=="
+    },
     "node_modules/jsbn": {
       "version": "1.1.0",
       "resolved": "https://registry.npmmirror.com/jsbn/-/jsbn-1.1.0.tgz",
@@ -5728,6 +5890,23 @@
         "node": ">=6"
       }
     },
+    "node_modules/jspdf": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmmirror.com/jspdf/-/jspdf-2.5.2.tgz",
+      "integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
+      "dependencies": {
+        "@babel/runtime": "^7.23.2",
+        "atob": "^2.1.2",
+        "btoa": "^1.2.1",
+        "fflate": "^0.8.1"
+      },
+      "optionalDependencies": {
+        "canvg": "^3.0.6",
+        "core-js": "^3.6.0",
+        "dompurify": "^2.5.4",
+        "html2canvas": "^1.0.0-rc.5"
+      }
+    },
     "node_modules/keyv": {
       "version": "4.5.4",
       "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz",
@@ -6177,8 +6356,7 @@
     "node_modules/ms": {
       "version": "2.1.3",
       "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
-      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
-      "dev": true
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
     },
     "node_modules/namespace-emitter": {
       "version": "2.0.1",
@@ -6322,6 +6500,11 @@
         "url": "https://github.com/fb55/nth-check?sponsor=1"
       }
     },
+    "node_modules/nzh": {
+      "version": "1.0.14",
+      "resolved": "https://registry.npmmirror.com/nzh/-/nzh-1.0.14.tgz",
+      "integrity": "sha512-wKgaqCSZdrySvB4RWop5g+v6IDv2IErsT6rjq06Bg0yiT9hiHYZO12GMGx/xweGVLcO2lDjX5RqWD0S/Jy9z5Q=="
+    },
     "node_modules/object-assign": {
       "version": "4.1.1",
       "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
@@ -6556,6 +6739,11 @@
         "node": ">=8"
       }
     },
+    "node_modules/performance-now": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz",
+      "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="
+    },
     "node_modules/picocolors": {
       "version": "1.1.0",
       "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.0.tgz",
@@ -6855,6 +7043,14 @@
         "node": ">=0.10"
       }
     },
+    "node_modules/raf": {
+      "version": "3.4.1",
+      "resolved": "https://registry.npmmirror.com/raf/-/raf-3.4.1.tgz",
+      "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
+      "dependencies": {
+        "performance-now": "^2.1.0"
+      }
+    },
     "node_modules/randombytes": {
       "version": "2.1.0",
       "resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz",
@@ -8322,6 +8518,14 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/rgbcolor": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/rgbcolor/-/rgbcolor-1.0.1.tgz",
+      "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
+      "engines": {
+        "node": ">= 0.8.15"
+      }
+    },
     "node_modules/rimraf": {
       "version": "5.0.10",
       "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-5.0.10.tgz",
@@ -8761,6 +8965,32 @@
         "node": ">=12.17.0"
       }
     },
+    "node_modules/socket.io-client": {
+      "version": "4.8.1",
+      "resolved": "https://registry.npmmirror.com/socket.io-client/-/socket.io-client-4.8.1.tgz",
+      "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
+      "dependencies": {
+        "@socket.io/component-emitter": "~3.1.0",
+        "debug": "~4.3.2",
+        "engine.io-client": "~6.6.1",
+        "socket.io-parser": "~4.2.4"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/socket.io-parser": {
+      "version": "4.2.4",
+      "resolved": "https://registry.npmmirror.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
+      "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
+      "dependencies": {
+        "@socket.io/component-emitter": "~3.1.0",
+        "debug": "~4.3.1"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
     "node_modules/sortablejs": {
       "version": "1.15.0",
       "resolved": "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.15.0.tgz",
@@ -8839,6 +9069,14 @@
       "resolved": "https://registry.npmmirror.com/ssr-window/-/ssr-window-3.0.0.tgz",
       "integrity": "sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA=="
     },
+    "node_modules/stackblur-canvas": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmmirror.com/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
+      "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
+      "engines": {
+        "node": ">=0.1.14"
+      }
+    },
     "node_modules/store": {
       "version": "2.0.12",
       "resolved": "https://registry.npmmirror.com/store/-/store-2.0.12.tgz",
@@ -9178,6 +9416,14 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/svg-pathdata": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmmirror.com/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
+      "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
     "node_modules/svg-tags": {
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/svg-tags/-/svg-tags-1.0.0.tgz",
@@ -9346,6 +9592,14 @@
         "node": ">=10"
       }
     },
+    "node_modules/text-segmentation": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz",
+      "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+      "dependencies": {
+        "utrie": "^1.0.2"
+      }
+    },
     "node_modules/text-table": {
       "version": "0.2.0",
       "resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
@@ -9537,6 +9791,14 @@
         "node": ">= 4"
       }
     },
+    "node_modules/utrie": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz",
+      "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+      "dependencies": {
+        "base64-arraybuffer": "^1.0.2"
+      }
+    },
     "node_modules/uuid": {
       "version": "10.0.0",
       "resolved": "https://registry.npmmirror.com/uuid/-/uuid-10.0.0.tgz",
@@ -9872,6 +10134,26 @@
         "vue": "^3.0.0"
       }
     },
+    "node_modules/vue-plugin-hiprint": {
+      "version": "0.0.60",
+      "resolved": "https://registry.npmmirror.com/vue-plugin-hiprint/-/vue-plugin-hiprint-0.0.60.tgz",
+      "integrity": "sha512-a5uOMn6Nr4qlYYaVNbQKwRZJa8UcNMTflfi6J430/NDtySJB+5ArE8I8+NLjgVV56x3/qdUBs/GWuZCX5Umv1w==",
+      "dependencies": {
+        "@claviska/jquery-minicolors": "^2.3.6",
+        "@wtto00/html2canvas": "^1.4.3",
+        "bwip-js": "^4.0.0",
+        "canvg": "^3.0.10",
+        "jquery": "^3.6.0",
+        "jsbarcode": "^3.11.5",
+        "jspdf": "^2.5.1",
+        "lodash": "^4.17.21",
+        "nzh": "^1.0.8",
+        "socket.io-client": "^4.5.1"
+      },
+      "engines": {
+        "node": ">=16"
+      }
+    },
     "node_modules/vue-quill-editor": {
       "version": "3.0.6",
       "resolved": "https://registry.npmmirror.com/vue-quill-editor/-/vue-quill-editor-3.0.6.tgz",
@@ -9964,19 +10246,19 @@
       "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="
     },
     "node_modules/vxe-pc-ui": {
-      "version": "4.2.9",
-      "resolved": "https://registry.npmmirror.com/vxe-pc-ui/-/vxe-pc-ui-4.2.9.tgz",
-      "integrity": "sha512-FxjGhk6hwMloA3lDk+2P6ruIDOaqCCEloU/yx8XtRHLRuTOBlmC+dycHQi4vdt4qg05ssB4QW/wtOrfTNgjlHg==",
+      "version": "4.6.17",
+      "resolved": "https://registry.npmmirror.com/vxe-pc-ui/-/vxe-pc-ui-4.6.17.tgz",
+      "integrity": "sha512-+F8LMoPdwGKpK57yzZ/4nIGveKt31nbCfVTewpB5Kgp9x+yALXnREOoEOe3ftRYG59aXk9ZrzFQBpQB98sPdaA==",
       "dependencies": {
-        "@vxe-ui/core": "^4.0.12"
+        "@vxe-ui/core": "^4.1.5"
       }
     },
     "node_modules/vxe-table": {
-      "version": "4.7.84",
-      "resolved": "https://registry.npmmirror.com/vxe-table/-/vxe-table-4.7.84.tgz",
-      "integrity": "sha512-dTBI20273NCWZ8Ngb3bFKYtjZiKH73ecbBUy9s4E1W2Xb9OjxKXwMhyi2TxO0OYl1Qxdf2KCwYNov6zuetx/iQ==",
+      "version": "4.13.35",
+      "resolved": "https://registry.npmmirror.com/vxe-table/-/vxe-table-4.13.35.tgz",
+      "integrity": "sha512-/IQGHLVI/hYPElcgEv+hcY1FIIOLCDtfyXHpMoTtnap/uOeX2PsSb3Og54OKZ8lODNmxy8XwzpwMooaj8EGGCw==",
       "dependencies": {
-        "vxe-pc-ui": "^4.2.9"
+        "vxe-pc-ui": "^4.6.0"
       }
     },
     "node_modules/warning": {
@@ -10256,10 +10538,30 @@
       "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
       "dev": true
     },
+    "node_modules/ws": {
+      "version": "8.17.1",
+      "resolved": "https://registry.npmmirror.com/ws/-/ws-8.17.1.tgz",
+      "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/xe-utils": {
-      "version": "3.5.30",
-      "resolved": "https://registry.npmmirror.com/xe-utils/-/xe-utils-3.5.30.tgz",
-      "integrity": "sha512-5Ez6JUANpMakduiTLxrNObzqMebnM4697KvHW5okedkUjXvYgGvkbg0tABTkvwDW/Pb09v7vT68dzBOeAuOu0g=="
+      "version": "3.7.5",
+      "resolved": "https://registry.npmmirror.com/xe-utils/-/xe-utils-3.7.5.tgz",
+      "integrity": "sha512-wDjqnXw02EQxf2jqlE1nhvT9HP3PDVcyrol5whDJN/NOvnMyXIzcwEiPB/H2T3aq07f2QQXsSs4Z8g5L3BVH5A=="
     },
     "node_modules/xml-name-validator": {
       "version": "4.0.0",
@@ -10270,6 +10572,14 @@
         "node": ">=12"
       }
     },
+    "node_modules/xmlhttprequest-ssl": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
+      "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/yallist": {
       "version": "3.1.1",
       "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz",

+ 2 - 0
package.json

@@ -49,6 +49,7 @@
     "diff2html": "3.4.47",
     "echarts": "5.4.3",
     "highlight.js": "11.8.0",
+    "js-base64": "^3.7.7",
     "js-cookie": "^3.0.5",
     "jslint": "^0.12.1",
     "lodash": "^4.17.21",
@@ -70,6 +71,7 @@
     "vue": "3.4.27",
     "vue-codemirror-lite": "^1.0.4",
     "vue-i18n": "9.13.1",
+    "vue-plugin-hiprint": "^0.0.60",
     "vue-quill-editor": "^3.0.6",
     "vue-router": "4.3.2",
     "vue3-json-viewer": "2.2.2",

+ 31 - 2
src/api/flow/flowApi.js

@@ -1,7 +1,7 @@
 /*
  * @Author: lc
  */
-import {request} from '/@/lib/axios'
+import {getDownload, request} from '/@/lib/axios'
 
 const flowApi = {
     getAllStFlowMenu: '/bpm/core/getAllStFlowMenu',
@@ -11,6 +11,8 @@ const flowApi = {
     saveStFlowTemp: '/bpm/core/istStFlowTemplate',
     addStFlowMenu: '/bpm/core/addStFlowMenu',
     getStTaskItemList: '/bpm/core/instance/task',
+    getUserTaskNum: '/bpm/core/getUserTaskNum',
+    getNotices: '/bpm/core/getNotices',
 
     finishInstance: '/flow/operation/finishInstance',
     cancelInstance: '/flow/operation/cancelInstance',
@@ -28,9 +30,29 @@ const flowApi = {
     insertUrge: "/bpm/sub/insertUrge",
     getUrges: "/bpm/sub/getUrges",
     // 删除语法
-    deleteScriptCode: '/form/engine/script/delete'
+    deleteScriptCode: '/form/engine/script/delete',
+    exportData: '/bpm/core/exportData',
+    getTaskCalendar: '/governmentProject/getTaskCalendar'
 
 }
+
+export function exportData(parameter) {
+    return getDownload(flowApi.exportData,parameter)
+}
+export function getUserTaskNum(parameter) {
+    return request({
+        url: flowApi.getUserTaskNum,
+        method: 'get',
+        params: parameter
+    })
+}
+export function getNotices(parameter) {
+    return request({
+        url: flowApi.getNotices,
+        method: 'get',
+        params: parameter
+    })
+}
 export function getFlowTemplateHistory(parameter) {
     return request({
         url: flowApi.getFlowTemplateHistory,
@@ -251,3 +273,10 @@ export function deleteScriptCode(id) {
         method: 'post',
     })
 }
+export function getTaskCalendar(parameter) {
+    return request({
+        url: flowApi.getTaskCalendar,
+        method: 'post',
+        data: parameter
+    })
+}

+ 148 - 100
src/api/flow/formApi.js

@@ -4,152 +4,200 @@
 import {request} from '/@/lib/axios'
 
 const formApi = {
-  getFormTemplate: '/form/engine/getFormTemplate',
-  getFormLoadTemplate: '/form/engine/getFormLoadTemplate',
-  buildParticipantTree: '/form/engine/buildParticipantTree',
-  getDynamicData: '/form/engine/getDynamicData',
-  istFormTemplate: '/form/engine/istFormTemplate',
-  updateFormTemplate: '/form/engine/updateFormTemplate',
-  getStCommentByInstanceid: '/form/engine/getStCommentByInstanceid',
-  getAllDict: '/form/engine/getAllDict',
-  updateJsStr: '/form/engine/script/update',
-  getJsStr: '/form/engine/script/get',
-  getParticipantName: '/bpm/sub/getParticipantName',
-  getUserByUserCode: '/bpm/sub/getUserByUserCode',
-  getCodeHistory: "/form/engine/script/history"
+    getFormTemplate: '/form/engine/getFormTemplate',
+    getFormLoadTemplate: '/form/engine/getFormLoadTemplate',
+    buildParticipantTree: '/form/engine/buildParticipantTree',
+    getDynamicData: '/form/engine/getDynamicData',
+    istFormTemplate: '/form/engine/istFormTemplate',
+    updateFormTemplate: '/form/engine/updateFormTemplate',
+    getStCommentByInstanceid: '/form/engine/getStCommentByInstanceid',
+    getAllDict: '/form/engine/getAllDict',
+    updateJsStr: '/form/engine/script/update',
+    getJsStr: '/form/engine/script/get',
+    getParticipantName: '/bpm/sub/getParticipantName',
+    getUserByUserCode: '/bpm/sub/getUserByUserCode',
+    getCodeHistory: "/form/engine/script/history",
+    viewList: "/bpm/core/instance/viewList",
+    getAddParticipant: "/form/engine/getAddParticipant",
+    addParticipant: "/form/engine/insert/addParticipant",
+    getLastApprovalNodes: "/flow/operation/getLastApprovalNodes",
+    rejectToSpecifyActivate: "/flow/operation/rejectToSpecifyActivate",
+
+}
+export function rejectToSpecifyActivate(data) {
+    return request({
+        url: formApi.rejectToSpecifyActivate,
+        method: 'post',
+        data
+    })
+}
+
+
+export function getLastApprovalNodes(parameter) {
+    return request({
+        url: formApi.getLastApprovalNodes,
+        method: 'get',
+        params: parameter
+    })
+}
+export function addParticipant(data) {
+    return request({
+        url: formApi.addParticipant,
+        method: 'post',
+        data
+    })
+}
+
+
+export function getAddParticipant(parameter) {
+    return request({
+        url: formApi.getAddParticipant,
+        method: 'get',
+        params: parameter
+    })
+}
+
+export function viewList(parameter) {
+    return request({
+        url: formApi.viewList,
+        method: 'get',
+        params: parameter
+    })
 }
+
 export function getUserByUserCode(parameter) {
-  return request({
-    url: formApi.getUserByUserCode,
-    method: 'get',
-    params: parameter
-  })
+    return request({
+        url: formApi.getUserByUserCode,
+        method: 'get',
+        params: parameter
+    })
 }
+
 export function formGet(url, parameter) {
-  return request({
-    url: url,
-    method: 'get',
-    params: parameter
-  })
+    return request({
+        url: url,
+        method: 'get',
+        params: parameter
+    })
 }
 
 export function formPost(url, parameter) {
-  return request({
-    url: url,
-    method: 'post',
-    data: parameter,
-    headers: {
-      'Content-Type': 'application/json;charset=UTF-8'
-    }
-  })
+    return request({
+        url: url,
+        method: 'post',
+        data: parameter,
+        headers: {
+            'Content-Type': 'application/json;charset=UTF-8'
+        }
+    })
 }
 
 export function getParticipantName(parameter) {
-  return request({
-    url: formApi.getParticipantName,
-    method: 'get',
-    params: parameter
-  })
+    return request({
+        url: formApi.getParticipantName,
+        method: 'get',
+        params: parameter
+    })
 }
 
 export function getFormLoadTemplate(parameter) {
-  return request({
-    url: formApi.getFormLoadTemplate,
-    method: 'get',
-    params: parameter
-  })
+    return request({
+        url: formApi.getFormLoadTemplate,
+        method: 'get',
+        params: parameter
+    })
 }
 
 export function getAllDict(parameter) {
-  return request({
-    url: formApi.getAllDict,
-    method: 'get',
-    params: parameter
-  })
+    return request({
+        url: formApi.getAllDict,
+        method: 'get',
+        params: parameter
+    })
 }
 
 export function getStCommentByInstanceid(parameter) {
-  return request({
-    url: formApi.getStCommentByInstanceid,
-    method: 'get',
-    params: parameter
-  })
+    return request({
+        url: formApi.getStCommentByInstanceid,
+        method: 'get',
+        params: parameter
+    })
 }
 
 export function getFormTemplate(parameter) {
-  return request({
-    url: formApi.getFormTemplate,
-    method: 'get',
-    params: parameter
-  })
+    return request({
+        url: formApi.getFormTemplate,
+        method: 'get',
+        params: parameter
+    })
 }
 
-export function getDynamicData(parameter) {
-  return request({
-    url: formApi.getDynamicData,
-    method: 'get',
-    params: parameter
-  })
+export function getDynamicData(data) {
+    return request({
+        url: formApi.getDynamicData,
+        method: 'post',
+        data
+    })
 }
 
 export function buildParticipantTree(parameter) {
-  return request({
-    url: formApi.buildParticipantTree,
-    method: 'get',
-    params: parameter
-  })
+    return request({
+        url: formApi.buildParticipantTree,
+        method: 'get',
+        params: parameter
+    })
 }
 
 export function istFormTemplate(parameter) {
-  return request({
-    url: formApi.istFormTemplate,
-    method: 'post',
-    data: parameter,
-    headers: {
-      'Content-Type': 'application/json;charset=UTF-8'
-    }
-  })
+    return request({
+        url: formApi.istFormTemplate,
+        method: 'post',
+        data: parameter,
+        headers: {
+            'Content-Type': 'application/json;charset=UTF-8'
+        }
+    })
 }
 
 
 export function updateJsStr(data) {
-  return request({
-    url: formApi.updateJsStr,
-    method: 'post',
-    data
-  })
+    return request({
+        url: formApi.updateJsStr,
+        method: 'post',
+        data
+    })
 }
 
 export function getJsStr(params) {
-  return request({
-    url: formApi.getJsStr,
-    method: 'get',
-    params
-  })
+    return request({
+        url: formApi.getJsStr,
+        method: 'get',
+        params
+    })
 }
 
 export function getAccess(url, parameter) {
-  return request({
-    url: url,
-    method: 'get',
-    params: parameter
-  })
+    return request({
+        url: url,
+        method: 'get',
+        params: parameter
+    })
 
 }
 
 export function postAccess(url, data) {
-  return request({
-    url: url,
-    method: 'post',
-    data
-  })
+    return request({
+        url: url,
+        method: 'post',
+        data
+    })
 }
 
 
 export function getCodeHistory(parameter) {
-  return request({
-    url: formApi.getCodeHistory,
-    method: 'get',
-    params: parameter
-  })
+    return request({
+        url: formApi.getCodeHistory,
+        method: 'get',
+        params: parameter
+    })
 }

+ 128 - 0
src/api/flow/settingApi.js

@@ -47,6 +47,134 @@ const settingApi = {
     getFlowException: '/setting/engine/getFlowException',
     updateFlowException: '/setting/engine/updateFlowException',
 
+    getTaskAgent: '/setting/engine/getTaskAgent',
+    insertTaskAgent: '/setting/engine/insertTaskAgent',
+    delTaskAgent: '/setting/engine/delTaskAgent',
+    retrieveFlow: '/setting/engine/retrieveFlow',
+
+    getCommonComment: '/setting/engine/getCommonComment',
+    insertCommonComment: '/setting/engine/insertCommonComment',
+    deleteCommonComment: '/setting/engine/deleteCommonComment',
+    userSelectTree: '/setting/engine/userSelectList',
+    searchDeptUserList: '/setting/engine/userSelectList/search',
+    flowUpgradeVersion: '/setting/engine/flowUpgradeVersion',
+    getTaskCategory: '/bpm/sub/getTaskCategory',
+
+    getPrint: '/setting/engine/getPrint',
+    updatePrint: '/setting/engine/updatePrint',
+
+}
+export function getPrint(searchInfo) {
+    return request({
+        url: settingApi.getPrint,
+        method: 'get',
+        params: searchInfo,
+    });
+}
+export function updatePrint(data) {
+    return request({
+        url: settingApi.updatePrint,
+        method: 'post',
+        data
+    })
+}
+export function getTaskCategory(searchInfo) {
+    return request({
+        url: settingApi.getTaskCategory,
+        method: 'get',
+        params: searchInfo,
+    });
+}
+export function flowUpgradeVersion(searchInfo) {
+    return request({
+        url: settingApi.flowUpgradeVersion,
+        method: 'get',
+        params: searchInfo,
+    });
+}
+// 按部门分组人员树
+export function userSelectTree(deptId, expandLevel, dicStoredFlag) {
+    if (deptId == null || deptId === '') {
+        deptId = '0';
+    }
+    if (expandLevel == null || expandLevel === '') {
+        expandLevel = 1;
+    }
+    if (dicStoredFlag == null || dicStoredFlag == undefined) {
+        dicStoredFlag = ''
+    }
+    const parameter = {}
+    parameter.id = deptId
+    parameter.level = expandLevel
+    parameter.dicStoredFlag = dicStoredFlag
+
+    return request({
+        url: settingApi.userSelectTree,
+        params: parameter,
+        method: 'get',
+
+    });
+}
+
+// 按部门树检索用户
+export function searchDeptUserList(searchInfo) {
+    return request({
+        url: settingApi.searchDeptUserList,
+        method: 'get',
+        params: searchInfo,
+    });
+}
+export function deleteCommonComment(data) {
+    return request({
+        url: settingApi.deleteCommonComment,
+        method: 'post',
+        data
+    })
+}
+export function insertCommonComment(data) {
+    return request({
+        url: settingApi.insertCommonComment,
+        method: 'post',
+        data
+    })
+}
+export function getCommonComment() {
+    return request({
+        url: settingApi.getCommonComment,
+        method: 'get',
+    })
+}
+
+
+
+export function retrieveFlow(parameter) {
+    return request({
+        url: settingApi.retrieveFlow,
+        method: 'post',
+        params: parameter
+    })
+}
+export function getTaskAgent(parameter) {
+    return request({
+        url: settingApi.getTaskAgent,
+        method: 'get',
+        params: parameter
+    })
+}
+
+export function insertTaskAgent(data) {
+    return request({
+        url: settingApi.insertTaskAgent,
+        method: 'post',
+        data
+    })
+}
+export function delTaskAgent(data) {
+    return request({
+        url: settingApi.delTaskAgent,
+        method: 'post',
+        data
+    })
 }
 export function getFlowException(parameter) {
     return request({

BIN
src/assets/back.png


BIN
src/assets/bgAdmin.png


BIN
src/assets/file.png


BIN
src/assets/flow/title.png


File diff suppressed because it is too large
+ 0 - 0
src/assets/flow/title.svg


BIN
src/assets/form/ask.png


BIN
src/assets/form/filling1.png


BIN
src/assets/form/filling2.png


BIN
src/assets/form/finish.png


+ 1 - 0
src/assets/form/finish.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1746604893883" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3019" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64z m-40 644l-80-78.4-160-156.8 80-78.4 160 156.8L712 316l80 78.4L472 708z" fill="#4279f9" p-id="3020"></path></svg>

BIN
src/assets/form/going.png


+ 1 - 0
src/assets/form/right.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1746609643399" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6483" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M881 562H81c-27.6 0-50-22.4-50-50s22.4-50 50-50h800c27.6 0 50 22.4 50 50s-22.4 50-50 50z" fill="#4279f9" p-id="6484"></path><path d="M907.6 540.7L695.5 328.6c-19.5-19.5-19.5-51.2 0-70.7s51.2-19.5 70.7 0L978.4 470c19.5 19.5 19.5 51.2 0 70.7-19.6 19.6-51.2 19.6-70.8 0z" fill="#4279f9" p-id="6485"></path><path d="M695.5 695.4l212.1-212.1c19.5-19.5 51.2-19.5 70.7 0s19.5 51.2 0 70.7L766.2 766.1c-19.5 19.5-51.2 19.5-70.7 0s-19.5-51.2 0-70.7z" fill="#4279f9" p-id="6486"></path></svg>

BIN
src/assets/form/state_0.png


BIN
src/assets/form/state_1.png


BIN
src/assets/form/state_2.png


BIN
src/assets/form/state_3.png


BIN
src/assets/form/state_4.png


BIN
src/assets/form/state_5.png


BIN
src/assets/place.png


BIN
src/assets/switch.png


BIN
src/assets/time.png


+ 24 - 0
src/components/BsUi/MonacoEditor/EditorWorker.vue

@@ -0,0 +1,24 @@
+<template></template>
+
+<script setup>
+import * as monaco from 'monaco-editor'
+import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
+import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
+import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
+import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
+
+self.MonacoEnvironment = {
+  getWorker(workerId, label) {
+    if (label === 'json') {
+      return new jsonWorker()
+    }
+    if (label === 'typescript' || label === 'javascript') {
+      return new tsWorker()
+    }
+    if (label === 'html') {
+      return new htmlWorker()
+    }
+    return new editorWorker()
+  }
+}
+</script>

+ 122 - 0
src/components/BsUi/MonacoEditor/index.hook.js

@@ -0,0 +1,122 @@
+import {ref, onBeforeUnmount, nextTick} from 'vue'
+import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'
+
+export const useMonacoEditor = (language = 'javascript') => {
+
+    let monacoEditor = null
+    let initReadOnly = false
+    const el = ref(null)
+    let javascriptModel = null
+    // 格式化
+    const onFormatDoc = async () => {
+        await monacoEditor?.getAction('monacoEditor.action.formatDocument')?.run()
+    }
+
+    // 更新
+    const updateVal = (val) => {
+        nextTick(async () => {
+            monacoEditor?.setValue(val)
+            initReadOnly && monacoEditor?.updateOptions({readOnly: false})
+            await onFormatDoc()
+            initReadOnly && monacoEditor?.updateOptions({readOnly: true})
+        })
+    }
+
+    const addContent = (content) => {
+        const position = monacoEditor.getPosition();
+
+        javascriptModel.applyEdits([{
+            range: {
+                startColumn: position.column,
+                startLineNumber: position.lineNumber,
+                endColumn: position.column,
+                endLineNumber: position.lineNumber,
+            },
+            text: content
+        }], true)
+    }
+    // 创建实例
+    const createEditor = (editorOption) => {
+        if (!el.value) return
+        javascriptModel = monaco.editor.createModel('', language)
+        initReadOnly = !!editorOption.readOnly
+
+        // 创建
+        monacoEditor = monaco.editor.create(el.value, {
+            model: javascriptModel,
+            // 是否启用预览图
+            multiCursorModifier: 'ctrlCmd',
+            // 滚动条
+            scrollbar: {
+                verticalScrollbarSize: 8,
+                horizontalScrollbarSize: 8
+            },
+            // 行号
+            tabSize: 8,
+            //字体大小
+            fontSize: 20,
+            // 字体
+            fontFamily: 'Consolas, "Courier New", monospace',
+            acceptSuggestionOnCommitCharacter: true, // 接受关于提交字符的建议
+            acceptSuggestionOnEnter: 'on', // 接受输入建议 "on" | "off" | "smart"
+            accessibilityPageSize: 10, // 辅助功能页面大小 Number 说明:控制编辑器中可由屏幕阅读器读出的行数。警告:这对大于默认值的数字具有性能含义。
+            accessibilitySupport: 'on', // 辅助功能支持 控制编辑器是否应在为屏幕阅读器优化的模式下运行。
+            autoClosingBrackets: 'always', // 是否自动添加结束括号(包括中括号) "always" | "languageDefined" | "beforeWhitespace" | "never"
+            autoClosingDelete: 'always', // 是否自动删除结束括号(包括中括号) "always" | "never" | "auto"
+            autoClosingOvertype: 'always', // 是否关闭改写 即使用insert模式时是覆盖后面的文字还是不覆盖后面的文字 "always" | "never" | "auto"
+            autoClosingQuotes: 'always', // 是否自动添加结束的单引号 双引号 "always" | "languageDefined" | "beforeWhitespace" | "never"
+            autoIndent: 'None', // 控制编辑器在用户键入、粘贴、移动或缩进行时是否应自动调整缩进
+            automaticLayout: true, // 自动布局
+            codeLens: false, // 是否显示codeLens 通过 CodeLens,你可以在专注于工作的同时了解代码所发生的情况 – 而无需离开编辑器。 可以查找代码引用、代码更改、关联的 Bug、工作项、代码评审和单元测试。
+            codeLensFontFamily: 'Consolas', // codeLens的字体样式
+            codeLensFontSize: 14, // codeLens的字体大小
+            colorDecorators: true, // 呈现内联色彩装饰器和颜色选择器
+            comments: {
+                ignoreEmptyLines: true, // 插入行注释时忽略空行。默认为真。
+                insertSpace: true, // 在行注释标记之后和块注释标记内插入一个空格。默认为真。
+            }, // 注释配置
+            contextmenu: true, // 启用上下文菜单
+            columnSelection: false, // 启用列编辑 按下shift键位然后按↑↓键位可以实现列选择 然后实现列编辑
+            autoSurround: 'never', // 是否应自动环绕选择
+            copyWithSyntaxHighlighting: true, // 是否应将语法突出显示复制到剪贴板中 即 当你复制到word中是否保持文字高亮颜色
+            cursorBlinking: 'Solid', // 光标动画样式
+            cursorSmoothCaretAnimation: true, // 是否启用光标平滑插入动画  当你在快速输入文字的时候 光标是直接平滑的移动还是直接"闪现"到当前文字所处位置
+            cursorStyle: 'UnderlineThin', // "Block"|"BlockOutline"|"Line"|"LineThin"|"Underline"|"UnderlineThin" 光标样式
+            cursorSurroundingLines: 0, // 光标环绕行数 当文字输入超过屏幕时 可以看见右侧滚动条中光标所处位置是在滚动条中间还是顶部还是底部 即光标环绕行数 环绕行数越大 光标在滚动条中位置越居中
+            cursorSurroundingLinesStyle: 'all', // "default" | "all" 光标环绕样式
+            cursorWidth: 2, // <=25 光标宽度
+            minimap: {
+                enabled: true, // 是否启用预览图
+            }, // 预览图设置
+            folding: true, // 是否启用代码折叠
+            links: true, // 是否点击链接
+            overviewRulerBorder: false, // 是否应围绕概览标尺绘制边框
+            renderLineHighlight: 'gutter', // 当前行突出显示方式
+            roundedSelection: false, // 选区是否有圆角
+            scrollBeyondLastLine: false, // 设置编辑器是否可以滚动到最后一行之后
+            readOnly: false, // 是否为只读模式
+            theme: 'vs', // vs, hc-black, or vs-dark
+            lineNumbers: 'on', // 显示行号
+            lineHeight: 1.6,
+            letterSpacing: '0.3px', // 字间距
+            fontLigatures: true,
+            ...editorOption
+        })
+
+        return monacoEditor
+    }
+
+    // 卸载
+    onBeforeUnmount(() => {
+        if (monacoEditor) monacoEditor.dispose()
+    })
+
+    return {
+        el,
+        updateVal,
+        getEditor: () => monacoEditor,
+        createEditor,
+        onFormatDoc,
+        addContent
+    }
+}

+ 4 - 0
src/components/BsUi/MonacoEditor/index.js

@@ -0,0 +1,4 @@
+import MonacoEditor from './index.vue';
+import EditorWorker from './EditorWorker.vue';
+
+export { MonacoEditor, EditorWorker };

+ 98 - 0
src/components/BsUi/MonacoEditor/index.vue

@@ -0,0 +1,98 @@
+<template>
+  <div ref="el" class="go-editor-area" :style="{ width, height }"></div>
+  <EditorWorker></EditorWorker>
+</template>
+
+<script setup>
+import {onMounted, watch, ref, toRaw} from 'vue'
+import {useMonacoEditor} from './index.hook'
+import {EditorWorker} from './index'
+
+const props = defineProps({
+  width: {
+    type: String,
+    default: '100%'
+  },
+  height: {
+    type: String,
+    default: '90vh'
+  },
+  language: {
+    type: String,
+    default: 'typescript'
+  },
+  preComment: {
+    type: String,
+    default: ''
+  },
+  modelValue: {
+    type: String,
+    default: ''
+  },
+  editorOptions: {
+    type: Object,
+    default: () => ({})
+  }
+})
+
+const emits = defineEmits(['blur', 'update:modelValue'])
+
+const {el, updateVal, getEditor, createEditor,addContent} = useMonacoEditor(props.language)
+
+const updateMonacoVal = (_val) => {
+  const {modelValue, preComment} = props
+  const val = preComment ? `${preComment}\n${_val || modelValue}` : _val || modelValue;
+  updateVal(val)
+}
+
+onMounted(() => {
+  const monacoEditor = createEditor(props.editorOptions)
+  monacoEditor.onDidChangeModelContent(() => {
+    emits('update:modelValue', monacoEditor.getValue())
+  })
+  monacoEditor.onDidBlurEditorText(() => {
+    emits('blur')
+  })
+  updateMonacoVal()
+
+  emits('asyncGetEditor', getEditor())
+})
+defineExpose({
+  addContent
+})
+
+watch(
+    () => props.modelValue,
+    (val) => {
+      val !== getEditor()?.getValue() && updateMonacoVal(val)
+    }
+)
+</script>
+
+<style lang="less" scoped>
+.go-editor-area {
+  position: relative;
+  border-radius: 4px;
+  overflow: hidden;
+  padding: 5px;
+  padding-left: 0;
+  box-sizing: border-box;
+  background-color: rgba(0, 0, 0, 0);
+@include deep() {
+  .margin,
+  .monaco-editor,
+  .inputarea.ime-input {
+    background-color: rgba(0, 0, 0, 0);
+  }
+
+  .monaco-editor-background {
+    background-color: rgba(0, 0, 0, 0);
+  @include fetch-bg-color('filter-color-shallow');
+  }
+
+  .margin {
+  @include fetch-bg-color('filter-color-shallow');
+  }
+}
+}
+</style>

+ 24 - 0
src/components/MonacoEditor/EditorWorker.vue

@@ -0,0 +1,24 @@
+<template></template>
+
+<script setup>
+import * as monaco from 'monaco-editor'
+import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
+import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
+import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
+import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
+
+self.MonacoEnvironment = {
+  getWorker(workerId, label) {
+    if (label === 'json') {
+      return new jsonWorker()
+    }
+    if (label === 'typescript' || label === 'javascript') {
+      return new tsWorker()
+    }
+    if (label === 'html') {
+      return new htmlWorker()
+    }
+    return new editorWorker()
+  }
+}
+</script>

+ 6 - 5
src/components/MonacoEditor/index.hook.js

@@ -52,11 +52,11 @@ export const useMonacoEditor = (language = 'javascript') => {
                 horizontalScrollbarSize: 8
             },
             // 行号
-            tabSize: 4,
+            tabSize: 8,
             //字体大小
-            fontSize: 16,
+            fontSize: 20,
             // 字体
-            fontFamily: 'Fira Code, \'JetBrains Mono\', monospace',
+            fontFamily: 'Consolas, "Courier New", monospace',
             acceptSuggestionOnCommitCharacter: true, // 接受关于提交字符的建议
             acceptSuggestionOnEnter: 'on', // 接受输入建议 "on" | "off" | "smart"
             accessibilityPageSize: 10, // 辅助功能页面大小 Number 说明:控制编辑器中可由屏幕阅读器读出的行数。警告:这对大于默认值的数字具有性能含义。
@@ -68,7 +68,7 @@ export const useMonacoEditor = (language = 'javascript') => {
             autoIndent: 'None', // 控制编辑器在用户键入、粘贴、移动或缩进行时是否应自动调整缩进
             automaticLayout: true, // 自动布局
             codeLens: false, // 是否显示codeLens 通过 CodeLens,你可以在专注于工作的同时了解代码所发生的情况 – 而无需离开编辑器。 可以查找代码引用、代码更改、关联的 Bug、工作项、代码评审和单元测试。
-            codeLensFontFamily: '', // codeLens的字体样式
+            codeLensFontFamily: 'Consolas', // codeLens的字体样式
             codeLensFontSize: 14, // codeLens的字体大小
             colorDecorators: true, // 呈现内联色彩装饰器和颜色选择器
             comments: {
@@ -97,7 +97,8 @@ export const useMonacoEditor = (language = 'javascript') => {
             readOnly: false, // 是否为只读模式
             theme: 'vs', // vs, hc-black, or vs-dark
             lineNumbers: 'on', // 显示行号
-            lineHeight: 1.5,
+            lineHeight: 1.6,
+            letterSpacing: '0.3px', // 字间距
             fontLigatures: true,
             ...editorOption
         })

+ 4 - 0
src/components/MonacoEditor/index.js

@@ -0,0 +1,4 @@
+import MonacoEditor from './index.vue';
+import EditorWorker from './EditorWorker.vue';
+
+export { MonacoEditor, EditorWorker };

+ 98 - 0
src/components/MonacoEditor/index.vue

@@ -0,0 +1,98 @@
+<template>
+  <div ref="el" class="go-editor-area" :style="{ width, height }"></div>
+  <EditorWorker></EditorWorker>
+</template>
+
+<script setup>
+import {onMounted, watch, ref, toRaw} from 'vue'
+import {useMonacoEditor} from './index.hook'
+import {EditorWorker} from './index'
+
+const props = defineProps({
+  width: {
+    type: String,
+    default: '100%'
+  },
+  height: {
+    type: String,
+    default: '90vh'
+  },
+  language: {
+    type: String,
+    default: 'typescript'
+  },
+  preComment: {
+    type: String,
+    default: ''
+  },
+  modelValue: {
+    type: String,
+    default: ''
+  },
+  editorOptions: {
+    type: Object,
+    default: () => ({})
+  }
+})
+
+const emits = defineEmits(['blur', 'update:modelValue'])
+
+const {el, updateVal, getEditor, createEditor,addContent} = useMonacoEditor(props.language)
+
+const updateMonacoVal = (_val) => {
+  const {modelValue, preComment} = props
+  const val = preComment ? `${preComment}\n${_val || modelValue}` : _val || modelValue;
+  updateVal(val)
+}
+
+onMounted(() => {
+  const monacoEditor = createEditor(props.editorOptions)
+  monacoEditor.onDidChangeModelContent(() => {
+    emits('update:modelValue', monacoEditor.getValue())
+  })
+  monacoEditor.onDidBlurEditorText(() => {
+    emits('blur')
+  })
+  updateMonacoVal()
+
+  emits('asyncGetEditor', getEditor())
+})
+defineExpose({
+  addContent
+})
+
+watch(
+    () => props.modelValue,
+    (val) => {
+      val !== getEditor()?.getValue() && updateMonacoVal(val)
+    }
+)
+</script>
+
+<style lang="less" scoped>
+.go-editor-area {
+  position: relative;
+  border-radius: 4px;
+  overflow: hidden;
+  padding: 5px;
+  padding-left: 0;
+  box-sizing: border-box;
+  background-color: rgba(0, 0, 0, 0);
+@include deep() {
+  .margin,
+  .monaco-editor,
+  .inputarea.ime-input {
+    background-color: rgba(0, 0, 0, 0);
+  }
+
+  .monaco-editor-background {
+    background-color: rgba(0, 0, 0, 0);
+  @include fetch-bg-color('filter-color-shallow');
+  }
+
+  .margin {
+  @include fetch-bg-color('filter-color-shallow');
+  }
+}
+}
+</style>

+ 18 - 0
src/components/StFormModal/index.js

@@ -0,0 +1,18 @@
+import { createVNode, render, createApp } from 'vue';
+import FormModelCom from '/@/components/StFormModal/stFormModal.vue';
+import Antd from 'ant-design-vue';
+// 创建一个dom容器节点div
+const parentNode = document.createElement('div'); 
+// 容器追加到body中
+document.body.appendChild(parentNode);
+//以上我们定义好了承装该组件虚拟dom的容器,接下来要把该组件添加到容器中,并导出为一个函数
+
+export const useFormModal = (options) => { 
+    const app = createApp(FormModelCom, { ...options });
+    app.use(Antd);
+    const vm = app.mount(parentNode);
+    vm.show.call(vm)
+}
+
+
+

+ 124 - 0
src/components/StFormModal/stFormModal.vue

@@ -0,0 +1,124 @@
+<template>
+  <Modal :open="visible" :destroyOnClose="true" @ok="submit" @cancel="close" width="800px">
+    <!-- 使用 #title 指定标题插槽 -->
+    <template #title>
+      <span>{{ title }}</span>
+    </template>
+    <!-- 使用 #default 指定默认插槽 -->
+    <template #default>
+      <StFormBuild ref="STB" :value="jsonData" :disabled="disabled" :config="config" @change="handleChange" />
+    </template>
+    <!-- 使用 #footer 指定 footer 插槽 -->
+    <template #footer>
+      <Button @click="handleCancel">取消</Button>
+      <Button @click="handleRest">重置</Button>
+      <Button type="primary" @click="submit">提交</Button>
+    </template>
+  </Modal>
+</template>
+
+<script>
+  import { defineComponent, ref, reactive, onMounted, nextTick } from 'vue';
+  import { Modal, Button, message } from 'ant-design-vue';
+  import StFormBuild from '/@/views/flow/stFormDesign/packages/StFormBuild/index.vue';
+
+  export default defineComponent({
+    name: 'MyModal',
+    components: { Modal, Button, StFormBuild },
+    setup(ctx, { attrs }) {
+
+      const visible = ref(false); // 控制模态框显示
+      const formData = ref({}); // 表单数据
+      const jsonData =  ref({});
+      const disabled = ref(false); // 禁用状态
+      const config = ref({}); // 表单配置
+      const title = ref('表单设计');
+      const STB = ref(null); // 引用 StFormBuild 组件
+
+      const vm = ref(null);
+
+      const handleChange = (value, key) => {
+        attrs.onChange && attrs.onChange(value, key, vm.value);
+      };
+      const handleCancel = () => {
+        // 处理取消按钮点击
+        close();
+      };
+
+      const handleRest = () => {
+        // 处理重置按钮点击
+        STB.value.reset();
+      };
+
+      const submit = async () => {
+        await STB.value.getData().then((form) => {
+          // 获取数据成功
+          formData.value = form;
+        });
+        const beforeSubmitType = attrs.beforeSubmit ? attrs.beforeSubmit(vm.value) : false;
+        if (typeof beforeSubmitType == 'boolean' && !beforeSubmitType) {
+          return false;
+        }
+        if (typeof beforeSubmitType == 'undefined') {
+          attrs.onSubmit(formData.value);
+          close();
+        } else {
+          attrs.beforeSubmit(vm.value).then(
+            (res) => {
+              STB.value.getData()
+                .then((form) => {
+                  // 获取数据成功
+                  attrs.onSubmit(form);
+                  close();
+                })
+                .catch((err) => {
+                  message.error('校验失败');
+                });
+            },
+            (err) => {
+              message.error(err);
+            }
+          );
+        }
+      };
+
+      onMounted(() => {
+        // 组件挂载后的操作
+      });
+
+      const show = function () {
+        vm.value = this;
+        visible.value = true;
+        jsonData.value = attrs.jsonData;
+        disabled.value = attrs.disabled;
+        config.value = attrs.config;
+        title.value = attrs.title;
+        nextTick(() => {
+          attrs.onMounted && attrs.onMounted(this, STB.value);
+        });
+      };
+
+      const close = () => {
+        visible.value = false;
+      };
+
+      return {
+        visible,
+        formData,
+        disabled,
+        config,
+        STB,
+        handleChange,
+        handleCancel,
+        handleRest,
+        submit,
+        show,
+        close,
+        title,
+        jsonData
+      };
+    },
+  });
+</script>
+
+<style scoped lang="less"></style>

+ 93 - 0
src/components/StMobile/StPopup/index.jsx

@@ -0,0 +1,93 @@
+import { defineComponent, createApp, ref, reactive, h, shallowRef } from 'vue';
+import { Popup, DatePicker, TimePicker, Picker } from 'vant';
+
+const otherComponentMap = {
+  date: DatePicker,
+  time: TimePicker,
+  select: Picker,
+};
+
+const StPopup = defineComponent({
+  name: 'StModal',
+  setup(ctx, { attrs, expose }) {
+    const visible = ref(false);
+    const show = () => {
+      visible.value = true;
+      attrs.onShow && attrs.onShow();
+    };
+
+    const close = () => {
+      visible.value = false;
+      attrs.onClose && attrs.onClose();
+    };
+
+    expose({ show, close });
+
+    return {
+      visible,
+      show,
+      close,
+      PopupOptions: shallowRef(attrs.popupOpt || {}),
+      OtherOptions: shallowRef(attrs.otherOpt || {}),
+    };
+  },
+  render(ctx) {
+    const { visible, close, PopupOptions, OtherOptions } = ctx;
+    return (
+      <Popup show={visible} {...PopupOptions} onClose={close}>
+        {(() => {
+
+           Object.keys(OtherOptions).forEach(key => {
+            if(typeof OtherOptions[key] === 'function') {
+              OtherOptions[key] = OtherOptions[key].bind(ctx)
+            }
+          });
+
+          return h(otherComponentMap[OtherOptions.type], {
+            ...OtherOptions,
+          });
+        })()}
+      </Popup>
+    );
+  },
+});
+
+export function useStPopup(popupOpt, otherOpt) {
+  const divBox = document.createElement('div');
+  document.body.appendChild(divBox);
+  const options = {
+    popupOpt,
+    otherOpt,
+  };
+  options.onShow = () => {
+    console.log('onShow', stPopupRef);
+  };
+
+  options.onClose = () => {
+    // 使用 setTimeout 确保 onClose 逻辑在动画完成后执行
+    setTimeout(() => {
+      if (divBox && document.body.contains(divBox)) {
+        // 从 DOM 中移除
+        document.body.removeChild(divBox);
+      }
+    }, 500);
+  };
+
+  const stPopupRef = createApp(StPopup, { ...options }).mount(divBox);
+
+  stPopupRef.show();
+
+  return {
+    stPopupRef,
+  };
+}
+
+const install = (app) => {
+  app.config.globalProperties.$useStPopup = useStPopup;
+};
+
+export default {
+  install,
+  useStPopup,
+  StPopup,
+};

+ 0 - 0
src/components/StModal/direct.js


+ 54 - 0
src/components/StModal/index.jsx

@@ -0,0 +1,54 @@
+import { defineComponent, createApp, ref, reactive } from 'vue';
+import { Modal } from 'ant-design-vue';
+
+const StModal = defineComponent({
+  name: 'StModal',
+  setup(ctx, { attrs, expose }) {
+    const visible = ref(false);
+    console.log("attrs", attrs);
+    const show = () => {
+      visible.value = true;
+    };
+    const close = () => {
+      visible.value = false;
+    };
+    expose({  show, close });
+    return {
+      visible,
+      show,
+      close,
+      modalOptions: reactive(attrs)
+    };
+  },
+  render(ctx) {
+    const { visible, close, modalOptions } = ctx;
+    return (
+      <Modal open={visible} {  ...modalOptions } onCancel={() => close()}>
+        11
+      </Modal>
+    );
+  },
+});
+// export default StModal;
+const divBox = document.createElement('div');
+document.body.appendChild(divBox);
+export function useStModal(options) {
+  const stModalRef = createApp(StModal, { ...options }).mount(divBox);
+  const showStModal = () => {
+    stModalRef.show();
+  };
+  return {
+    showStModal,
+    stModalRef,
+  };
+}
+
+const install = (app) => {
+  app.config.globalProperties.$useStModal = useStModal;
+}
+
+export default {
+  install,
+  useStModal,
+  StModal
+}

+ 26 - 0
src/components/StModal/index.vue

@@ -0,0 +1,26 @@
+<template>
+    <Modal title="2222" :open="visible">
+        111111
+    </Modal>
+</template>
+<script setup>
+import { Modal } from 'ant-design-vue';
+import { ref } from 'vue';
+const props = defineProps({})
+
+const visible = ref(false)
+
+const show = () => {
+    visible.value = true
+}
+
+const close = () => {
+    visible.value = false
+}
+
+defineExpose({
+    show,
+    close
+})
+
+</script>

+ 16 - 0
src/components/StModal/install.js

@@ -0,0 +1,16 @@
+import ModalCom from './index.vue';
+import { defineComponent, createApp, ref, reactive, h, render } from 'vue';
+
+function renderBox(options) {
+  const divBox = document.createElement('div');
+  const vnode = h(ModalCom, { ...options });
+  render(vnode, divBox);
+  document.body.appendChild(divBox);
+  return vnode;
+}
+
+export default function (options) {
+  const aa = renderBox(options);
+
+  console.log('aa', aa);
+}

+ 792 - 0
src/components/StSelectUser/index.vue

@@ -0,0 +1,792 @@
+<template>
+  <div>
+    <a-modal width="1000px" :open="open" :title="title" @cancel="cancel">
+      <a-row>
+        <a-col class="treeBox treeborder" :span="12">
+          <a-input-search placeholder="请输入用户信息" @search="filterNode"/>
+          <div class="personSelectTree">
+            <a-tree
+                v-if="selectModel == 'multi'&&allowSelectDepartment==false"
+                v-model:value="checkedKeys"
+                checkable
+                showIcon
+                :field-names="replaceFields"
+                :expandedKeys="expandedKeys"
+                v-model:selectedKeys="selectedKeys"
+                v-model:checkedKeys="checkedKeys"
+                :auto-expand-parent="autoExpandParent"
+                :tree-data="deptOptions"
+                :load-data="onLoadData"
+                @expand="onExpand"
+                @check="checkNode"
+            >
+              <template #icon="{ key, selected }">
+
+              </template>
+            </a-tree>
+            <a-tree
+                v-if="selectModel == 'single'||allowSelectDepartment"
+                :field-names="replaceFields"
+                :expanded-keys="expandedKeys"
+                :auto-expand-parent="autoExpandParent"
+                :selected-keys="selectedKeys"
+                :tree-data="deptOptions"
+                :load-data="onLoadData"
+                @expand="onExpand"
+                @select="selectNode"
+            >
+            </a-tree>
+          </div>
+        </a-col>
+        <a-col :span="12">
+          <div class="contentBox">
+            <div v-if="selectModel !== 'single'" :style="{ padding: '10px 20px' }">
+              已选({{ selectCount }})
+
+            </div>
+            <div>
+              <div v-if="selectModel == 'single'">
+                <div style="margin-left: 20px; margin-top: 20px" v-for="item in userdata">
+                  <span class="title-name">已选:{{ item.name }}</span>
+                  <span class="title-dept">({{ item.subtitle }})</span>
+                </div>
+              </div>
+              <a-list v-else item-layout="horizontal" :data-source="userdata" ref="editTable">
+                <template #renderItem="{ item }">
+                  <a-list-item>
+                    <template #actions>
+                      <DeleteOutlined @click="removeUser(item)"/>
+                    </template>
+                    <a-list-item-meta
+                        :description="item.name+'('+item.subtitle+')'"
+                    >
+                      <template #avatar>
+                        <UserOutlined/>
+                      </template>
+
+                    </a-list-item-meta>
+                  </a-list-item>
+                </template>
+              </a-list>
+            </div>
+          </div>
+        </a-col>
+      </a-row>
+      <template #footer>
+        <a-button @click="cancel">取消</a-button>
+        <a-button type="primary" @click="saveSelectUser">确认</a-button>
+      </template>
+    </a-modal>
+  </div>
+</template>
+<script>
+import {userSelectTree, searchDeptUserList} from '/@/api/flow/settingApi.js';
+import Sortable from 'sortablejs';
+
+export default {
+  props: {
+    title: {
+      type: String,
+      default: '人员选择',
+    },
+    // 默认值
+    defaultValue: {
+      required: false,
+      default: null,
+    },
+    // 返回数据
+    value: {
+      required: false,
+    },
+    // 单选 single ,多选 multi
+    selectModel: {
+      type: String,
+      required: false,
+      default: 'single',
+    },
+    maxSelect: {
+      type: Number,
+      required: false,
+      default: 0,
+    },
+    allowSelectDepartment: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+  },
+  data() {
+    return {
+      showValue: '',
+      oldValue: '',
+      indeterminate: false,
+      checkAll: false,
+      userCheckedList: [],
+      spinning: false,
+      delayTime: 200,
+      // allIcon,
+      sortable: undefined,
+      replaceFields: {
+        children: 'children',
+        key: 'id',
+        value: 'id',
+      },
+      open: false,
+      expandedKeys: [],
+      autoExpandParent: true,
+      checkedKeys: [],
+      selectedKeys: [], // 左侧树所有选中节点
+      deptOptions: [],
+      deptNodes: [],
+      oldDeptOptions: [], // 记录查询前数据结构
+      oldExpandedKeys: [],
+      expandSonData: [], // 异步展开节点时记录子节点
+      select: {
+        ids: '',
+        names: '',
+      }, // 最终选择用户对象
+      searchValue: '',
+      userdata: [],
+      checkedUser: [],
+      selectCount: 0,
+    };
+  },
+  components: {},
+  created() {
+    this.getTreeselect();
+  },
+  watch: {
+    checkedKeys(val) {
+    },
+    checkedUser: {
+      immediate: true,
+      handler(val) {
+        this.userCheckedList = val;
+      },
+    },
+    userdata: {
+      immediate: true,
+      handler(val) {
+        this.selectCount = val.length;
+        if (this.selectModel === 'multi' && val.length > 0 && this.sortable === undefined) {
+          this.rowDrop();
+        }
+      },
+    },
+    select: {
+      immediate: true,
+      handler(val) {
+        this.oldValue = this.select && this.select.names ? this.select.names : '';
+        this.showValue = this.oldValue;
+      },
+    },
+    value: {
+      immediate: true,
+      handler(newV) {
+        if (newV) {
+          this.select = newV;
+        } else {
+          this.select = {
+            ids: '',
+            names: '',
+          };
+        }
+      },
+    },
+  },
+  methods: {
+    removeUser(item) {
+
+      this.deletSelectUserByParentIds(item.id, item.parentIds)
+
+    },
+    getSelectUserInfo() {
+      if (this.select.ids !== undefined && this.select.ids !== 'undefined' && this.select.ids !== '') {
+        const userids = {
+          userIds: this.select.ids,
+        };
+        // getUserInfoByIds(userids).then((response) => {
+        //   this.userdata = response.data;
+        //   this.selectedKeys = [];
+        //   this.checkedKeys = [];
+        //   this.userdata.forEach((node) => {
+        //     this.selectedKeys.push(node.id);
+        //     this.checkedKeys.push(node.id);
+        //   });
+        //   // this.selectCount = this.userdata.length
+        // });
+      }
+    },
+    checkBoxOnChange(e) {
+      if (e.target.checked) {
+        this.checkedUser.push(e.target.value);
+      } else {
+        this.checkedUser = this.checkedUser.filter(function (item) {
+          return item !== e.target.value;
+        });
+      }
+    },
+    onChange(checkedList) {
+      // 右侧已选用户复选框勾选触发
+      /* this.indeterminate = !!checkedList.length && checkedList.length < this.userdata.length
+    this.checkAll = checkedList.length === this.userdata.length */
+      this.indeterminate = !!this.checkedUser.length && this.checkedUser.length < this.userdata.length;
+      this.checkAll = this.checkedUser.length === this.userdata.length;
+    },
+    onCheckAllChange(e) {
+      Object.assign(this, {
+        userCheckedList: e.target.checked ? this.selectedKeys : [],
+        checkedUser: e.target.checked ? this.selectedKeys : [],
+        indeterminate: false,
+        checkAll: e.target.checked,
+      });
+    },
+    deleteSelectUser() {
+      // 右侧已选用户上方删除按钮,全选和勾选删除
+      if (this.checkAll) {
+        // 全选状态下直接清空数据
+        this.userdata.forEach((record) => {
+          this.checkedKeys = this.checkedKeys.filter(function (item) {
+            return ('/' + record.parentIds + '/').indexOf('/' + item.toString() + '/') < 0;
+          });
+        });
+        this.userdata = [];
+        this.userCheckedList = [];
+        this.checkAll = false;
+        // this.selectCount = this.userdata.length
+      } else {
+        // 移除已勾选数据
+        this.userCheckedList.forEach((checkItem) => {
+          this.userdata.some((record, i) => {
+            if (record.id === checkItem) {
+              this.userdata.splice(i, 1);
+              this.checkedKeys = this.checkedKeys.filter(function (item) {
+                console.log()
+                return ('/' + record.parentIds + '/').indexOf('/' + item.toString() + '/') < 0;
+              });
+            }
+          });
+        });
+        // this.selectCount = this.userdata.length
+      }
+    },
+    deletSelectUserByParentIds(id, parentIds) {
+      // 右侧已选用户悬浮删除按钮删除方法
+      this.checkedKeys = this.checkedKeys.filter(function (item) {
+        return item.toString() != id
+      });
+      this.selectedKeys = this.selectedKeys.filter(function (item) {
+        return item.toString() != id
+      });
+      this.userdata.some((record, i) => {
+        if (record.id === id) {
+          this.userdata.splice(i, 1);
+          this.checkedKeys = this.checkedKeys.filter(function (item) {
+            return ('/' + record.parentIds + '/').indexOf('/' + item.toString() + '/') < 0;
+          });
+        }
+      });
+      this.selectCount = this.userdata.length
+    },
+    resetSelectUserInfo() {
+      this.checkedKeys = [];
+      this.userdata = [];
+      // this.selectCount = this.userdata.length
+    },
+    /** 查询部门下拉树结构 */
+    getTreeselect() {
+      let dicStoredFlag = localStorage.getItem("dicStoredFlag")
+      const treeSelectUserData = localStorage.getItem("treeSelectUserData")
+      if (treeSelectUserData == null || treeSelectUserData == '' || treeSelectUserData == undefined
+          || treeSelectUserData == '[]') {
+        dicStoredFlag = ''//这个字段为空,后台就要重新组织数据返回
+      }
+      userSelectTree('0', 5, dicStoredFlag).then((response) => {
+        if (response.data == null || response.data.length == 0) {
+          this.deptOptions = JSON.parse(treeSelectUserData);
+        } else {
+          this.deptOptions = response.data;
+          localStorage.setItem("treeSelectUserData", JSON.stringify(this.deptOptions))
+        }
+        this.getExpandedKeys(this.deptOptions, 3);
+        Object.assign(this, {
+          expandedKeys: this.expandedKeys,
+          searchValue: '',
+          autoExpandParent: true,
+        });
+      });
+    },
+    getExpandedKeys(nodes, expandLevel) {
+      // 递归展开指定层级
+      if (expandLevel > 1) {
+        if (nodes == null) {
+          return []
+        }
+        // 最后一层不展开
+        nodes.forEach((node) => {
+          this.expandedKeys.push(node.id);
+          expandLevel = expandLevel - 1;
+          return this.getExpandedKeys(node.children, expandLevel);
+        });
+      }
+    },
+    getExpandedAllKeys(nodes) {
+      // 递归展开所有层
+      if (nodes == null || nodes == undefined || nodes.length === 0) {
+        return [];
+      }
+
+      // 最后一层不展开
+      nodes.forEach((node) => {
+        this.deptNodes.push(node.id);
+        return this.getExpandedAllKeys(node.children);
+      });
+    },
+    onLoadData(treeNode) {
+      // 展开节点时动态加载数据
+      return new Promise((resolve) => {
+        if (treeNode.dataRef.children) {
+          resolve();
+          return;
+        }
+        this.spinning = !this.spinning;
+        userSelectTree(treeNode.dataRef.id, 1, '').then((response) => {
+          treeNode.dataRef.children = response.data;
+          this.expandSonData = response.data;
+          if (treeNode.checked) {
+            // 当前节点已经是选中状态时异步加载子节点的话,需要将子节点人员选择到已选人员列表
+            this.setSelectUserInfoByNodes(response.data);
+          } else {
+            this.checkedKeys = this.selectedKeys;
+          }
+          this.spinning = !this.spinning;
+          resolve();
+        });
+      });
+    },
+    showSelectUser() {
+      this.getSelectUserInfo();
+      this.open = true;
+      this.resetSelectUserInfo();
+    },
+    filterNode(value, e) {
+      if (this.oldDeptOptions.length === 0) {
+        this.oldDeptOptions = this.deptOptions;
+        this.oldExpandedKeys = this.expandedKeys;
+      }
+      if (value.trim() === '') {
+        // 触发父页面设置树数据
+        this.deptOptions = this.oldDeptOptions;
+        Object.assign(this, {
+          expandedKeys: this.oldExpandedKeys,
+          searchValue: value,
+          autoExpandParent: true,
+        });
+      } else {
+        const searchInfo = {
+          searchText: value,
+        };
+        searchDeptUserList(searchInfo).then((response) => {
+          // 触发父页面设置树数据
+          this.deptOptions = response.data;
+          this.getExpandedAllKeys(response.data);
+          Object.assign(this, {
+            expandedKeys: this.deptNodes,
+            searchValue: value,
+            autoExpandParent: true,
+          });
+          this.deptNodes = [];
+        });
+      }
+    },
+    callback(key) {
+      console.log(key);
+    },
+    cancel(e) {
+      this.$emit('close');
+      this.open = false;
+    },
+    onExpand(expandedKeys) {
+      this.expandedKeys = expandedKeys;
+      this.autoExpandParent = false;
+    },
+    onCheck(checkedKeys) {
+      console.log('onCheck', checkedKeys);
+      this.checkedKeys = checkedKeys;
+    },
+    selectNode(selectedKeys, e) {
+      if (this.allowSelectDepartment == false) {
+        // 单选树节点触发
+        var nodeData = e.node.dataRef;
+        const deptType = nodeData.attributes.deptType;
+        if (deptType === 'user') {
+          this.selectedKeys = [];
+          this.userdata = [];
+          const id = nodeData.id;
+          const code = nodeData.code;
+          const name = nodeData.title;
+          const parentIds = nodeData.parentIds;
+          const subtitle = nodeData.attributes.subtitle;
+          const selectUser = {
+            id: id,
+            code: code,
+            name: name,
+            subtitle: subtitle,
+            parentIds: parentIds,
+            icon: 'user',
+          };
+          this.selectedKeys.push(id);
+          this.userdata.push(selectUser);
+          this.selectCount = this.userdata.length
+        } else {
+          this.$message.warning('请选择用户添加');
+        }
+      } else {
+        var nodeData = e.node.dataRef;
+        const id = nodeData.id;
+        if (this.selectedKeys.includes(id) == false) {
+          const code = nodeData.code;
+          const name = nodeData.title;
+          const parentIds = nodeData.parentIds;
+          const subtitle = nodeData.attributes.subtitle;
+          const deptType = nodeData.attributes.deptType;
+          const selectUser = {
+            id: id,
+            code: code,
+            name: name,
+            subtitle: subtitle,
+            parentIds: parentIds,
+            deptType: deptType
+          };
+          this.selectedKeys.push(id);
+          this.userdata.push(selectUser);
+        }
+
+      }
+    },
+    checkNode(selectedKeys, e) {
+      if (e.checked && !e.node.isLeaf) {
+        const children = e.node.dataRef.children;
+        if (children === null) {
+          // 选中非叶子节点时需要加载子节点并选中
+          Promise.all([this.onLoadData(e.node)]).then((res) => {
+            const id = e.node.dataRef.id;
+            this.expandedKeys.push(id);
+            // 选中子节点
+            if (this.expandSonData.length > 0) {
+              this.setSelectUserInfoByNodes(this.expandSonData);
+              this.expandSonData = [];
+            }
+          });
+        } else {
+          this.setSelectUserInfo(e.checkedNodes);
+        }
+      } else if (e.checked && e.node.isLeaf) {
+        this.setSelectUserInfo(e.checkedNodes);
+      } else {
+        // 移除当前选中节点及其子节点数据
+        this.removeSelectUserByUserTree(e.node, 'node');
+      }
+    },
+    unique(arr) {
+      // 数据去重
+      const res = new Map();
+      return arr.filter((arr) => !res.has(arr.id) && res.set(arr.id, 1));
+    },
+    removeSelectUserByUserTree(node, dataSource) {
+      let id = '';
+      let childrens = null;
+      if (dataSource === 'node') {
+        id = node.dataRef.id;
+        childrens = node.dataRef.children;
+      } else {
+        id = node.id;
+        childrens = node.children;
+      }
+      this.selectedKeys = this.selectedKeys.filter(function (item) {
+        return item !== id;
+      });
+      this.userdata = this.userdata.filter(function (item) {
+        return item.id !== id;
+      });
+
+      if (childrens !== null) {
+        childrens.forEach((childrenNode) => {
+          this.removeSelectUserByUserTree(childrenNode, 'children');
+        });
+      }
+      // this.selectCount = this.userdata.length
+    },
+    setSelectUserInfo(checkedNodes) {
+      // 过滤掉部门数据
+      checkedNodes.forEach((node) => {
+        const name = node.title;
+        const id = node.id;
+        const code = node.code;
+        const parentIds = node.parentIds;
+        const deptType = node.attributes.deptType;
+        const subtitle = node.attributes.subtitle;
+        this.setSelectEdUserInfo(id, code, name, subtitle, deptType, parentIds);
+      });
+    },
+    setSelectUserInfoByNodes(checkedNodes) {
+
+      // 专门处理直接通过勾选父节点展开数据后的选人操作
+      checkedNodes.forEach((node) => {
+        const name = node.title;
+        const id = node.id;
+        const code = node.code;
+        const parentIds = node.parentIds;
+        const deptType = node.attributes.deptType;
+        const subtitle = node.attributes.subtitle;
+        this.setSelectEdUserInfo(id, code, name, subtitle, deptType, parentIds);
+      });
+    },
+    setSelectEdUserInfo(id, code, name, subtitle, deptType, parentIds) {
+      this.selectedKeys.push(id);
+      this.checkedKeys.push(id);
+      if (deptType === 'user') {
+        const selectUser = {
+          id: id,
+          name: name,
+          code: code,
+          subtitle: subtitle,
+          parentIds: parentIds,
+          icon: 'user',
+        };
+        this.userdata.push(selectUser);
+        this.userdata = this.unique(this.userdata);
+        this.selectCount = this.userdata.length
+      }
+    },
+    saveSelectUser() {
+      // 保存选中数据
+      let ids = '';
+      let names = '';
+      let codes = '';
+      let types = '';
+      if (this.userdata.length > this.maxSelect && this.maxSelect !== 0) {
+        this.$message.warning(`已设置最多选择${this.maxSelect}人!`);
+        return;
+      }
+      this.userdata.forEach(function (node, index) {
+        if (index > 0) {
+          ids += ';';
+          codes += ';';
+          names += ';';
+          types += ';';
+        }
+        ids = ids + node.id;
+        names = names + node.name;
+        codes = codes + node.code;
+        types = types + node.deptType;
+      });
+      this.showValue = names;
+      const result = {
+        ids,
+        codes,
+        names,
+        types,
+      };
+      this.$emit('change', result);
+      this.$nextTick(() => {
+        this.select = result;
+        // 双向绑定
+        this.$emit('input', result);
+        this.$emit('callBack', result);
+      });
+      this.open = false;
+    },
+    /**
+     * 行拖拽事件
+     */
+    rowDrop() {
+      const that = this;
+      this.$nextTick(() => {
+        const xGrid = this.$refs.editTable;
+        const el = xGrid.$el.querySelector('.ant-list-items');
+        this.sortable = Sortable.create(el, {
+          handle: '.ant-list-item',
+          animation: 300,
+          delay: 100,
+          chosenClass: 'select-list-color', // 被选中项的css 类名
+          dragClass: 'drag-list-color', // 正在被拖拽中的css类名
+          onEnd: ({newIndex, oldIndex}) => {
+            const currRow = that.userdata.splice(oldIndex, 1)[0];
+            that.userdata.splice(newIndex, 0, currRow);
+          },
+          onUpdate(event) {
+            const newIndex = event.newIndex;
+            const oldIndex = event.oldIndex;
+            const $body = el;
+            const $tr = $body.children[newIndex];
+            const $oldTr = $body.children[oldIndex];
+            // 先删除移动的节点
+            $body.removeChild($tr);
+            // 再插入移动的节点到原有节点,还原了移动的操作
+            if (newIndex > oldIndex) {
+              $body.insertBefore($tr, $oldTr);
+            } else {
+              $body.insertBefore($tr, $oldTr.nextSibling);
+            }
+          },
+        });
+      });
+    },
+  },
+};
+</script>
+<style lang="less" scoped>
+.ant-tree-checkbox-disabled {
+}
+
+body .ant-tree li .ant-tree-node-content-wrapper .depIcon {
+  color: #666666;
+  font-size: 20px;
+}
+
+.personSelect .ant-modal-body {
+  padding: 0;
+}
+
+.personSelectTree {
+  height: 325px;
+  overflow: auto;
+  padding-left: 15px;
+}
+
+.personSelect {
+  .ant-tabs-bar {
+    margin: 0;
+  }
+
+  .treeborder {
+    border-right: 1px solid #e8e8e8;
+  }
+
+  .treeBox {
+    padding: 0;
+
+    .ant-input-search {
+      width: 100%;
+      padding: 10px 15px 5px;
+    }
+
+    .ant-input-affix-wrapper .ant-input-suffix {
+      right: 22px;
+    }
+
+    .ant-tree-checkbox {
+      padding: 8px 0 0;
+    }
+
+    .ant-tree-checkbox-checked::after {
+      border: none;
+    }
+
+    .ant-tree li .ant-tree-node-content-wrapper {
+      width: calc(100% - 40px);
+    }
+  }
+
+  .contentBox {
+    padding: 0;
+
+    .ant-list-items {
+      margin: 0 10px;
+    }
+
+    .ant-avatar {
+      width: 30px;
+      height: 30px;
+      line-height: 30px;
+      background: #47b5e6;
+    }
+
+    .ant-checkbox-wrapper {
+      font-size: 12px;
+    }
+
+    .ant-checkbox-group {
+      display: block;
+      height: 330px;
+      overflow: auto;
+    }
+
+    .ant-list-item-meta-avatar {
+      margin-right: 0;
+    }
+
+    .ant-list-item-meta-title {
+      line-height: 30px;
+      font-size: 12px;
+      margin-bottom: 0px;
+    }
+
+    .ant-avatar.ant-avatar-icon {
+      margin: 0 10px;
+    }
+
+    .ant-list-item-action > li {
+      padding: 0 5px;
+    }
+
+    .ant-list-split .ant-list-item {
+      border-bottom: 0;
+      padding: 5px 10px;
+
+      .ant-list-item-action {
+        display: none;
+        margin-left: 0;
+      }
+
+      .title-name {
+        color: #323232;
+        margin-right: 5px;
+      }
+
+      .title-dept {
+        color: #a5a5a5;
+      }
+    }
+
+    .ant-list-item:hover {
+      background: #f0f6ff;
+      cursor: move;
+
+      .ant-list-item-action {
+        display: block;
+      }
+    }
+
+    .select-list-color {
+      box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
+      z-index: 9999;
+    }
+
+    .ant-list-item-action .ant-list-item-action-split {
+      width: 0;
+    }
+  }
+
+  .ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab {
+    margin-right: 0px;
+    margin-top: 0px;
+    height: 40px;
+    line-height: 40px;
+    border: 0;
+    border-right: 1px solid #fff;
+    background: #f3f3f3;
+    border-radius: 0;
+  }
+
+  .ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab-active {
+    background: #fff;
+  }
+
+  .ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-nav-wrap {
+    padding: 0 10px;
+    background: #f3f3f3;
+  }
+}
+</style>

+ 129 - 0
src/components/common/common-table/components/search.vue

@@ -0,0 +1,129 @@
+<template>
+  <a-form class="smart-query-form">
+    <a-row class="smart-query-form-row">
+      <template v-for="(item, index) in searchConfig" :key="index">
+        <a-form-item :label="item.label" class="smart-query-form-item">
+          <template v-if="item.type === 'input'">
+            <a-input :style="itemStyle(item)"
+              v-bind="{ allowClear: true, placeholder: `请输入${item.label}`, ...item.attrs }"
+              v-model:value="queryForm[item.field]" @pressEnter="onSearch" />
+          </template>
+          <template v-if="item.type === 'inputNumber'">
+            <a-input-number :style="itemStyle(item)" v-bind="{ placeholder: `请输入${item.label}`, ...item.attrs }"
+              v-model:value="queryForm[item.field]" @pressEnter="onSearch" />
+          </template>
+          <template v-if="item.type === 'radioGroup'">
+            <a-radio-group :style="itemStyle(item)" v-bind="{ buttonStyle: 'solid', ...item.attrs }"
+              v-model:value="queryForm[item.field]">
+              <a-radio-button :value="undefined">全部</a-radio-button>
+              <a-radio-button v-for="(option, opIdx) in item.options" :key="opIdx" :value="option.value">{{ option.label
+              }}</a-radio-button>
+            </a-radio-group>
+          </template>
+          <template v-if="item.type === 'select'">
+            <a-select :style="itemStyle(item)"
+                      option-filter-prop="label"
+                      :options="item.options"
+              v-bind="{ allowClear: true, placeholder: `请选择${item.label}`, ...item.attrs }"
+              v-model:value="queryForm[item.field]">
+
+            </a-select>
+          </template>
+          <template v-if="item.type === 'dictSelect'">
+            <DictSelect :style="itemStyle(item)"
+              v-bind="{ placeholder: `请选择${item.label}`, maxTagCount: 1, ...item.attrs }"
+              v-model:value="queryForm[item.field]" />
+          </template>
+          <template v-if="item.type === 'date'">
+            <a-date-picker :style="itemStyle(item)"
+              v-bind="{ placeholder: `请选择${item.label}`, valueFormat: 'YYYY-MM-DD', ...item.attrs }"
+              v-model:value="queryForm[item.field]" />
+          </template>
+          <template v-if="item.type === 'dateRange'">
+            <a-range-picker :style="itemStyle(item)" v-bind="{ valueFormat: 'YYYY-MM-DD', ...item.attrs }"
+              v-model:value="queryForm[item.field]" />
+          </template>
+          <template v-if="item.type === 'cascader'">
+            <a-cascader :style="itemStyle(item)" v-bind="{ placeholder: `请选择${item.label}`, ...item.attrs }"
+              v-model:value="queryForm[item.field]" />
+          </template>
+          <template v-if="item.type === 'treeSelect'">
+            <a-tree-select v-model:value="queryForm[item.field]" :treeData="isRefOptions(item.treeData)"
+              :style="itemStyle(item)" :fieldNames="item.fieldNames"
+              :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" v-bind="{ ...item.attrs }"
+              :placeholder="`请选择${item.label}`" allow-clear />
+          </template>
+        </a-form-item>
+      </template>
+
+      <a-form-item class="smart-query-form-item smart-margin-left10">
+        <a-button-group>
+          <a-button type="primary" @click="onSearch">
+            <template #icon>
+              <SearchOutlined />
+            </template>
+            查询
+          </a-button>
+          <a-button @click="onReload">
+            <template #icon>
+              <ReloadOutlined />
+            </template>
+            重置
+          </a-button>
+        </a-button-group>
+      </a-form-item>
+    </a-row>
+  </a-form>
+</template>
+
+<script setup>
+import { watchEffect, isRef, unref } from 'vue';
+import DictSelect from '/@/components/common/dict-select/index.vue';
+const props = defineProps({
+  searchConfig: {
+    type: Array,
+    default: () => [],
+  },
+  queryForm: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+
+// 动态设置自定义值
+watchEffect(() => {
+  props.searchConfig.forEach((item) => {
+    if (item.customValue !== undefined) {
+      props.queryForm[item.field] = item.customValue; // 设置自定义值
+    }
+  });
+});
+
+const emit = defineEmits(['search', 'reload']);
+
+function isRefOptions(options) {
+  return isRef(options) ? unref(options) : options
+}
+
+for (const item of props.searchConfig) {
+  if (item.options && typeof item.options === 'function') {
+    item.options = await item.options();
+  }
+}
+
+const onSearch = () => {
+  emit('search');
+};
+
+const onReload = () => {
+  emit('reload');
+};
+
+function itemStyle(item) {
+  return {
+    width: item.hasOwnProperty('width') ? item.width : '240px',
+  };
+}
+</script>
+
+<style lang="less" scoped></style>

+ 514 - 0
src/components/common/common-table/components/table.vue

@@ -0,0 +1,514 @@
+<template>
+  <div class="common-table-item table" ref="table">
+    <!-- 查询表单 -->
+    <template v-if="props.search.length > 0">
+      <Search :searchConfig="props.search" v-model:queryForm="queryForm" @search="onSearch" @reload="onReload" />
+    </template>
+    <a-card :class="{ 'common-table-tabs-item-card': props.isTabs }" size="small" :bordered="false">
+      <!-- 表格上方 -->
+      <a-row class="smart-table-btn-block">
+        <!-- 操作按钮 -->
+        <div class="smart-table-operate-block">
+          <template v-if="props.buttons.length > 0">
+            <a-button v-for="(item, index) in props.buttons" :key="index" v-bind="btnsAttrs(item)">
+              <template #icon>
+                <component :is="$antIcons[item.iconName]" />
+              </template>
+              {{ item.label }}
+            </a-button>
+          </template>
+        </div>
+        <div class="smart-table-model-block">
+          <!-- 卡片模式 -->
+          <template v-if="props.isShowCardbtn">
+            <div class="smart-card-operate-button">
+              <span class="list_tabs" v-for="(item, index) in modelList" :key="index"
+                :class="{ active: activeKey == item.key }" @click="clickTab(item.key)">
+                <PicCenterOutlined v-show="item.key == 1" />
+                <PicLeftOutlined v-show="item.key == 2" />
+                {{ item.name }}
+              </span>
+            </div>
+          </template>
+          <!-- 工具栏 -->
+          <div class="smart-table-setting-block" v-if="props.showTableOperator">
+            <TableOperator v-model="tableConfig.columns" :tableId="props.tableId" :refresh="onSearch" />
+          </div>
+        </div>
+      </a-row>
+      <!-- 自定义顶部插槽内容 -->
+      <div v-if="tableConfig.renderTopSlot" class="smart-table-top-slot" style="margin-bottom: 6px">
+        <render-top-slot />
+      </div>
+      <!-- 表格 -->
+      <div>
+        <a-table v-if="props.checked === true && props.checkedTypeRadio"
+          :rowSelection="{ type: 'radio', selectedRowKeys: state.selectedRowKeys, onChange: onSelectChange }"
+          :rowKey="props.checked_key" v-bind="{ ...tableConfig, pagination: false }">
+        </a-table>
+        <a-table v-else-if="props.checked === true && !props.checkedTypeRadio"
+          :rowSelection="{ selectedRowKeys: state.selectedRowKeys, onChange: onSelectChange }"
+          :rowKey="props.checked_key" v-bind="{ ...tableConfig, pagination: false }">
+        </a-table>
+        <div v-else>
+          <a-table v-if="activeKey == 1" v-bind="{ ...tableConfig, pagination: false }"> </a-table>
+          <CommonCard v-if="activeKey == 2" :dataSource="tableConfig.dataSource"></CommonCard>
+        </div>
+      </div>
+      <!-- 分页 -->
+      <div class="smart-query-table-page" v-if="tableConfig.pagination === true && tableConfig.dataSource.length">
+        <a-pagination showSizeChanger showQuickJumper show-less-items :pageSizeOptions="PAGE_SIZE_OPTIONS"
+          :defaultPageSize="pagination.pageSize" v-model:current="pagination.pageNum"
+          v-model:pageSize="pagination.pageSize" :total="total" @change="fetchTableData"
+          @showSizeChange="fetchTableData" :show-total="(total) => `共${total}条`" />
+      </div>
+    </a-card>
+  </div>
+</template>
+
+<script setup lang="jsx">
+import Search from './search.vue';
+import TableOperator from '/@/components/support/table-operator/index.vue';
+import { PAGE_SIZE, PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
+import { commonTableApi } from '/@/api/support/common-table';
+import { smartSentry } from '/@/lib/smart-sentry';
+import { ref, reactive, onMounted, watch, h, computed } from 'vue';
+const state = reactive({
+  selectedRowKeys: [],
+  loading: false,
+});
+const activeKey = ref(1);
+const modelList = ref([
+  {
+    key: 1,
+    name: '列表模式',
+  },
+  {
+    key: 2,
+    name: '卡片模式',
+  },
+]);
+const clickTab = (key) => {
+  activeKey.value = key;
+};
+const hasSelected = computed(() => state.selectedRowKeys.length > 0);
+const emit = defineEmits(['onSelectChange']);
+const onSelectChange = (selectedRowKeys) => {
+  state.selectedRowKeys = selectedRowKeys;
+  emit('onSelectChange', selectedRowKeys);
+};
+const props = defineProps({
+  // 是否修改card样式
+  isTabs: {
+    type: Boolean,
+    default: false,
+  },
+  showTableOperator: {
+    type: Boolean,
+    default: true,
+  },
+  url: {
+    type: String,
+    default: '',
+  },
+  checked_key: {
+    type: String,
+    default: '',
+  },
+  checked: {
+    type: Boolean,
+    default: false,
+  },
+  checkedTypeRadio: {
+    type: Boolean,
+    default: false,
+  },
+  requestMethod: {
+    type: String,
+    default: 'POST',
+  },
+  tableId: {
+    type: Number,
+    default: null,
+  },
+  tableAttrs: {
+    type: Object,
+    default: () => ({}),
+  },
+  search: {
+    type: Array,
+    default: () => [],
+  },
+  buttons: {
+    type: Array,
+    default: () => [],
+  },
+  isShowCardbtn: {
+    type: Boolean,
+    default: false,
+  },
+
+  // 表头数据
+  columns: {
+    type: Array,
+    default: () => [],
+  },
+  // 是否显示序号列
+  showIndexColumn: {
+    type: Boolean,
+    default: false,
+  },
+  beforeFetch: {
+    type: Function,
+  },
+  afterFetch: {
+    type: Function,
+  },
+  // 自定义顶部插槽渲染函数
+  renderTopSlot: {
+    type: Function,
+  },
+});
+
+// 查询表单
+const queryForm = ref({});
+
+// 分页数据
+const pagination = reactive({
+  pageNum: 1,
+  pageSize: PAGE_SIZE,
+});
+
+// 数据总数
+const total = ref(0);
+// table默认配置
+const tableConfig = reactive({
+  size: 'small',
+  dataSource: [],
+  summary: {},
+  columns: props.columns ? props.columns : props.tableAttrs.columns ? props.tableAttrs.columns : [],
+  bordered: true,
+  pagination: true,
+  loading: false,
+  scroll: { x: 'max-content' },
+  ...props.tableAttrs,
+  renderTopSlot: props.renderTopSlot, // 添加 renderTopSlot 属性
+});
+
+// 动态渲染顶部插槽内容
+const renderTopSlot = () => {
+  if (props.renderTopSlot) {
+    // 将 queryForm 和其他参数传递给 renderTopSlot 函数
+    return h(props.renderTopSlot, { data: tableConfig.summary });
+  }
+  return null;
+};
+
+const indexColumnWatch = watch(
+  () => props.showIndexColumn,
+  (newVal, oldVal) => {
+    if (newVal !== oldVal) {
+      const noIndexColumn = tableConfig.columns.every((item) => item.dataIndex !== '_index');
+
+      if (newVal === true && noIndexColumn) {
+        tableConfig.columns.unshift({
+          title: '序号',
+          dataIndex: '_index',
+          width: 60,
+          align: 'center',
+          fixed: 'left',
+        });
+      }
+
+      if (newVal === false && !noIndexColumn) {
+        tableConfig.columns = tableConfig.columns.filter((item) => item.dataIndex !== '_index');
+      }
+    }
+  },
+  { immediate: true }
+);
+
+onMounted(fetchTableData);
+// 生成模拟数据
+function generateMockData() {
+  const data = [];
+  for (let i = 0; i < 10; i++) {
+    const item = {};
+    for (let j = 0; j < props.columns.length; j++) {
+      item[props.columns[j].dataIndex] = `row${i + 1},col${j + 1}`;
+    }
+
+    if (props.showIndexColumn === true) {
+      // item._index = i + 1 + (pagination.pageNum - 1) * pagination.pageSize;
+      item._index = i + 1;
+    }
+    data.push(item);
+  }
+  return data;
+}
+
+const urlWatch = watch(
+  () => props.url,
+  (newVal, oldVal) => {
+    if (newVal !== oldVal) {
+      fetchTableData();
+    }
+  }
+);
+
+// 获取表格数据
+async function fetchTableData() {
+  try {
+    if (!props.url) {
+      tableConfig.dataSource = generateMockData();
+      total.value = tableConfig.dataSource.length;
+      return;
+    }
+
+    tableConfig.loading = true;
+
+    let params = {
+      ...queryForm.value,
+    };
+
+    // 处理日期范围选择 转为两个参数
+    for (const item of props.search) {
+      if (item.type === 'dateRange') {
+        if (params[item.field] && params[item.field].length > 0) {
+          params[`${item.field}Start`] = params[item.field][0];
+          params[`${item.field}End`] = params[item.field][1];
+          Reflect.deleteProperty(params, item.field);
+        }
+      }
+    }
+
+    let result = null;
+
+    if (tableConfig.pagination) {
+      params.pageNum = pagination.pageNum;
+      params.pageSize = pagination.pageSize;
+    }
+
+    if (props.beforeFetch) {
+      params = props.beforeFetch({ params });
+    }
+
+    const method = props.requestMethod.toUpperCase();
+    if (method === 'GET') {
+      result = await commonTableApi['queryTableListByGet'](props.url, params);
+    } else if (method === 'POST') {
+      result = await commonTableApi['queryTableListByPost'](props.url, params);
+    } else {
+      throw new Error('requestMethod is not supported');
+    }
+
+    if (props.showIndexColumn === true) {
+      result.data.list.forEach((item, index) => {
+        // item._index = index + 1 + (pagination.pageNum - 1) * pagination.pageSize;
+        item._index = index + 1;
+      });
+    }
+
+    if (props.afterFetch) {
+      tableConfig.dataSource = props.afterFetch({ data: result.data });
+    } else {
+      tableConfig.dataSource = result.data.list;
+      tableConfig.summary = result.data.summary;
+    }
+
+    total.value = result.data.total;
+  } catch (err) {
+    smartSentry.captureError(err);
+  } finally {
+    tableConfig.loading = false;
+  }
+}
+
+// 查询
+function onSearch() {
+  pagination.pageNum = 1;
+  fetchTableData();
+}
+
+// 重置
+function onReload() {
+  queryForm.value = {};
+  pagination.pageNum = 1;
+  state.selectedRowKeys = []
+  fetchTableData();
+}
+
+// 按钮disabled属性调用
+function btnsAttrs(item) {
+  return {
+    ...item.attrs,
+    disabled: item.attrs?.disabled?.(),
+  };
+}
+
+defineExpose({
+  onReload,
+  tableConfig,
+});
+</script>
+<style lang="less" scoped>
+// tableOptions为数组时,使用下面的样式
+:deep(.common-table-tabs-item-card) {
+  box-shadow: none;
+
+  .ant-card-body {
+    padding: 0;
+  }
+}
+
+.smart-table-btn-block {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  // margin-top: 10px;
+
+  .smart-card-operate-button {
+    .list_tabs {
+      display: inline-block;
+      width: 106px;
+      height: 30px;
+      line-height: 30px;
+      text-align: center;
+      color: rgb(68, 68, 79);
+      border: 1px solid #378EFF;
+      cursor: pointer;
+    }
+
+    .active {
+      display: inline-block;
+      width: 106px;
+      height: 30px;
+      line-height: 30px;
+      text-align: center;
+      background-color: rgb(43, 105, 248);
+      color: #fff;
+      border: 1px solid #378EFF;
+      // border-radius:
+    }
+
+    .list_tabs:nth-child(1) {
+      border-radius: 4px 0 0 4px;
+    }
+
+    .list_tabs:nth-child(2) {
+      border-radius: 0 4px 4px 0;
+    }
+  }
+}
+
+.smart-table-model-block {
+  display: flex;
+  align-items: center;
+}
+
+.table {
+  .smart-table-top-slot {
+    font-size: calc(var(--fitWidthRatio) * 16px);
+  }
+
+  :deep(.ant-btn-primary),
+  :deep(.ant-btn-default) {
+    display: flex;
+    align-items: center;
+    padding: calc(var(--fitWidthRatio) * 5px) calc(var(--fitWidthRatio) * 8px) calc(var(--fitWidthRatio) * 6px) calc(var(--fitWidthRatio) * 8px);
+    // height: calc(var(--fitWidthRatio) * 35px);
+  }
+
+  :deep(.ant-btn-primary > span),
+  :deep(.ant-btn-default > span),
+  :deep(.ant-select-selection-item) {
+    font-size: calc(var(--fitWidthRatio) * 16px);
+  }
+
+  :deep(.ant-select-selector) {
+    height: calc(var(--fitWidthRatio) * 35px);
+    padding: 0 calc(var(--fitWidthRatio) * 10px);
+  }
+
+  :deep(.ant-select-selection-placeholder) {
+    display: flex;
+    align-items: center;
+    // line-height: calc(var(--fitWidthRatio) * 32px);
+  }
+
+  :deep(.ant-input-affix-wrapper > input),
+  :deep(.ant-select-selection-placeholder) {
+    font-size: calc(var(--fitWidthRatio) * 16px);
+  }
+
+  :deep(.ant-table-cell-fix-right-first::after),
+  :deep(.ant-table-cell-fix-right-last::after) {
+    transform: translateX(-90%);
+  }
+
+  :deep(.ant-form-item-label > label) {
+    font-size: calc(var(--fitWidthRatio) * 16px);
+  }
+
+  :deep(.ant-tabs-nav) {
+    margin: 0;
+  }
+
+  :deep(.ant-table-cell-fix-right) {
+    box-shadow: calc(var(--fitWidthRatio) * -2px) 0 calc(var(--fitWidthRatio) * 8px) 0px rgba(5, 5, 5, 0.06);
+  }
+
+  :deep(.ant-table-cell) {
+    font-size: calc(var(--fitWidthRatio) * 16px);
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+
+    //表格内标签适配
+    .ant-tag {
+      font-size: calc(var(--fitWidthRatio) * 16px);
+      line-height: calc(var(--fitWidthRatio) * 20px);
+      padding-inline: calc(var(--fitWidthRatio) * 7px);
+      margin-inline-end: calc(var(--fitWidthRatio) * 8px);
+    }
+
+    //表格内link按钮适配
+    .ant-btn-link {
+      font-size: calc(var(--fitWidthRatio) * 16px);
+    }
+
+    //表格内描述适配
+    .ant-typography {
+      font-size: calc(var(--fitWidthRatio) * 16px);
+    }
+  }
+
+  :deep(.smart-query-form-item) {
+    margin-right: calc(var(--fitWidthRatio) * 30px);
+  }
+
+  :deep(.ant-tabs-nav-list) {
+    padding: 0 calc(var(--fitWidthRatio) * 10px);
+  }
+
+  :deep(.ant-pagination) {
+    height: calc(var(--fitWidthRatio) * 32px);
+    min-width: calc(var(--fitWidthRatio) * 32px);
+    font-size: calc(var(--fitWidthRatio) * 16px);
+  }
+
+  :deep(.ant-pagination > li) {
+    height: calc(var(--fitWidthRatio) * 32px);
+    min-width: calc(var(--fitWidthRatio) * 32px);
+    line-height: calc(var(--fitWidthRatio) * 32px);
+  }
+
+  :deep(.ant-select-item) {
+    font-size: calc(var(--fitWidthRatio) * 16px);
+  }
+
+  :deep(.ant-select-selection-item) {
+    line-height: calc(var(--fitWidthRatio) * 30px);
+  }
+}
+</style>

+ 130 - 0
src/components/common/common-table/example.js

@@ -0,0 +1,130 @@
+export default {
+  /**
+   * @description: tab名称 仅在多个tab时需要
+   * @type {string}
+   */
+  name: undefined,
+
+  /**
+   * @description: 请求地址 为空展示mock数据
+   * @type {string}
+   */
+  url: undefined,
+
+  /**
+   * @description: 请求方法 GET | POST 不区分大小写
+   * @type {string}
+   * @default 'GET'
+   */
+  requestMethod: 'GET',
+
+  /**
+   * @description: 表格id 用于存储表格状态 顺序调整列宽等
+   * @type {string}
+   */
+  tableId: undefined,
+
+  /**
+   * @description: 搜索条件
+   * @type {array}
+   * @example
+   * {
+   * label: '名称',
+   * field: '字段',
+   * type: 'select',
+   * customValue: '自定义值', // 自定义值字段
+   * options: async () => { // 数组 | 方法(可异步)
+   *  const { data } = await api.xxx();
+   *  return [
+   *      ...data.map((item) => ({ label: item.名称, value: item.值 })),
+   *  ];
+   * },
+   * attrs: {
+   *  ...select 属性
+   * }
+   */
+  search: [],
+
+  /**
+   * @description: 表格按钮
+   * @type {array}
+   * @example
+   * {
+   *  label: '按钮名称',
+   *  iconName: 'antd图标名称',
+   *  attrs: {
+   *      type: 'primary',
+   *      onClick: () => {},
+   *      disabled: () => {},
+   *  }
+   * },
+   */
+  buttons: [],
+
+  /**
+   * @description: 表格列
+   * @type {array}
+   * @example
+   * {
+   *  title: '标题',
+   *  dataIndex: 'dataIndex',
+   *  width: 200,
+   *  ellipsis: true,
+   *  customRender: ({ text, record, index }) => { // <script lang="jsx"></script>
+   *      return <a>{text}</a>;
+   *  }
+   */
+  columns: [],
+
+  /**
+   * @description: 表格属性
+   * @type {object}
+   * @example
+   * {
+   *  ...antd table 属性
+   * }
+   */
+  tableAttrs: {},
+
+  /**
+   * @description: 是否显示序号列
+   * @type {boolean}
+   * @default false
+   */
+  showIndexColumn: false,
+
+  /**
+   * @description: 是否显示操作图标
+   * @type {boolean}
+   * @default true
+   */
+  showTableOperator: true,
+
+  /**
+   * @description: 请求前处理
+   * @type {Function}
+   * @param {object} params 请求参数
+   * @return {object} 处理后的请求参数 配置后必须返回数据
+   * @example
+   * beforeFetch({ params }) {
+   *  return params;
+   * }
+   */
+  beforeFetch({ params }) {
+    return params;
+  },
+
+  /**
+   * @description: 请求后处理
+   * @type {Function}
+   * @param {object} data 请求返回数据
+   * @return {object} 处理后的数据 配置后必须返回数据
+   * @example
+   * afterFetch({ data }) {
+   *  return data;
+   * }
+   */
+  afterFetch({ data }) {
+    return data;
+  },
+};

+ 135 - 0
src/components/common/common-table/index.vue

@@ -0,0 +1,135 @@
+<!--
+  * 公共组件  表格
+  *
+  * @Author:    DCCloud
+  * @Date:      2024-09-26 10:02:20
+-->
+<template>
+  <div ref="table" class="table">
+    <template v-if="Array.isArray(props.tableOptions)">
+      <!-- <a-card size="small" :bordered="false" class="cardTable"> -->
+      <a-tabs v-model:activeKey="activeKey" destroyInactiveTabPane @change="toRouter" :type="props.tabsStyle">
+        <a-tab-pane v-for="(item, index) in props.tableOptions" :key="index" :tab="item.name || `未命名${index + 1}`">
+          <Table v-bind="item" ref="tableTabsRef" @onSelectChange="onSelectChange" />
+        </a-tab-pane>
+        <template v-for="(item, index) in tabsUseSlots" :key="item" #[item]>
+          <slot :name="`tabs-${item}`" v-bind="{ activeKey }" />
+        </template>
+      </a-tabs>
+      <!-- </a-card> -->
+    </template>
+    <template v-else>
+      <Table v-bind="props.tableOptions" ref="tableRef" @onSelectChange="onSelectChange" />
+    </template>
+  </div>
+</template>
+<script setup>
+import { onMounted, onUnmounted, ref, useSlots } from 'vue';
+import Table from './components/table.vue';
+import { useRouter } from 'vue-router';
+const router = useRouter();
+const emit = defineEmits(['onSelectChange']);
+const onSelectChange = (selectedRowKeys) => {
+  emit('onSelectChange', selectedRowKeys);
+};
+const props = defineProps({
+  activeKey: {
+    type: Number,
+    default: 0
+  },
+  tabsStyle: {
+    type: String,
+    default: ''
+  },
+  tableOptions: {
+    type: [Array, Object],
+    default: () => ({
+      name: undefined,
+      url: undefined,
+      checked: false,
+      // requestMethod: 'POST',
+      tableId: undefined,
+      search: [],
+      buttons: [],
+      columns: [],
+      tableAttrs: {},
+      showIndexColumn: false,
+      showTableOperator: true,
+      beforeFetch({ params }) {
+        return params;
+      },
+      afterFetch({ data }) {
+        return data;
+      },
+      toRouter: '',
+    }),
+  },
+});
+const activeKey = ref(props.activeKey);
+// console.log(activeKey, props.activeKey)
+const slots = useSlots();
+const tabsUseSlots = Object.keys(slots)
+  .filter((key) => key.startsWith('tabs-'))
+  .map((key) => key.replace('tabs-', ''));
+
+const tableRef = ref(null);
+const tableTabsRef = ref([]);
+
+// 给外部页面使用的刷新方法
+function reload() {
+  if (Array.isArray(props.tableOptions)) {
+    // destroyInactiveTabPane 为 true 时 始终是第一个tab
+    tableTabsRef.value[0].onReload();
+    // destroyInactiveTabPane 为 false 时
+    // tableTabsRef.value[activeKey.value].onReload();
+  } else {
+    tableRef.value.onReload();
+  }
+}
+//------------- 添加响应式 -------------//
+onMounted(reduceRatio)
+const table = ref()
+function reduceRatio() {
+  handleResize();
+  window.addEventListener('resize', handleResize);
+}
+function handleResize() {
+  const ratio = window.innerWidth / 1920;
+  table.value.style.setProperty('--fitWidthRatio', ratio);
+}
+function toRouter(key) {
+  if (props.tableOptions[key].toRouter) {
+    router.push(props.tableOptions[key].toRouter);
+  }
+}
+onUnmounted(() => {
+  window.removeEventListener('resize', handleResize);
+})
+defineExpose({
+  reload,
+  tableConfig: () => tableRef.value.tableConfig,
+  activeKey
+});
+</script>
+
+<style lang="less" scoped>
+.table {
+  background-color: #fff;
+  border-radius: calc(var(--fitWidthRatio) * 8px);
+
+  :deep(.ant-tabs-tab-btn) {
+    font-family: PingFang SC;
+    font-weight: 500;
+    font-size: calc(var(--fitWidthRatio) * 16px);
+    color: #000000;
+  }
+
+  :deep(.ant-tabs-tab) {
+    padding: calc(var(--fitWidthRatio) * 16px) calc(var(--fitWidthRatio) * 10px);
+  }
+
+  :deep(.ant-tabs-nav-list) {
+    padding: 0 calc(var(--fitWidthRatio) * 10px);
+  }
+}
+</style>

+ 14 - 2
src/main.js

@@ -38,8 +38,15 @@ import 'vxe-table/lib/style.css'
 import '/@/components/BsUi/Render/index.js'
 
 import BsUi from "/@/components/BsUi"
-
-
+// 导入样式
+import "/@/views/flow/stFormDesign/styles/form-design.less";
+// 导出本地iconfont
+import "/@/views/flow/stFormDesign/static/icons/iconfont";
+// 引入表单设计器
+import { StFormDesign, StFormMobileDesign } from "/@/views/flow/stFormDesign/packages/index.js";
+import StFormBuild from "/@/views/flow/stFormDesign/packages/StFormBuild/index.js";
+// 引入common-table
+import CommonTable from './components/common/common-table/index.vue';
 /*
  * -------------------- ※ 着重 解释说明下main.js的初始化逻辑 begin ※ --------------------
  *
@@ -77,6 +84,11 @@ function initVue() {
   let vueApp = createApp(App);
   let app = vueApp.use(router).use(store).use(i18n).use(Antd).use(smartEnumPlugin, constantsInfo).use(privilegePlugin).use(JsonViewer);
   app.use(VxeUI).use(VxeUITable);
+  app.use(StFormDesign);
+  app.use(StFormMobileDesign);
+
+  app.use(StFormBuild);
+  app.component('CommonTable', CommonTable);
   app.use(VantUi);
   //注入权限
   app.directive('privilege', {

+ 27 - 1
src/router/flow/index.js

@@ -6,6 +6,12 @@ export const flowRouters = [
       title: '待办任务',
       hideInMenu: true,
     },
+    children: [
+      {
+        path: 'formWork',
+        component: () => import('/src/views/flow/stFormWork/formWork.vue'),
+      },
+    ],
   },
   {
     path: '/flow',
@@ -13,6 +19,26 @@ export const flowRouters = [
     meta: {
       title: '查看流程',
       hideInMenu: true,
-    }
+    },
+    children: [
+      {
+        path: 'flowView',
+        component: () => import('/src/views/flow/stFlowView/flowView.vue'),
+      },
+    ],
+  },
+  {
+    path: '/printPreview',
+    name: 'printPreview',
+    meta: {
+      title: '报销审批表',
+      hideInMenu: true,
+    },
+    children: [
+      {
+        path: 'cover',
+        component: () => import('/src/views/flowCustomComponents/flowPrintPreview/cover.vue'),
+      },
+    ],
   },
 ];

+ 222 - 0
src/views/flow/aigc/chat/index.vue

@@ -0,0 +1,222 @@
+<template xmlns="http://www.w3.org/1999/html">
+  <div class="chat-container">
+    <a-layout class="layout">
+      <a-layout-content class="chat-content">
+        <div class="chat-box">
+          <!-- 消息列表 -->
+          <div class="chat-message" v-for="(message, index) in messages" :key="index">
+            <!-- AI 消息 -->
+            <div v-if="message.sender === 'ai'" class="message-container ai-message">
+
+              <img src="/@/assets/loginNew/logo.svg" alt="AI Avatar" class="avatar"/>
+              <div class="message-bubble">
+                <div v-html="renderMarkdown(message.text)"></div>
+                <span class="timestamp">{{ message.timestamp }}</span>
+                <span>参考文档:</span>
+                <br>
+                <div v-for="(source,index) in message.sources">
+                  <a >{{ source.title }}</a>
+                </div>
+              </div>
+            </div>
+            <!-- 用户消息 -->
+            <div v-else class="message-container user-message">
+              <div class="message-bubble">
+                <p>{{ message.text }}</p>
+                <span class="timestamp">{{ message.timestamp }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </a-layout-content>
+      <a-layout-footer class="chat-footer">
+        <a-spin size="large" :spinning="spinning">
+          <a-textarea
+              class="chat-input"
+              v-model:value="chatValue"
+              placeholder="输入你的问题..."
+              :auto-size="{ minRows: 2, maxRows: 4 }"
+          />
+          <a-button class="send-button" type="primary" @click="chatClick">发送</a-button>
+        </a-spin>
+      </a-layout-footer>
+    </a-layout>
+<!--    <a-modal v-model:open="documentDescribeOpen" title="文档描述">-->
+<!--      <div v-html="renderMarkdown(documentDescribe.text )"></div>-->
+<!--    </a-modal>-->
+  </div>
+</template>
+
+<script setup>
+import {ref} from 'vue';
+import {chat} from "/src/api/flow/aigcApi.js";
+import {marked} from 'marked'; // 引入 marked 库
+// Markdown 渲染方法
+const renderMarkdown = (markdownText) => {
+  return marked(markdownText); // 将 Markdown 转换为 HTML
+};
+
+const chatValue = ref('');
+const messages = ref([]); // 存储所有消息
+const spinning = ref(false);
+const documentDescribe = ref('')
+const documentDescribeOpen = ref(false);
+const documentDescribeClick = (source) => {
+  documentDescribeOpen.value = true
+  documentDescribe.value = source
+}
+// 获取当前时间
+const getCurrentTime = () => {
+  const now = new Date();
+  return `${now.getHours()}:${String(now.getMinutes()).padStart(2, '0')}`;
+};
+
+const chatClick = async () => {
+  if (!chatValue.value.trim()) return; // 如果输入框为空,则不发送
+
+  // 将用户输入的消息添加到消息列表
+  messages.value.push({sender: 'user', text: chatValue.value, timestamp: getCurrentTime()});
+
+  const send = {
+    message: chatValue.value,
+    slug: slug.value,
+    sessionId: chatSessionValue.value,
+  };
+  spinning.value = true;
+  await chat(send).then(
+      (res) => {
+        if (res.data !== null) {
+          const data = JSON.parse(res.data);
+          spinning.value = false;
+          console.log(data.sources,'data.sources')
+          // 将AI回复的消息添加到消息列表
+          messages.value.push({
+            sender: 'ai',
+            text: data.textResponse,
+            sources: data.sources.filter((item, index, self) => {
+              return self.findIndex(t => t.title === item.title) === index;
+            }),
+            timestamp: getCurrentTime()
+          });
+          chatSessionValue.value = data.chatId + "";
+        } else {
+          spinning.value = false;
+          messages.value.push({sender: 'ai', text: res.message, timestamp: getCurrentTime()});
+        }
+      },
+      (err) => {
+        spinning.value = false;
+      }
+  );
+
+  // 清空输入框
+  chatValue.value = '';
+};
+
+const slug = ref('my-node');
+const chatSessionValue = ref('');
+</script>
+
+<style scoped>
+.chat-container {
+  max-width: 900px;
+  margin: 0 auto;
+  padding: 20px;
+  background-color: #f5f5f5;
+  border-radius: 10px;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+}
+
+.layout {
+  background-color: #fff;
+  border-radius: 10px;
+  overflow: hidden;
+}
+
+.chat-content {
+  padding: 20px;
+  height: 500px;
+  overflow-y: auto;
+  background-color: #f9f9f9;
+}
+
+.chat-box {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+}
+
+.chat-message {
+}
+
+.message-container {
+  display: flex;
+  align-items: flex-end;
+  max-width: 99%;
+}
+
+.ai-message {
+  justify-content: flex-start;
+}
+
+.user-message {
+  justify-content: flex-end;
+}
+
+.message-bubble {
+  max-width: 70%;
+  padding: 10px 15px;
+  border-radius: 15px;
+  word-wrap: break-word;
+  position: relative;
+}
+
+.ai-message .message-bubble {
+  background-color: #e1f5fe;
+  margin-left: 10px;
+}
+
+.user-message .message-bubble {
+  background-color: #d1c4e9;
+  margin-right: 10px;
+}
+
+.timestamp {
+  display: block;
+  font-size: 12px;
+  color: #666;
+  margin-top: 5px;
+  text-align: right;
+}
+
+.chat-footer {
+  display: flex;
+  gap: 10px;
+  padding: 10px;
+  background-color: #fff;
+  border-top: 1px solid #e0e0e0;
+}
+
+.chat-input {
+  flex: 1;
+  border-radius: 20px;
+  padding: 10px;
+  border: 1px solid #e0e0e0;
+}
+
+.send-button {
+  border-radius: 20px;
+  padding: 0 20px;
+}
+
+.ai-message {
+  align-items: flex-start; /* 对齐方式改为起始对齐 */
+}
+
+.avatar {
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  object-fit: cover;
+}
+</style>

+ 145 - 0
src/views/flow/aigc/knowledge/index.vue

@@ -0,0 +1,145 @@
+<template>
+  <div>
+    <a-spin size="large" :spinning="spinning">
+      <a-layout>
+        <a-upload
+            v-model:file-list="fileList"
+            name="file"
+            :action="getAction"
+            :headers="headers"
+            @change="handleChange"
+        >
+          <a-button>
+            <upload-outlined></upload-outlined>
+            上传文档
+          </a-button>
+        </a-upload>
+        <a-layout>
+          <a-table :columns="columns" :data-source="data">
+            <template #headerCell="{ column }">
+              <template v-if="column.key === 'title'">
+                <a-input-search
+                    style="width:300px"
+                    v-model:value="searchValue"
+                    placeholder="文档名称"
+                    enter-button
+                    @search="onSearch"
+                />
+              </template>
+            </template>
+            <template #bodyCell="{ column, record }">
+              <template v-if="column.key === 'title'">
+                <a :href="record.url">
+                  {{ record.title }}
+                </a>
+              </template>
+              <template v-if="column.key === 'action'">
+                <a-button danger block size="small" @click="deleteDocumentClick(record)">
+                  <template #icon>
+                    <DeleteOutlined/>
+                  </template>
+                </a-button>
+              </template>
+            </template>
+          </a-table>
+        </a-layout>
+
+      </a-layout>
+    </a-spin>
+
+
+  </div>
+
+</template>
+<script setup>
+import {ref, onMounted} from 'vue';
+import {message, Modal} from 'ant-design-vue';
+import {localRead} from "/src/utils/local-util.js";
+import LocalStorageKeyConst from "/src/constants/local-storage-key-const.js";
+import {documents, deleteDocument} from "/src/api/flow/aigcApi.js"
+
+
+onMounted(() => {
+  getDocuments();
+})
+const getDocuments = () => {
+  documents({"title": searchValue.value}).then((res) => {
+        data.value = res.data
+      }
+  )
+}
+const searchValue = ref('')
+const onSearch = () => {
+  getDocuments();
+}
+const deleteDocumentClick = (record) => {
+
+  Modal.confirm({
+    title: '确认删除文档?',
+    onOk() {
+      deleteDocument({location: record.location}).then((res) => {
+            getDocuments();
+            message.info(`删除文档成功`);
+          }
+      )
+    },
+    onCancel() {
+      console.log('Cancel');
+    },
+    class: 'test',
+  });
+
+
+}
+
+const columns = [
+  {
+    name: '文档名称',
+    dataIndex: 'title',
+    key: 'title',
+  },
+  {
+    title: '上传时间',
+    dataIndex: 'published',
+    key: 'published',
+  },
+  {
+    title: '上传人',
+    dataIndex: 'publisher',
+    key: 'publisher',
+  },
+  {
+    title: '',
+    dataIndex: 'action',
+    key: 'action',
+  },
+];
+const data = ref([]);
+const spinning = ref(false);
+const slug = ref('my-node')
+const getAction = () => {
+  return import.meta.env.VITE_APP_API_URL + '/aigc/uploadFile?slug=' + slug.value
+}
+const handleChange = info => {
+  console.log(info)
+  if (info.file.status !== 'uploading') {
+  }
+  if (info.file.status === 'done') {
+    fileList.value = []
+    if (info.file.response.ok == false) {
+      message.error(`文件上传失败:${info.file.response.msg} `);
+    } else {
+      message.info(`文件上传成功:${info.file.name}`);
+      getDocuments();
+    }
+  }
+};
+const fileList = ref([]);
+const headers = {
+  'x-access-token': localRead(LocalStorageKeyConst.USER_TOKEN),
+};
+
+</script>
+<style>
+
+</style>

+ 100 - 0
src/views/flow/aigc/project/index.vue

@@ -0,0 +1,100 @@
+<template>
+  <div>
+    <a-spin size="large" :spinning="spinning">
+      <a-layout>
+        <a-upload
+            v-model:file-list="fileList"
+            name="file"
+            :action="getAction"
+            :headers="headers"
+            @change="handleChange"
+        >
+          <a-button>
+            <upload-outlined></upload-outlined>
+            上传文档
+          </a-button>
+        </a-upload>
+        <a-layout>
+          <a-layout-sider :style="siderStyle">
+            <a-textarea style="height: 100%" v-model:value="chatValue" placeholder="问题" :rows="4"/>
+          </a-layout-sider>
+          <a-layout-content :style="contentStyle">
+            <a-textarea style="height: 100%" v-model:value="resultValue" placeholder="结果" :rows="4"/>
+          </a-layout-content>
+        </a-layout>
+        <a-layout-footer :style="footerStyle">
+          <a-button @click="chatClick">发送</a-button>
+        </a-layout-footer>
+      </a-layout>
+    </a-spin>
+
+
+  </div>
+
+</template>
+<script setup>
+import {ref} from 'vue';
+import {message} from 'ant-design-vue';
+import {localRead} from "/src/utils/local-util.js";
+import LocalStorageKeyConst from "/src/constants/local-storage-key-const.js";
+import {chat} from "/src/api/flow/aigcApi.js"
+
+const chatValue = ref('');
+const resultValue = ref('');
+const chatSessionValue = ref('');
+const spinning = ref(false);
+const chatClick = async () => {
+  const send = {}
+  send.message = chatValue.value
+  send.slug = slug.value
+  send.sessionId = chatSessionValue.value
+  spinning.value = true
+  await chat(send).then(
+      (res) => {
+        if (res.data !== null) {
+          const data = JSON.parse(res.data)
+          resultValue.value = data.textResponse
+          chatSessionValue.value = data.chatId+""
+          console.log(JSON.parse(res.data), 'JSON.parse(res.data)')
+          spinning.value = false
+        }
+      },
+      (err) => {
+        console.log(err);
+      }
+  );
+}
+const slug = ref('my-node')
+const getAction = () => {
+  return import.meta.env.VITE_APP_API_URL + '/aigc/uploadFile?slug=' + slug.value
+}
+const handleChange = info => {
+  if (info.file.status !== 'uploading') {
+    console.log(info.file, info.fileList);
+  }
+  if (info.file.status === 'done') {
+    message.success(`${info.file.name} file uploaded successfully`);
+  } else if (info.file.status === 'error') {
+    message.error(`${info.file.name} file upload failed.`);
+  }
+};
+const fileList = ref([]);
+const headers = {
+  'x-access-token': localRead(LocalStorageKeyConst.USER_TOKEN),
+};
+const headerStyle = {
+  height: 64,
+  lineHeight: '64px',
+};
+const contentStyle = {
+  minHeight: 320,
+  lineHeight: '320px',
+};
+const siderStyle = {
+  lineHeight: '320px',
+};
+const footerStyle = {};
+</script>
+<style>
+
+</style>

+ 224 - 0
src/views/flow/ragflow/chat/index.vue

@@ -0,0 +1,224 @@
+<template xmlns="http://www.w3.org/1999/html">
+  <div class="chat-container">
+    <a-layout class="layout">
+      <a-layout-content class="chat-content">
+        <div class="chat-box">
+          <!-- 消息列表 -->
+          <div class="chat-message" v-for="(message, index) in messages" :key="index">
+            <!-- AI 消息 -->
+            <div v-if="message.sender === 'ai'" class="message-container ai-message">
+
+              <img src="/@/assets/loginNew/logo.svg" alt="AI Avatar" class="avatar"/>
+              <div class="message-bubble">
+                <div v-html="renderMarkdown(message.text)"></div>
+                <span class="timestamp">{{ message.timestamp }}</span>
+                <span>参考文档:</span>
+                <br>
+                <div v-for="(source,index) in message.sources">
+                  <a >{{ source.doc_name }}</a>
+                </div>
+              </div>
+            </div>
+            <!-- 用户消息 -->
+            <div v-else class="message-container user-message">
+              <div class="message-bubble">
+                <p>{{ message.text }}</p>
+                <span class="timestamp">{{ message.timestamp }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </a-layout-content>
+      <a-layout-footer class="chat-footer">
+        <a-spin size="large" :spinning="spinning">
+          <a-textarea
+              class="chat-input"
+              v-model:value="chatValue"
+              placeholder="输入你的问题..."
+              :auto-size="{ minRows: 2, maxRows: 4 }"
+          />
+          <a-button class="send-button" type="primary" @click="chatClick">发送</a-button>
+        </a-spin>
+      </a-layout-footer>
+    </a-layout>
+<!--    <a-modal v-model:open="documentDescribeOpen" title="文档描述">-->
+<!--      <div v-html="renderMarkdown(documentDescribe.text )"></div>-->
+<!--    </a-modal>-->
+  </div>
+</template>
+
+<script setup>
+import {ref} from 'vue';
+import {chat} from "/src/api/flow/RAGFlowApi.js";
+import {marked} from 'marked'; // 引入 marked 库
+// Markdown 渲染方法
+const renderMarkdown = (markdownText) => {
+  return marked(markdownText); // 将 Markdown 转换为 HTML
+};
+
+const chatValue = ref('');
+const messages = ref([]); // 存储所有消息
+const spinning = ref(false);
+const documentDescribe = ref('')
+const documentDescribeOpen = ref(false);
+const documentDescribeClick = (source) => {
+  documentDescribeOpen.value = true
+  documentDescribe.value = source
+}
+// 获取当前时间
+const getCurrentTime = () => {
+  const now = new Date();
+  return `${now.getHours()}:${String(now.getMinutes()).padStart(2, '0')}`;
+};
+
+const chatClick = async () => {
+  if (!chatValue.value.trim()) return; // 如果输入框为空,则不发送
+
+  // 将用户输入的消息添加到消息列表
+  messages.value.push({sender: 'user', text: chatValue.value, timestamp: getCurrentTime()});
+
+  const send = {
+    message: chatValue.value,
+    slug: slug.value,
+    sessionId: chatSessionValue.value,
+  };
+  spinning.value = true;
+  await chat(send).then(
+      (res) => {
+        if (res.data !== null) {
+          let unicodeString=res.data
+          const decodedString = unicodeString.replace(/\\u([\dA-Fa-f]{4})/g, (match, grp) => {
+            return String.fromCharCode(parseInt(grp, 16));
+          });
+          const data = JSON.parse(decodedString);
+          spinning.value = false;
+          console.log(data,'data');
+          // 将AI回复的消息添加到消息列表
+          messages.value.push({
+            sender: 'ai',
+            text: data.data.answer,
+            sources: data.data.reference?.doc_aggs,
+            timestamp: getCurrentTime()
+          });
+          chatSessionValue.value = data.chatId + "";
+        } else {
+          spinning.value = false;
+          messages.value.push({sender: 'ai', text: res.message, timestamp: getCurrentTime()});
+        }
+      },
+      (err) => {
+        spinning.value = false;
+      }
+  );
+
+  // 清空输入框
+  chatValue.value = '';
+};
+
+const slug = ref('19c431a6f7f211efbed70242ac130006');
+const chatSessionValue = ref('');
+</script>
+
+<style scoped>
+.chat-container {
+  max-width: 900px;
+  margin: 0 auto;
+  padding: 20px;
+  background-color: #f5f5f5;
+  border-radius: 10px;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+}
+
+.layout {
+  background-color: #fff;
+  border-radius: 10px;
+  overflow: hidden;
+}
+
+.chat-content {
+  padding: 20px;
+  height: 500px;
+  overflow-y: auto;
+  background-color: #f9f9f9;
+}
+
+.chat-box {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+}
+
+.chat-message {
+}
+
+.message-container {
+  display: flex;
+  align-items: flex-end;
+  max-width: 99%;
+}
+
+.ai-message {
+  justify-content: flex-start;
+}
+
+.user-message {
+  justify-content: flex-end;
+}
+
+.message-bubble {
+  max-width: 70%;
+  padding: 10px 15px;
+  border-radius: 15px;
+  word-wrap: break-word;
+  position: relative;
+}
+
+.ai-message .message-bubble {
+  background-color: #e1f5fe;
+  margin-left: 10px;
+}
+
+.user-message .message-bubble {
+  background-color: #d1c4e9;
+  margin-right: 10px;
+}
+
+.timestamp {
+  display: block;
+  font-size: 12px;
+  color: #666;
+  margin-top: 5px;
+  text-align: right;
+}
+
+.chat-footer {
+  display: flex;
+  gap: 10px;
+  padding: 10px;
+  background-color: #fff;
+  border-top: 1px solid #e0e0e0;
+}
+
+.chat-input {
+  flex: 1;
+  border-radius: 20px;
+  padding: 10px;
+  border: 1px solid #e0e0e0;
+}
+
+.send-button {
+  border-radius: 20px;
+  padding: 0 20px;
+}
+
+.ai-message {
+  align-items: flex-start; /* 对齐方式改为起始对齐 */
+}
+
+.avatar {
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  object-fit: cover;
+}
+</style>

+ 145 - 0
src/views/flow/ragflow/knowledge/index.vue

@@ -0,0 +1,145 @@
+<template>
+  <div>
+    <a-spin size="large" :spinning="spinning">
+      <a-layout>
+        <a-upload
+            v-model:file-list="fileList"
+            name="file"
+            :action="getAction"
+            :headers="headers"
+            @change="handleChange"
+        >
+          <a-button>
+            <upload-outlined></upload-outlined>
+            上传文档
+          </a-button>
+        </a-upload>
+        <a-layout>
+          <a-table :columns="columns" :data-source="data">
+            <template #headerCell="{ column }">
+              <template v-if="column.key === 'title'">
+                <a-input-search
+                    style="width:300px"
+                    v-model:value="searchValue"
+                    placeholder="文档名称"
+                    enter-button
+                    @search="onSearch"
+                />
+              </template>
+            </template>
+            <template #bodyCell="{ column, record }">
+              <template v-if="column.key === 'title'">
+                <a :href="record.url">
+                  {{ record.title }}
+                </a>
+              </template>
+              <template v-if="column.key === 'action'">
+                <a-button danger block size="small" @click="deleteDocumentClick(record)">
+                  <template #icon>
+                    <DeleteOutlined/>
+                  </template>
+                </a-button>
+              </template>
+            </template>
+          </a-table>
+        </a-layout>
+
+      </a-layout>
+    </a-spin>
+
+
+  </div>
+
+</template>
+<script setup>
+import {ref, onMounted} from 'vue';
+import {message, Modal} from 'ant-design-vue';
+import {localRead} from "/src/utils/local-util.js";
+import LocalStorageKeyConst from "/src/constants/local-storage-key-const.js";
+import {documents, deleteDocument} from "/src/api/flow/RAGFlowApi.js"
+
+
+onMounted(() => {
+  getDocuments();
+})
+const getDocuments = () => {
+  documents({"title": searchValue.value}).then((res) => {
+        data.value = res.data
+      }
+  )
+}
+const searchValue = ref('')
+const onSearch = () => {
+  getDocuments();
+}
+const deleteDocumentClick = (record) => {
+
+  Modal.confirm({
+    title: '确认删除文档?',
+    onOk() {
+      deleteDocument({slug: slug.value, id: record.id}).then((res) => {
+            getDocuments();
+            message.info(`删除文档成功`);
+          }
+      )
+    },
+    onCancel() {
+      console.log('Cancel');
+    },
+    class: 'test',
+  });
+
+
+}
+
+const columns = [
+  {
+    name: '文档名称',
+    dataIndex: 'title',
+    key: 'title',
+  },
+  {
+    title: '上传时间',
+    dataIndex: 'published',
+    key: 'published',
+  },
+  {
+    title: '上传人',
+    dataIndex: 'publisher',
+    key: 'publisher',
+  },
+  {
+    title: '',
+    dataIndex: 'action',
+    key: 'action',
+  },
+];
+const data = ref([]);
+const spinning = ref(false);
+const slug = ref('11471e46f7f011efb94a0242ac130006');
+const getAction = () => {
+  return import.meta.env.VITE_APP_API_URL + '/ragflow/uploadFile?slug=' + slug.value
+}
+const handleChange = info => {
+  console.log(info)
+  if (info.file.status !== 'uploading') {
+  }
+  if (info.file.status === 'done') {
+    fileList.value = []
+    if (info.file.response.ok == false) {
+      message.error(`文件上传失败:${info.file.response.msg} `);
+    } else {
+      message.info(`文件上传成功:${info.file.name}`);
+      getDocuments();
+    }
+  }
+};
+const fileList = ref([]);
+const headers = {
+  'x-access-token': localRead(LocalStorageKeyConst.USER_TOKEN),
+};
+
+</script>
+<style>
+
+</style>

+ 100 - 0
src/views/flow/ragflow/project/index.vue

@@ -0,0 +1,100 @@
+<template>
+  <div>
+    <a-spin size="large" :spinning="spinning">
+      <a-layout>
+        <a-upload
+            v-model:file-list="fileList"
+            name="file"
+            :action="getAction"
+            :headers="headers"
+            @change="handleChange"
+        >
+          <a-button>
+            <upload-outlined></upload-outlined>
+            上传文档
+          </a-button>
+        </a-upload>
+        <a-layout>
+          <a-layout-sider :style="siderStyle">
+            <a-textarea style="height: 100%" v-model:value="chatValue" placeholder="问题" :rows="4"/>
+          </a-layout-sider>
+          <a-layout-content :style="contentStyle">
+            <a-textarea style="height: 100%" v-model:value="resultValue" placeholder="结果" :rows="4"/>
+          </a-layout-content>
+        </a-layout>
+        <a-layout-footer :style="footerStyle">
+          <a-button @click="chatClick">发送</a-button>
+        </a-layout-footer>
+      </a-layout>
+    </a-spin>
+
+
+  </div>
+
+</template>
+<script setup>
+import {ref} from 'vue';
+import {message} from 'ant-design-vue';
+import {localRead} from "/src/utils/local-util.js";
+import LocalStorageKeyConst from "/src/constants/local-storage-key-const.js";
+import {chat} from "/src/api/flow/aigcApi.js"
+
+const chatValue = ref('');
+const resultValue = ref('');
+const chatSessionValue = ref('');
+const spinning = ref(false);
+const chatClick = async () => {
+  const send = {}
+  send.message = chatValue.value
+  send.slug = slug.value
+  send.sessionId = chatSessionValue.value
+  spinning.value = true
+  await chat(send).then(
+      (res) => {
+        if (res.data !== null) {
+          const data = JSON.parse(res.data)
+          resultValue.value = data.textResponse
+          chatSessionValue.value = data.chatId+""
+          console.log(JSON.parse(res.data), 'JSON.parse(res.data)')
+          spinning.value = false
+        }
+      },
+      (err) => {
+        console.log(err);
+      }
+  );
+}
+const slug = ref('my-node')
+const getAction = () => {
+  return import.meta.env.VITE_APP_API_URL + '/aigc/uploadFile?slug=' + slug.value
+}
+const handleChange = info => {
+  if (info.file.status !== 'uploading') {
+    console.log(info.file, info.fileList);
+  }
+  if (info.file.status === 'done') {
+    message.success(`${info.file.name} file uploaded successfully`);
+  } else if (info.file.status === 'error') {
+    message.error(`${info.file.name} file upload failed.`);
+  }
+};
+const fileList = ref([]);
+const headers = {
+  'x-access-token': localRead(LocalStorageKeyConst.USER_TOKEN),
+};
+const headerStyle = {
+  height: 64,
+  lineHeight: '64px',
+};
+const contentStyle = {
+  minHeight: 320,
+  lineHeight: '320px',
+};
+const siderStyle = {
+  lineHeight: '320px',
+};
+const footerStyle = {};
+</script>
+<style>
+
+</style>

+ 164 - 0
src/views/flow/setting/eventService.vue

@@ -0,0 +1,164 @@
+<template>
+  <div>
+    <a-button @click="add">新增</a-button>
+    <a-table size="small" :columns="columns" :data-source="data" bordered>
+      <template #bodyCell="{ column, text, record, index }">
+
+        <div v-for="(col, idx) in ['name', 'url'].filter(el => el== column.key)">
+          <a-input v-if="record.editable" style="margin: -5px 0" v-model:value="record[column.key]" @change="(e) => handleChange(e.target.value, record.key, col)" />
+          <template v-else>
+            {{ text }}
+          </template>
+        </div>
+
+        <div v-if="column.key === 'operation'">
+          <div class="editable-row-operations">
+            <span v-if="record.editable">
+              <a @click="() => save(record.key)">保存</a>
+              <a-popconfirm title="确定取消?" @confirm="() => cancel(record.key)">
+                <a>取消</a>
+              </a-popconfirm>
+            </span>
+            <span v-else>
+              <a @click="() => edit(record.key)">编辑</a>
+              <a-popconfirm title="确定删除?" @confirm="() => delete1(record.key)">
+                <a>删除</a>
+              </a-popconfirm>
+            </span>
+          </div>
+        </div>
+      </template>
+    </a-table>
+  </div>
+</template>
+<script>
+  import { getEventService, istEventService, updateEventService, deleteEventService } from '/@/api/flow/settingApi';
+
+  const columns = [
+    {
+      title: '业务服务名称',
+      dataIndex: 'name',
+      width: '25%',
+      key: 'name',
+    },
+    {
+      title: 'url',
+      dataIndex: 'url',
+      width: '55%',
+      key: 'url',
+    },
+    {
+      title: '操作',
+      dataIndex: 'operation',
+      key: 'operation',
+    },
+  ];
+
+  export default {
+    data() {
+      return {
+        data: [],
+        cacheData: [],
+        columns,
+        editingKey: '',
+      };
+    },
+    mounted() {
+      getEventService().then((res) => {
+        for (let i = 0; i < res.data.length; i++) {
+          this.data.push({
+            id: res.data[i].id,
+            key: res.data[i].id,
+            name: res.data[i].name,
+            url: res.data[i].url,
+          });
+        }
+        this.cacheData = this.data.map((item) => ({ ...item }));
+      });
+    },
+    methods: {
+      add() {
+        this.data.push({
+          key: Date.now(),
+          name: '',
+          url: '',
+        });
+        this.cacheData = this.data.map((item) => ({ ...item }));
+      },
+      handleChange(value, key, column) {
+        const newData = [...this.data];
+        const target = newData.find((item) => key === item.key);
+        if (target) {
+          target[column] = value;
+          this.data = newData;
+        }
+      },
+      edit(key) {
+        const newData = [...this.data];
+        const target = newData.find((item) => key === item.key);
+        this.editingKey = key;
+        if (target) {
+          target.editable = true;
+          this.data = newData;
+        }
+      },
+      delete1(key) {
+        const newData = [...this.data];
+        const target = newData.find((item) => key === item.key);
+        if (target) {
+          deleteEventService({ ...target }).then((res) => {
+            this.data = this.data.filter((item) => item !== target);
+            this.cacheData = this.data.map((item) => ({ ...item }));
+          });
+        }
+      },
+      save(key) {
+        console.log(key);
+        console.log(this.data);
+        console.log(this.cacheData);
+        const newData = [...this.data];
+        console.log(newData);
+
+        const newCacheData = [...this.cacheData];
+        const target = newData.find((item) => key === item.key);
+        const targetCache = newCacheData.find((item) => key === item.key);
+
+        if (target && targetCache) {
+          console.log(1);
+
+          delete target.editable;
+          this.data = newData;
+          Object.assign(targetCache, target);
+          console.log(1);
+
+          this.cacheData = newCacheData;
+          console.log(target.id, 'target.id');
+          if (target.id) {
+            updateEventService({ ...target }).then((res) => {});
+          } else {
+            istEventService({ ...target }).then((res) => {});
+          }
+        }
+        this.editingKey = '';
+      },
+      cancel(key) {
+        const newData = [...this.data];
+        const target = newData.find((item) => key === item.key);
+        this.editingKey = '';
+        if (target) {
+          Object.assign(
+            target,
+            this.cacheData.find((item) => key === item.key)
+          );
+          delete target.editable;
+          this.data = newData;
+        }
+      },
+    },
+  };
+</script>
+<style scoped>
+  .editable-row-operations a {
+    margin-right: 8px;
+  }
+</style>

+ 118 - 0
src/views/flow/setting/flowAdministrator.vue

@@ -0,0 +1,118 @@
+<template>
+  <a-button type="primary" @click="add">添加流程管理员</a-button>
+  <a-table size="small":columns="columns" :data-source="data">
+    <template #headerCell="{ column }">
+      <template v-if="column.key === 'name'">
+        <span>
+          <PoweroffOutlined/>
+          人员
+        </span>
+      </template>
+      <template v-else>
+        <span>
+          {{ column.content }}
+        </span>
+      </template>
+    </template>
+    <template #bodyCell="{ column, record }">
+      <template v-if="column.key === 'name'">
+        <a>
+          {{ record.name }}
+        </a>
+      </template>
+      <template v-else-if="column.key === 'type'">
+        <a-switch v-model:checked="record.openFlag" checked-children="启用" un-checked-children="禁用"
+                  @change="updateState(record)"/>
+        <a-divider type="vertical"/>
+        <a @click="deleteData(record)">删除</a>
+      </template>
+    </template>
+  </a-table>
+  <select-user :title="'选择组织人员'" selectModel='multi' @callBack='selectU' :allowSelectDepartment="true"
+               v-show='false' ref='selectUserRef'/>
+</template>
+<script setup>
+import {
+  istAdminAdmit,
+  updateAdminAdmit,
+  delAdminAdmit,
+  getAdminAdmit,
+} from '/@/api/flow/settingApi.js';
+import {onMounted, ref} from 'vue'
+import SelectUser from "/@/components/StSelectUser/index.vue";
+import {message, Modal} from 'ant-design-vue';
+
+const selectUserRef = ref();
+const columns = [
+  {
+    content: '人员',
+    dataIndex: 'name',
+    key: 'name',
+    align: 'center',
+  },
+
+  {
+    content: '操作',
+    title: '',
+    key: 'type',
+    align: 'center',
+  },
+];
+
+const data = ref([])
+const props = defineProps(['flowCode'])
+onMounted(() => {
+  getAdminAdmit({flowCode: props.flowCode}).then((res) => {
+        data.value = res.data
+      }
+  )
+})
+const updateState = (record) => {
+  updateAdminAdmit(record)
+}
+const deleteData = (record) => {
+  Modal.confirm({
+    title: '删除确认?',
+    content: '删除后无法恢复',
+    okText: '确认',
+    okType: 'danger',
+    cancelText: '取消',
+    onOk() {
+      delAdminAdmit(record).then(() => {
+        data.value = data.value.filter(f => f.id != record.id)
+      })
+    },
+    onCancel() {
+    },
+  });
+}
+const add = () => {
+  selectUserRef.value.open = true;
+  selectUserRef.value.selectedKeys = []
+  selectUserRef.value.userdata = []
+}
+const selectU = (user) => {
+  console.log(user, user)
+  const users = user.codes.split(';')
+  const types = user.types.split(';')
+  const names = user.names.split(';')
+  let istData = []
+  for (let i in users) {
+    if (data.value.filter(f => f.content == users[i]).length <= 0) {
+      istData.push({
+        content: users[i],
+        type: types[i],
+        name: names[i],
+        flowCode: props.flowCode,
+        openFlag: true
+      })
+    }
+  }
+  data.value.push(...istData)
+  istAdminAdmit(istData)
+
+
+}
+
+</script>
+

+ 286 - 0
src/views/flow/setting/flowAgent.vue

@@ -0,0 +1,286 @@
+<template>
+  <a-button type="primary" @click="add">添加代理</a-button>
+  <a-table size="small" :columns="columns" :data-source="data" :scroll="{ x: 1500, y: 300 }">
+    <template #bodyCell="{ column, record }">
+      <template v-if="column.key === 'operation'">
+        <a @click="deleteData(record)">删除</a>
+      </template>
+      <template v-else-if="column.key === 'flowCodeInclude'">
+        <a-popover placement="right">
+          <template #content>
+            <a-textarea
+                :value=record.flowCodeInclude
+                :auto-size="{ minRows: 2, maxRows: 10 }"
+            />
+          </template>
+          {{ record.flowCodeInclude }}
+        </a-popover>
+      </template>
+    </template>
+  </a-table>
+  <a-modal v-model:open="openMain" title="添加代理任务" :footer="null">
+    <a-form
+        :model="formState"
+        name="basic"
+        :label-col="{ span: 4 }"
+        :wrapper-col="{ span: 24 }"
+        autocomplete="off"
+        @finish="onFinish"
+        @finishFailed="onFinishFailed"
+    >
+      <a-form-item
+          label="委托人"
+          name="userName"
+          :rules="[{ required: true, message: '请选择委托人!' }]"
+      >
+        <a-input-search
+            v-model:value="formState.userName"
+            placeholder="请选择委托人"
+            enter-button="选择委托人"
+            @search="onSearch('user')"
+        />
+      </a-form-item>
+      <a-form-item
+          label="代理人"
+          name="agentUserName"
+          :rules="[{ required: true, message: '请选择代理人!' }]"
+      >
+        <a-input-search
+            v-model:value="formState.agentUserName"
+            placeholder="请选择代理人"
+            enter-button="选择代理人"
+            @search="onSearch('agentUser')"
+        />
+      </a-form-item>
+      <a-form-item
+          label="开始时间"
+          name="agentBeginTimeDate"
+          :rules="[{ required: true, message: '请选择开始时间!' }]"
+      >
+        <a-date-picker v-model:value="formState.agentBeginTimeDate"/>
+      </a-form-item>
+      <a-form-item
+          label="结束时间"
+          name="agentEndTimeDate"
+          :rules="[{ required: true, message: '请选择结束时间!' }]"
+      >
+        <a-date-picker v-model:value="formState.agentEndTimeDate"/>
+      </a-form-item>
+      <a-form-item
+          label="包含流程"
+          name="includeFlow"
+      >
+        <a-tree-select
+            v-model:value="formState.flowCodeIncludeArray"
+            style="width: 100%"
+            :tree-data="treeData"
+            tree-checkable
+            allow-clear
+            placeholder="请选择包含流程"
+            tree-node-filter-prop="label"
+            :fieldNames="{
+              children: 'children',
+              label: 'flowName',
+              value: 'flowCode',
+            }"
+        />
+        <a-tag color="#2db7f5">
+          <template #icon>
+            <exclamation-circle-outlined/>
+          </template>
+          包含流程为空,表示选择全部流程
+        </a-tag>
+      </a-form-item>
+
+      <a-form-item :wrapper-col="{ offset: 1, span: 16 }">
+        <a-button type="primary" html-type="提交">确定</a-button>
+      </a-form-item>
+    </a-form>
+  </a-modal>
+  <select-user :title="'选择组织人员'" selectModel='single' @callBack='selectU' :allowSelectDepartment="false"
+               v-show='false' ref='selectUserRef'/>
+  <a-modal v-model:open="openFlow" title="添加流程" @ok="handleFlowOk">
+
+  </a-modal>
+</template>
+<script setup>
+import {
+  getTaskAgent,
+  insertTaskAgent,
+  delTaskAgent,
+} from '/@/api/flow/settingApi.js';
+import {onMounted, ref, reactive} from 'vue'
+import SelectUser from "/@/components/StSelectUser/index.vue";
+import {message, Modal} from 'ant-design-vue';
+import {settingApi} from '/@/api/process/process-setting';
+
+const openFlow = ref(false)
+const selectUserRef = ref();
+const columns = [
+  {
+    title: '编号',
+    dataIndex: 'id',
+    key: 'id',
+    align: 'center',
+    width: 100
+  },
+  {
+    title: '委托人',
+    dataIndex: 'userCode',
+    key: 'userCode',
+    align: 'center',
+    width: 150
+  },
+  {
+    title: '代理人',
+    dataIndex: 'agentUserCode',
+    key: 'agentUserCode',
+    align: 'center',
+    width: 150
+  },
+  {
+    title: '包含流程',
+    dataIndex: 'flowCodeInclude',
+    key: 'flowCodeInclude',
+    align: 'center',
+    ellipsis: true,
+  },
+  {
+    title: '开始时间',
+    dataIndex: 'agentBeginTime',
+    key: 'agentBeginTime',
+    align: 'center',
+    width: 150
+  },
+  {
+    title: '结束时间',
+    dataIndex: 'agentEndTime',
+    key: 'agentEndTime',
+    align: 'center',
+    width: 150
+  },
+  {
+    title: '操作',
+    key: 'operation',
+    align: 'center',
+    width: 150
+  },
+];
+const openMain = ref(false)
+const data = ref([])
+const treeData = ref([])
+const props = defineProps(['flowCode'])
+const handleFlowOk = () => {
+
+}
+onMounted(() => {
+  getTaskAgent().then((res) => {
+        data.value = res.data
+      }
+  )
+  settingApi.getAllStFlowMenu().then((res) => {
+    treeData.value = JSON.parse(res.data)
+    console.log(treeData.value, ';treeData.value')
+  })
+})
+const deleteData = (record) => {
+  Modal.confirm({
+    title: '删除确认?',
+    content: '删除后无法恢复',
+    okText: '确认',
+    okType: 'danger',
+    cancelText: '取消',
+    onOk() {
+      delTaskAgent([record.id]).then(() => {
+        getTaskAgent().then((res) => {
+              data.value = res.data
+            }
+        )
+      })
+    },
+    onCancel() {
+    },
+  });
+}
+const onSearch = (flag) => {
+  selectUserRef.value.open = true;
+  selectUserRef.value.selectedKeys = []
+  selectUserRef.value.userdata = []
+  selectFlag.value = flag
+}
+const add = () => {
+  formState = reactive({
+    userName: '',
+    userCode: '',
+    agentUserName: '',
+    agentUserCode: '',
+    agentBeginTimeDate: null,
+    agentBeginTime: '',
+    agentEndTimeDate: null,
+    agentEndTime: '',
+    flowCodeIncludeArray: [],
+    flowCodeInclude: '',
+  });
+  openMain.value = true
+}
+const selectFlag = ref('')
+
+let formState = reactive({
+  userName: '',
+  userCode: '',
+  agentUserName: '',
+  agentUserCode: '',
+  agentBeginTimeDate: null,
+  agentBeginTime: '',
+  agentEndTimeDate: null,
+  agentEndTime: '',
+  flowCodeIncludeArray: [],
+  flowCodeInclude: '',
+});
+const selectU = (user) => {
+  if (selectFlag.value == 'user') {
+    formState.userCode = user.codes
+    formState.userName = user.names
+  } else if (selectFlag.value == 'agentUser') {
+    formState.agentUserCode = user.codes
+    formState.agentUserName = user.names
+  }
+}
+const onFinish = values => {
+  if (formState.flowCodeIncludeArray.length > 0) {
+    formState.flowCodeInclude = JSON.stringify(formState.flowCodeIncludeArray)
+  }
+  formState.agentBeginTime = formattedDate(formState.agentBeginTimeDate)
+  formState.agentEndTime = formattedDate(formState.agentEndTimeDate)
+  if (formState.agentBeginTime > formState.agentEndTime) {
+    message.error("开始时间不能大于结束时间!")
+  } else if (formState.userCode == formState.agentUserCode) {
+    message.error("委托人和代理人不能是同一人!")
+  } else {
+    insertTaskAgent(formState).then((res) => {
+      if (res.success === true) {
+        openMain.value = false
+        message.info("保存成功。")
+        getTaskAgent().then((res) => {
+              data.value = res.data
+            }
+        )
+      } else {
+        message.error(res.message)
+      }
+    })
+  }
+
+};
+const formattedDate = (value1) => {
+  const date = new Date(value1);
+  const year = date.getFullYear();
+  const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从 0 开始,需要加 1
+  const day = String(date.getDate()).padStart(2, '0');
+  return `${year}-${month}-${day} 00:00:00`;
+}
+const onFinishFailed = errorInfo => {
+  console.log('Failed:', errorInfo);
+};
+</script>
+

+ 43 - 0
src/views/flow/setting/flowOperation.vue

@@ -0,0 +1,43 @@
+<template>
+  <div>
+    <a-row :gutter="16">
+      <a-col class="gutter-row" :span="5">
+        <div class="label-content">流程instanceId:</div>
+      </a-col>
+      <a-col class="gutter-row" :span="7">
+        <a-input v-model:value="instanceIdForUpdateVersion"></a-input>
+      </a-col>
+      <a-col class="gutter-row" :span="2">
+        <a-button type="primary" @click="upgradeVersion">流程版本升级</a-button>
+      </a-col>
+      <a-col class="gutter-row" :span="2"><a-tag>仅针对新增流程审批节点的流程,如果删除节点则无法升级</a-tag>
+        <a-tag>升级完成后需要从待办、已办中重新打开流程</a-tag></a-col>
+    </a-row>
+  </div>
+</template>
+<script setup>
+import {
+  flowUpgradeVersion,
+} from '/@/api/flow/settingApi.js';
+import {onMounted, ref} from 'vue'
+import {message, Modal} from 'ant-design-vue';
+
+
+const instanceIdForUpdateVersion = ref('')
+
+onMounted(() => {
+
+})
+const upgradeVersion = () => {
+  flowUpgradeVersion({instanceId: instanceIdForUpdateVersion.value}).then(res => {
+    console.log(res, 'res')
+    message.info("升级成功")
+  })
+}
+</script>
+<style scoped>
+.label-content {
+  text-align: right; /* 设置内容右对齐 */
+}
+</style>
+

+ 68 - 0
src/views/flow/setting/mainSetting.vue

@@ -0,0 +1,68 @@
+<template>
+  <div>
+    <a-drawer
+        title='流程设置'
+        width='100%'
+        placement='right'
+        :open='visible'
+        @close='onClose'
+        :closable='true'
+    >
+      <div class="custom-drawer">
+        <a-collapse accordion>
+          <a-collapse-panel key='1' header='任务代理:'>
+            <FlowAgent></FlowAgent>
+          </a-collapse-panel>
+          <a-collapse-panel key='2' header='数据字典:'>
+            <DictIndex></DictIndex>
+          </a-collapse-panel>
+          <a-collapse-panel key='3' header='业务服务:'>
+            <event-service></event-service>
+          </a-collapse-panel>
+          <a-collapse-panel key='4' header='查询服务:'>
+            <QueryService></QueryService>
+          </a-collapse-panel>
+          <a-collapse-panel key='5' header='流程管理员:'>
+            <FlowAdministrator></FlowAdministrator>
+          </a-collapse-panel>
+          <a-collapse-panel key='6' header='流程操作:'>
+            <FlowOperation></FlowOperation>
+          </a-collapse-panel>
+        </a-collapse>
+      </div>
+
+    </a-drawer>
+  </div>
+</template>
+<script>
+import FlowOperation from '/@/views/flow/setting/flowOperation.vue'
+import FlowAdministrator from '/@/views/flow/setting/flowAdministrator.vue'
+import FlowAgent from '/@/views/flow/setting/flowAgent.vue'
+import QueryService from '/@/views/flow/setting/queryService.vue'
+import EventService from '/@/views/flow/setting/eventService.vue'
+import DictIndex from '/@/views/support/dict/index.vue'
+
+export default {
+  components: {QueryService, EventService, DictIndex, FlowAdministrator, FlowAgent,FlowOperation},
+  data() {
+    return {
+      visible: false
+
+    }
+  },
+  methods: {
+    show() {
+      this.visible = true
+    },
+    onClose() {
+      this.visible = false
+    }
+  }
+}
+</script>
+<style scoped>
+.custom-drawer {
+  height: 100%;
+  overflow-y: auto;
+}
+</style>

+ 237 - 0
src/views/flow/setting/queryService.vue

@@ -0,0 +1,237 @@
+<template>
+  <div>
+    <a-flex style="padding: 10px" wrap="wrap" gap="small">
+      <a-input-search
+         style="width: 20%"
+          v-model:value="queryData"
+          placeholder="请输入查询名称"
+          enter-button
+          @search="query"
+      />
+    <a-button @click="add">新增查询</a-button>
+    </a-flex>
+    <a-table :columns="columns" :data-source="data" bordered>
+      <template #bodyCell="{ column, text, record, index }">
+        <template v-if="column.dataIndex === 'operation'">
+          <div class="editable-row-operations">
+            <a @click="() => edit(record)">编辑</a>
+          </div>
+        </template>
+      </template>
+    </a-table>
+
+    <a-modal width="85%" title="数据查询" :open="visible" @ok="handleOk" @cancel="handleCancel">
+      <a-row class="top-row">
+        <a-col :span="2"> 查询名称</a-col>
+        <a-col :span="22">
+          <a-input v-model:value="record.queryName"></a-input>
+        </a-col>
+      </a-row>
+      <a-row class="top-row">
+        <a-col :span="2"> 查询编码</a-col>
+        <a-col :span="22">
+          <a-input v-model:value="record.queryCode"></a-input>
+        </a-col>
+      </a-row>
+      <a-row class="top-row">
+        <a-col :span="2"> 查询SQL</a-col>
+        <a-col :span="22">
+          <a-textarea v-model:value="record.querySql" :rows="8"></a-textarea>
+        </a-col>
+      </a-row>
+      <a-button class="addBtn" @click="addChild">新增子项</a-button>
+      <a-table size="small" :columns="columnsZi" :data-source="dataZi" bordered :pagination="false">
+        <template #bodyCell="{ column, text, record, index }">
+          <div v-for="(col, idx) in columnsZi.filter(el => el.key == column.key).map(el => el.key)">
+            <a-input
+                v-if="col == 'field' || col == 'fieldName'"
+                style="margin: -5px 0"
+                :value="record[col]"
+                @change="(e) => handleChange(e.target.value, record, col)"
+            />
+            <a-checkbox
+                v-if="col == 'visible' || col == 'search' || col == 'mapping'"
+                v-model:checked="record[col]"
+                @change="(e) => handleChange(record[col], record, col)"
+            ></a-checkbox>
+            <a-button v-if="col == 'action'" @click="delChild(record, index)">删除</a-button>
+          </div>
+        </template>
+      </a-table>
+    </a-modal>
+  </div>
+</template>
+<script>
+import {getQueryMainList, updateQueryMain, istQueryMain} from '/@/api/flow/settingApi';
+
+const columnsZi = [
+  {
+    title: '字段编号',
+    dataIndex: 'field',
+    width: '25%',
+    key: 'field',
+  },
+  {
+    title: '字段名称',
+    dataIndex: 'fieldName',
+    width: '25%',
+    key: 'fieldName',
+  },
+  {
+    title: '可见',
+    dataIndex: 'visible',
+    width: '10%',
+    key: 'visible',
+  },
+  {
+    title: '可查',
+    dataIndex: 'search',
+    width: '10%',
+    key: 'search',
+  },
+  {
+    title: '用于映射',
+    dataIndex: 'mapping',
+    width: '10%',
+    key: 'mapping',
+  },
+  {
+    title: '操作',
+    key: 'action',
+  },
+];
+const columns = [
+  {
+    title: '查询服务名称',
+    dataIndex: 'queryName',
+    width: '20%',
+  },
+  {
+    title: '查询服务编码',
+    dataIndex: 'queryCode',
+    width: '20%',
+  },
+  {
+    title: '查询服务SQL',
+    dataIndex: 'querySql',
+    width: '30%',
+  },
+  {
+    title: '创建人',
+    dataIndex: 'createBy',
+    width: '5%',
+  },
+  {
+    title: '创建时间',
+    dataIndex: 'createTime',
+    width: '10%',
+  },
+  {
+    title: '操作',
+    dataIndex: 'operation',
+    scopedSlots: {customRender: 'operation'},
+  },
+];
+
+export default {
+  data() {
+    return {
+      data: [],
+      dataZi: [],
+      columns,
+      columnsZi,
+      record: {},
+      visible: false,
+      queryData:'',
+    };
+  },
+  mounted() {
+    this.initData();
+  },
+  methods: {
+    delChild(record, index) {
+      console.log(record, index, 'record');
+      this.dataZi.splice(index, 1);
+    },
+    initData() {
+      getQueryMainList().then((res) => {
+        this.data = res.data;
+      });
+    },
+    query() {
+      getQueryMainList({queryName:this.queryData}).then((res) => {
+        this.data = res.data;
+      });
+    },
+    add() {
+      this.dataZi = [];
+      this.visible = true;
+      this.record = {
+        queryName: '',
+        queryCode: '',
+        querySql: '',
+        queryColumnList: [
+          {
+            field: '',
+            fieldName: '',
+            visible: false,
+            search: false,
+          },
+        ],
+      };
+    },
+    addChild() {
+      this.dataZi.push({
+        field: '',
+        fieldName: '',
+        visible: false,
+        search: false,
+        mapping: false,
+      });
+    },
+    edit(record) {
+      this.dataZi = [];
+      this.visible = true;
+      this.record = record;
+      this.dataZi = record.queryColumnList;
+    },
+    onChangeCheckbox(e) {
+      console.log(e);
+    },
+    handleChange(value, record, column) {
+      console.log(value, record, column);
+      record[column] = value;
+    },
+    handleOk(e) {
+      this.visible = false;
+      this.record.queryColumns = JSON.stringify(this.dataZi);
+      if (this.record.id) {
+        updateQueryMain({...this.record}).then((res) => {
+          this.initData();
+        });
+      } else {
+        istQueryMain({...this.record}).then((res) => {
+          this.initData();
+        });
+      }
+      this.dataZi = [];
+    },
+    handleCancel(e) {
+      this.visible = false;
+    },
+  },
+};
+</script>
+<style scoped>
+.editable-row-operations a {
+  margin-right: 8px;
+}
+
+.top-row {
+  padding: 10px;
+}
+
+.addBtn {
+  margin: 10px;
+}
+</style>

+ 192 - 0
src/views/flow/stDataModel/components/SmartFieldDialog.vue

@@ -0,0 +1,192 @@
+<template>
+  <a-modal v-model:open="open" :title="title" @ok="handleOk" @cancel="close" width="70%">
+    <div class="modal-page">
+      <a-form :model="formData" :rules="rules" ref="formRef">
+        <a-row :gutter="20">
+          <a-col :span="12">
+            <div class="help-box1">
+              <a-form-item  label="" name="smartFields">
+                <a-textarea placeholder="智能添加内容" v-model:value="formData.smartFields" :rows="6"></a-textarea>
+              </a-form-item>
+            </div>
+          </a-col>
+          <a-col :span="12">
+            <div class="help-box2">
+              <div class="m1">
+                <ExclamationCircleOutlined />
+                <span>帮助</span>
+              </div>
+
+              <div class="mm">
+                <div v-for="(it, idIdx) in modeList" :key="idIdx">
+                  <span>{{ it.name }}:</span>
+                  <span>{{ it.value }}</span>
+                </div>
+              </div>
+            </div>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item  label="插入到表" name="insertTable">
+              <a-select
+                placeholder="请选择"
+                v-model:value="formData.insertTable"
+                :options="tableOpts"
+              ></a-select>
+            </a-form-item>
+          </a-col>
+        </a-row>
+      </a-form>
+    </div>
+  </a-modal>
+</template>
+<script setup>
+  import { computed, ref, toRefs, watch } from 'vue';
+  import { message } from 'ant-design-vue';
+  import { dataSmartAdd } from '/@/api/flow/dataApi';
+  import { ExclamationCircleOutlined} from "@ant-design/icons-vue"
+
+  const props = defineProps(["tableData"]);
+  const emits = defineEmits(["ok"]);
+  const { tableData } = toRefs(props);
+
+  const modeList = ref([{
+    name: "模式1",
+    value: "字段中文名称,逗号分隔开"
+  },{
+    name: "模式2",
+    value: "-场景描述,自动匹配字段"
+  },{
+    name: "模式3",
+    value: "*字段中文1,字段英文1;字段中文2,字段英文2"
+  }])
+
+  const tableOpts = computed(() => {
+    let mainData = [{
+        value: "main",
+        label: "主表"
+    }]
+
+    tableData.value.forEach(el => {
+      if(Array.isArray(el.children)) {
+        mainData.push({
+          label: el.name,
+          value: el.code,
+        })
+      }
+    })
+    return mainData;
+  })
+
+  const open = ref(false);
+
+  const title = ref("智能添加");
+
+  const formRef = ref(null);
+
+  const formData = ref({
+    smartFields: undefined,
+    insertTable: undefined
+  });
+
+  const rules = ref({
+    smartFields: [{ required: true, message: "请输入", trigger: 'change' }],
+    insertTable: [{ required: true, message: "请输入", trigger: 'change' }],
+  });
+
+  const handleOk = () => {
+    formRef.value
+      .validate()
+      .then((res) => {
+        return dataSmartAdd({ fieldNames: formData.value.smartFields })
+      }).then(res => {
+        const resFields = JSON.parse(res.data);
+        handleFields(resFields);
+        close();
+      })
+      .catch(error => {
+        console.log("error", error);
+        message.error("表单验证失败!");
+      });
+
+  };
+
+  const show = () => {
+    formData.value.insertTable = undefined;
+    formData.value.smartFields = undefined
+    open.value = true;
+  }
+
+  const close = () => {
+    open.value = false;
+  };
+
+
+  const handleFields = (list) => {
+    if(formData.value.insertTable === "main") {
+      tableData.value.forEach(el => {
+        const delIdx = list.findIndex(v => v.code === el.code);
+        if(delIdx > -1) list.splice(delIdx, 1);
+      })
+      list.forEach(v => {
+        const obj = {
+          ...v,
+          publish: "0",
+          trace: false,
+        }
+        tableData.value.push(obj);
+      })
+    } else {
+      const zMainList = tableData.value.find(v => v.code === formData.value.insertTable);
+      zMainList.children.forEach(el => {
+        const delIdx = list.findIndex(v => v.code === el.code);
+        if(delIdx > -1) list.splice(delIdx, 1);
+      })
+      list.forEach(v => {
+        const obj = {
+          ...v,
+          publish: "0",
+          trace: false,
+        }
+        zMainList.children.push(obj);
+      })
+    }
+    emits("ok", tableData.value);
+  };
+
+
+  defineExpose({show, close})
+</script>
+
+<style scoped lang="less">
+  .modal-page {
+    padding: 20px;
+
+    :deep(.ant-form-item) {
+      margin-bottom: 20px;
+    }
+
+    .help-box1 {
+      width: 100%;
+    }
+
+    .help-box2 {
+      width: 100%;
+      height: 140px;
+      background: #f5f9ff;
+      border-radius: 8px;
+      padding:10px 20px;
+      .m1 {
+        color: #467AF8;
+        font-weight: 600;
+        font-size: 16px;
+        display: flex;
+        gap: 5px;
+      }
+      .mm {
+        line-height: 30px;
+        color: #467AF8;
+        font-size: 16px;
+      }
+    }
+  }
+</style>

+ 143 - 0
src/views/flow/stDataModel/dataJsonData.js

@@ -0,0 +1,143 @@
+export const dataJsonData = {
+    'list': [
+        {
+            'type': 'input',
+            'label': '数据项名称',
+            'options': {
+                'type': 'text',
+                'width': '100%',
+                'defaultValue': '',
+                'placeholder': '请输入',
+                'clearable': false,
+                'maxLength': null,
+                'addonBefore': '',
+                'addonAfter': '',
+                'hidden': false,
+                'disabled': false
+            },
+            'model': 'name',
+            'key': 'input_1717149547245',
+            'help': '',
+            'rules': [
+                {
+                    'required': true,
+                    'message': '必填项'
+                }
+            ]
+        },
+        {
+            'type': 'input',
+            'label': '数据项编号',
+            'options': {
+                'type': 'text',
+                'width': '100%',
+                'defaultValue': '',
+                'placeholder': '请输入',
+                'clearable': false,
+                'maxLength': null,
+                'addonBefore': '',
+                'addonAfter': '',
+                'hidden': false,
+                'disabled': false
+            },
+            'model': 'code',
+            'key': 'input_1717149548695',
+            'help': '',
+            'rules': [
+                {
+                    'required': true,
+                    'message': '必填项'
+                }
+            ]
+        },
+        {
+            'type': 'select',
+            'label': '数据项类型',
+            'options': {
+                'width': '100%',
+                'multiple': false,
+                'disabled': false,
+                'clearable': false,
+                'hidden': false,
+                'placeholder': '请选择',
+                'dynamicKey': '',
+                'dynamic': false,
+                'options': [
+                    {
+                        'value': 'STRING',
+                        'label': '短文本'
+                    },
+                    {
+                        'value': 'TEXT',
+                        'label': '长文本'
+                    },
+                    {
+                        'value': 'SINGLE',
+                        'label': '人员组织(单选)'
+                    },
+                    {
+                        'value': 'MULTI',
+                        'label': '多选(包括 人员组织)'
+                    },
+                    {
+                        'value': 'INTEGER',
+                        'label': '整数'
+                    },
+                    {
+                        'value': 'DECIMAL',
+                        'label': '小数'
+                    },
+                    {
+                        'value': 'DATETIME',
+                        'label': '时间'
+                    },
+                    {
+                        'value': 'ATTACH',
+                        'label': '附件'
+                    },
+                    {
+                        'value': 'IMAGE',
+                        'label': '图片'
+                    },
+                    {
+                        'value': 'CHILDREN',
+                        'label': '子表'
+                    }
+                ],
+                'showSearch': false
+            },
+            'model': 'type',
+            'key': 'select_1717149549995',
+            'help': '',
+            'rules': [
+                {
+                    'required': true,
+                    'message': '必填项'
+                }
+            ]
+        }
+    ],
+    'config': {
+        'layout': 'horizontal',
+        'labelCol': {
+            'xs': 4,
+            'sm': 4,
+            'md': 4,
+            'lg': 4,
+            'xl': 4,
+            'xxl': 4
+        },
+        'labelWidth': 135,
+        'labelLayout': 'flex',
+        'wrapperCol': {
+            'xs': 18,
+            'sm': 18,
+            'md': 18,
+            'lg': 18,
+            'xl': 18,
+            'xxl': 18
+        },
+        'hideRequiredMark': false,
+        'customStyle': ''
+    }
+}

+ 472 - 0
src/views/flow/stDataModel/index.vue

@@ -0,0 +1,472 @@
+<template>
+  <div class="page">
+    <div>
+      <a-row style="margin: 2px; padding: 10px 0px 0px 0px" type="flex">
+        <a-col :flex="1">
+          <a-button style="margin-left: 30px" type="dashed" @click="addField"> 添加数据</a-button>
+        </a-col>
+        <a-col style="display: flex; align-items: center; gap: 10px; margin-right: 10px">
+          <a-button type="dashed" @click="smartAdd">智能添加</a-button>
+          <a-button type="dashed" @click="saveField">保存</a-button>
+          <a-button @click="publishField" type="primary"> 发布</a-button>
+        </a-col>
+      </a-row>
+      <a-row style="margin: 2px; padding: 10px">
+        <a-table size="middle" :pagination="false" :columns="columns" :data-source="data" :defaultExpandAllRows="true"
+                 :scroll="{ y: 530 }" :loading="loading">
+          <template #bodyCell="{ column, text, record, index }">
+            <span v-if="column.key === 'seq'">{{ index + 1 }}</span>
+            <span v-if="['name','code'].includes(column.key)" style="font-size: 16px">{{ text }}</span>
+            <a style="font-size: 16px" v-if="column.key === 'type'">
+              <span v-if="text == 'STRING'">短文本</span>
+              <span v-else-if="text == 'TEXT'">长文本</span>
+              <span v-else-if="text == 'SINGLE'">人员组织(单选)</span>
+              <span v-else-if="text == 'MULTI'">多选(包括 人员组织)</span>
+              <span v-else-if="text == 'INTEGER'">整数</span>
+              <span v-else-if="text == 'DECIMAL'">小数</span>
+              <span v-else-if="text == 'DATETIME'">时间</span>
+              <span v-else-if="text == 'ATTACH'">附件</span>
+              <span v-else-if="text == 'IMAGE'">图片</span>
+              <span v-else-if="text == 'CHILDREN'">子表</span>
+              <span v-else>
+                {{ text }}
+              </span>
+            </a>
+            <a-switch v-if="column.key === 'publish'" :checked="checked(text)"/>
+            <a-switch v-if="chargeCanTrance(column,record)"
+                      v-model:checked="record.trace"/>
+            <template v-if="column.key === 'operation'">
+              <div style="display: flex; gap: 10px">
+                <a-button type="dashed" @click="handleRem(record, 'up')" :disabled="!record.isMoveUp">
+                  <UpOutlined/>
+                </a-button>
+                <a-button type="dashed" @click="handleRem(record, 'down')" :disabled="!record.isMoveDown">
+                  <DownOutlined/>
+                </a-button>
+                <a-button type="dashed" @click="handleEdit(record)">编辑</a-button>
+                <a-button v-show="record.publish!='1'" danger @click="handleDel(record)">删除</a-button>
+                <a-button type="primary" v-if="record.type == 'CHILDREN'" @click="handleAddChild(record)">新增子数据
+                </a-button>
+              </div>
+            </template>
+          </template>
+        </a-table>
+      </a-row>
+    </div>
+
+    <SmartFieldDialog ref="smartFielDialog" :tableData="data" @ok="okSmartFieldDialog"></SmartFieldDialog>
+  </div>
+</template>
+
+<script>
+import {dataJsonData} from './dataJsonData.js';
+import {getDataTemplate, istDataTemplate, dataSmartAdd} from '/@/api/flow/dataApi';
+import StSetting from '/@/views/flow/stSetting/index.vue';
+import {useFlowStore} from '/@/store/modules/flow';
+import cloneDeep from 'lodash/cloneDeep';
+import {useFormModal as stFormModal} from "/@/components/StFormModal/index";
+import {defineComponent, getCurrentInstance} from 'vue';
+import {message} from 'ant-design-vue';
+import {UpOutlined, DownOutlined} from '@ant-design/icons-vue';
+import SmartFieldDialog from '/@/views/flow/stDataModel/components/SmartFieldDialog.vue';
+
+const columns = [
+  {
+    title: '序号',
+    width: 80,
+    dataIndex: 'seq',
+    align: 'center',
+    key: 'seq',
+  },
+  {
+    title: '数据项名称',
+    width: 300,
+    dataIndex: 'name',
+    align: 'left',
+    key: 'name',
+  },
+  {
+    title: '数据项编号',
+    width: 300,
+    dataIndex: 'code',
+    align: 'center',
+    key: 'code',
+  },
+  {
+    title: '数据项类型',
+    width: 300,
+    dataIndex: 'type',
+    align: 'center',
+    key: 'type',
+  },
+  {
+    title: '痕迹',
+    width: 150,
+    dataIndex: 'trace',
+    align: 'center',
+    key: 'trace',
+  },
+  {
+    title: '是否发布',
+    width: 150,
+    dataIndex: 'publish',
+    align: 'center',
+    key: 'publish',
+  },
+  {
+    title: '操作',
+    width: 320,
+    dataIndex: 'operation',
+    align: 'center',
+    key: 'operation',
+  },
+];
+export default defineComponent({
+  name: 'StFlowModalData',
+  components: {
+    StSetting,
+    SmartFieldDialog
+  },
+  data() {
+    return {
+      columns,
+      data: [],
+      loading: false,
+      smartFields: '',
+    };
+  },
+
+  computed: {
+    currentFlowModal() {
+      return useFlowStore().StFlowModal;
+    },
+  },
+  mounted() {
+    this.initForm();
+  },
+  methods: {
+    handleRem(row, dr) {
+      if (dr == 'up') {
+        this.moveNodeUp(row);
+      } else {
+        this.moveNodeDown(row);
+      }
+      this.initializeTreeData();
+    },
+    moveNodeUp(nodeToMove) {
+      let trees = this.data;
+      const findAndMoveUp = (nodes) => {
+        for (let i = 0; i < nodes.length; i++) {
+          if (nodes[i].children) {
+            // 递归地在子节点中查找并上移
+            findAndMoveUp(nodes[i].children);
+          }
+          if (nodes[i].code === nodeToMove.code) {
+            // 找到了节点,进行上移操作
+            if (i === 0) {
+              this.$message.error("已是第一位,无法上移");
+              return;
+            }
+            // 将节点上移一位
+            const movedNode = nodes.splice(i, 1)[0];
+            nodes.splice(i - 1, 0, movedNode);
+            this.$message.success("节点上移成功");
+            return true;
+          }
+        }
+        return false;
+      };
+
+      // 遍历整个树,找到并上移节点
+      const isMoved = findAndMoveUp(trees);
+      if (!isMoved) {
+        console.log("未找到节点或节点已经在第一位");
+      }
+    },
+    moveNodeDown(nodeToMove) {
+      let trees = this.data;
+      const findAndMoveDown = (nodes) => {
+        for (let i = 0; i < nodes.length; i++) {
+          if (nodes[i].children) {
+            // 递归地在子节点中查找并下移
+            findAndMoveDown(nodes[i].children);
+          }
+          if (nodes[i].code === nodeToMove.code) {
+            // 找到了节点,进行下移操作
+            if (i === nodes.length - 1) {
+              this.$message.error("已是最后一位,无法下移");
+              return;
+            }
+            // 将节点下移一位
+            const movedNode = nodes.splice(i, 1)[0];
+            nodes.splice(i + 1, 0, movedNode);
+            this.$message.success("节点下移成功");
+            return true;
+          }
+        }
+        return false;
+      };
+
+      // 遍历整个树,找到并下移节点
+      const isMoved = findAndMoveDown(trees);
+      if (!isMoved) {
+        console.log("未找到节点或节点已经在最后一位");
+      }
+    },
+    initializeTreeData(data = null) {
+      let nodes = data || this.data;
+      nodes.forEach((node, index) => {
+        // 判断当前节点是否能够上移
+        node.isMoveUp = index > 0;
+        // 判断当前节点是否能够下移
+        node.isMoveDown = index < nodes.length - 1;
+        // 如果当前节点有子节点,递归调用 initializeTree
+        if (Array.isArray(node.children) && node.children.length > 0) {
+          this.initializeTreeData(node.children);
+        }
+      });
+    },
+    chargeCanTrance(column, record) {
+      const parentExists = this.data.some(item => {
+        // 检查当前项是否有children数组
+        if (item.children && Array.isArray(item.children)) {
+          // 检查children数组中是否有对象的code为'code'
+          return item.children.some(child => child.code == record.code);
+        }
+        return false;
+      });
+
+      if (column.key === 'trace'
+          && (record.type == 'STRING' || record.type == 'SINGLE' || record.type == 'INTEGER' || record.type == 'DECIMAL' || record.type == 'DATETIME')
+          && parentExists == false) {
+        return true;
+      }
+      return false
+    },
+    checked(text) {
+      if (text == '1') {
+        return true
+      } else {
+        return false
+      }
+    },
+    smartAdd() {
+      this.$refs.smartFielDialog.show();
+      // dataSmartAdd({fieldNames: this.smartFields}).then((res) => {
+      //   this.data.push(...JSON.parse(res.data));
+      //   this.initializeTreeData();
+      // });
+    },
+    initForm() {
+      const self = this;
+      getDataTemplate({flowCode: this.currentFlowModal.flowCode}).then(
+          (res) => {
+            if (res.data !== null) {
+              self.data = JSON.parse(res.data.content);
+              this.initializeTreeData();
+            }
+          },
+          (err) => {
+            console.log(err);
+          }
+      );
+    },
+    addField() {
+      const self = this;
+      const cloneFlowJSONData = cloneDeep(dataJsonData);
+      // TODO
+      stFormModal({
+        disabled: false,
+        title: '添加数据项',
+        jsonData: cloneFlowJSONData,
+        config: {},
+        onMounted(vm, formData) {
+        },
+        onChange(value, key, vm) {
+        },
+        beforeSubmit(vm) {
+          if (self.data.some((item) => item.name === vm.formData.name || item.code === vm.formData.code)) {
+            message.error('重复的名称或编号');
+            return false;
+          }
+        },
+        onSubmit(formData) {
+          let fieldData = {
+            name: formData['name'],
+            code: formData['code'],
+            type: formData['type'],
+          };
+          if (fieldData.type == 'CHILDREN') {
+            fieldData.children = [];
+          }
+          self.data.push(fieldData);
+          self.initializeTreeData();
+        },
+      });
+    },
+    saveField() {
+      const self = this;
+      const data = this.handleDataScope();
+      data.published = '0';
+      istDataTemplate({
+        ...data,
+      }).then(
+          (res) => {
+            if (res.success) {
+              self.$message.success('保存成功');
+            } else {
+              self.$message.error(res.message);
+            }
+          }
+      ).catch(err => {
+        console.log(err)
+      })
+    },
+    findDuplicates(arr) {
+      const seen = new Set();
+      const duplicates = [];
+      for (let item of arr) {
+        if (seen.has(item)) {
+          duplicates.push(item); // 如果已经见过,加入重复项数组
+        } else {
+          seen.add(item); // 否则记录为见过
+        }
+      }
+      return duplicates;
+    },
+    publishField() {
+      const self = this;
+      const data = this.handleDataScope();
+      data.published = '1';
+      const duplicateCode = []//判断是否有重复的项目
+      for (const i in data.columns) {
+        data.columns[i]['publish'] = 1;
+        duplicateCode.push(data.columns[i]['code'])
+        if (data.columns[i]['children'] != null && data.columns[i]['children'] != undefined && data.columns[i]['children'].length > 0) {
+          for (const j in data.columns[i]['children']) {
+            data.columns[i]['children'][j]['publish'] = 1;
+            duplicateCode.push(data.columns[i]['children'][j]['code'])
+          }
+        }
+      }
+      const duplicates = self.findDuplicates(duplicateCode)
+      if (duplicates.length > 0) {
+        self.$message.error(`存在重复的数据项编号【${duplicates}】`);
+      } else {
+        istDataTemplate({
+          ...data,
+        }).then(
+            (res) => {
+              if (res.success) {
+                self.$message.success('发布成功');
+              } else {
+                self.$message.error(res.message);
+              }
+            }
+        ).catch(err => {
+          console.log(err)
+        })
+      }
+    },
+    handleEdit(record) {
+      const self = this;
+      const cloneFlowJSONData = cloneDeep(dataJsonData);
+      stFormModal({
+        disabled: false,
+        title: '编辑数据项',
+        jsonData: cloneFlowJSONData,
+        config: {},
+        onMounted(vm, formData) {
+          if (record.publish == '1') {
+            formData.disable("type");
+            formData.disable("code");
+          }
+          formData.setData(record);
+        },
+        onChange(value, key, vm) {
+        },
+        beforeSubmit(vm) {
+          if (self.data.some((item) => item.name === vm.formData.name && item.name != record.name)) {
+            message.error('重复的名称');
+            return false;
+          }
+        },
+        onSubmit(formData) {
+          record.name = formData['name'];
+          record.code = formData['code'];
+          record.type = formData['type'];
+        },
+      });
+    },
+    handleDel(record) {
+      const self = this;
+      if (record.publish != 1) {
+        let indexToRemove = self.data.findIndex((item) => item.code === record.code);
+        if (indexToRemove !== -1) {
+          self.data.splice(indexToRemove, 1);
+          self.initializeTreeData();
+        }
+      } else {
+        this.$message.error('已发布的字段,无法删除,需在后台处理');
+      }
+    },
+    handleAddChild(record) {
+      const self = this;
+      const cloneFlowJSONData = cloneDeep(dataJsonData);
+      stFormModal({
+        disabled: false,
+        title: '添加子数据项',
+        jsonData: cloneFlowJSONData,
+        config: {},
+        onMounted(vm, formData) {
+        },
+        onChange(value, key, vm) {
+        },
+        beforeSubmit(vm) {
+          if (self.data.some((item) => item.name === vm.formData.name || item.code === vm.formData.code)) {
+            message.error('重复的名称或编号');
+            return false;
+          }
+        },
+        onSubmit(formData) {
+          let childElement = self.data.find((item) => item.code == record.code);
+          if (childElement.children == null) {
+            childElement.children = []
+          }
+          childElement.children.push({
+            name: formData['name'],
+            code: formData['code'],
+            type: formData['type'],
+          });
+          self.initializeTreeData();
+        },
+      });
+    },
+    getContainerSize() {
+      return {
+        width: document.body.offsetWidth - 690,
+        height: document.body.offsetHeight - 38,
+      };
+    },
+    handleDataScope() {
+      const dataTemplate = {};
+      dataTemplate.flowCode = this.currentFlowModal.flowCode;
+      dataTemplate.columns = this.data;
+      return dataTemplate;
+    },
+    okSmartFieldDialog(list) {
+      // TODO 联调
+      console.log("list ====>", list)
+      this.data = list;
+      this.initializeTreeData();
+    }
+  },
+});
+</script>
+
+<style scoped lang="less">
+.page {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 115 - 0
src/views/flow/stDesignMain/index.vue

@@ -0,0 +1,115 @@
+<template>
+  <a-drawer v-model:open="open" width="100%" destroyOnClose class="package-age">
+    <template #title>
+      <div class="pag-header">
+        <span class="pg-h-txt">{{ title }}<span style="padding: 20px;font-size: 14px;color: #0D366F">{{ flowCode}}</span></span>
+        <a-button-group size="large" style="flex: 1; display: flex; justify-content: center">
+          <a-button
+              :type="currentComName === item.componentName && 'primary'"
+              size="large"
+              @click="switchTag(item, index)"
+              v-for="(item, index) in headerList"
+              :key="index"
+          >
+            {{ item.title }}
+          </a-button>
+        </a-button-group>
+      </div>
+    </template>
+    <div class="pag-content" v-if="visible">
+      <component :is="currentComName"></component>
+    </div>
+  </a-drawer>
+</template>
+<script setup>
+import {ref} from 'vue';
+import StDataIndex from '/@/views/flow/stDataModel/index.vue';
+import StFormIndex from '/@/views/flow/stFormSetting/index.vue';
+import StFlowIndex from '/@/views/flow/stFlowDesign/index.vue';
+import StFormMobileIndex from '/@/views/flow/stFormSetting/MobileIndex.vue';
+import StPageSetting from '/@/views/flow/stPageSetting/index.vue';
+import StPrint from '/@/views/flow/stPrint/index.vue';
+
+const headerList = ref([
+  {
+    title: '数据模型',
+    componentName: StDataIndex,
+    type: 'database',
+  },
+  {
+    title: 'PC表单设计',
+    componentName: StFormIndex,
+    type: 'form',
+  },
+  {
+    title: '移动表单设计',
+    componentName: StFormMobileIndex,
+    type: 'mobileForm',
+  },
+  {
+    title: '流程模型',
+    componentName: StFlowIndex,
+    type: 'branches',
+  },
+  {
+    title: '打印设置',
+    componentName: StPrint,
+    type: 'setting',
+  },
+  {
+    title: '页面设置',
+    componentName: StPageSetting,
+    type: 'setting',
+  },
+]);
+
+const currentRow = ref(undefined);
+
+const open = ref(false);
+
+const currentIndex = ref(0);
+
+const title = ref('');
+const flowCode = ref('');
+
+const visible = ref(false);
+
+const currentComName = ref("");
+
+const showModal = (row) => {
+  currentComName.value = headerList.value[0].componentName;
+  currentRow.value = row;
+  title.value = row.flowName;
+  flowCode.value = row.flowCode
+  open.value = true;
+  visible.value = true;
+};
+const closeModel = () => {
+  open.value = false;
+  visible.value = false;
+};
+
+const switchTag = (tag, idx) => {
+  currentComName.value = tag.componentName
+};
+
+defineExpose({showModal, closeModel});
+</script>
+<style lang="less" scoped>
+.pag-header {
+  display: flex;
+  align-items: center;
+
+  .pg-h-txt {
+    font-size: 20px;
+    font-weight: 600;
+    position: absolute;
+  }
+}
+
+.pag-content {
+  width: 100%;
+  height: 100%;
+  // background: #f4f5f6;
+}
+</style>

+ 235 - 0
src/views/flow/stFlowDesign/components/ConfigPanel/ConfigEdge/index.vue

@@ -0,0 +1,235 @@
+<template>
+  <div class='edge-list'>
+    <div class='nl-header'>
+      <span>线条属性</span>
+    </div>
+    <a-collapse accordion defaultActiveKey='1'>
+      <a-collapse-panel key='1' header='一般选项'>
+        <a-row align='middle' style="margin-bottom: 10px">
+          <a-col :span='1'></a-col>
+          <a-col :span='5'>线条名称</a-col>
+          <a-col :span='17'>
+            <a-input
+              :value='globalGridAttr.label'
+              style='width: 100%'
+              @change='onLabelChange'
+            />
+          </a-col>
+        </a-row>
+        <a-row align='middle'>
+          <a-col :span='1'></a-col>
+          <a-col :span='5'>条件</a-col>
+          <a-col :span='12'>
+            <a-input
+              :value='globalGridAttr.condition'
+              style='width: 100%'
+              @change='onConditionChange'
+            />
+          </a-col>
+          <a-col :span='2'>
+            <a-button type='primary' @click='selectBook'>
+              <CloudOutlined />
+            </a-button> 
+          </a-col>
+        </a-row>
+        <div v-if="globalGridAttr.condition == '1'"></div>
+      </a-collapse-panel>
+      <!--      <a-collapse-panel key="2" header="显示">-->
+      <!--        <a-row align="middle">-->
+      <!--          <a-col :span="1"></a-col>-->
+      <!--          <a-col :span="5">线宽</a-col>-->
+      <!--          <a-col :span="15">-->
+      <!--            <a-slider-->
+      <!--              :min="1"-->
+      <!--              :max="5"-->
+      <!--              :step="1"-->
+      <!--              :value="globalGridAttr.strokeWidth"-->
+      <!--              @change="onStrokeWidthChange"-->
+      <!--            />-->
+      <!--          </a-col>-->
+      <!--          <a-col :span="2">-->
+      <!--            <div class="result">{{ globalGridAttr.strokeWidth }}</div>-->
+      <!--          </a-col>-->
+      <!--        </a-row>-->
+      <!--        <a-row align="middle">-->
+      <!--          <a-col :span="1"></a-col>-->
+      <!--          <a-col :span="5">颜色</a-col>-->
+      <!--          <a-col :span="15">-->
+      <!--            <a-input-->
+      <!--              type="color"-->
+      <!--              :value="globalGridAttr.stroke"-->
+      <!--              style="width: 100%"-->
+      <!--              @change="onStrokeChange"-->
+      <!--            />-->
+      <!--          </a-col>-->
+      <!--        </a-row>-->
+      <!--        <a-row align="middle">-->
+      <!--          <a-col :span="1"></a-col>-->
+      <!--          <a-col :span="5">样式</a-col>-->
+      <!--          <a-col :span="15">-->
+      <!--            <a-select-->
+      <!--              style="width: 100%"-->
+      <!--              :value="globalGridAttr.connector"-->
+      <!--              @change="onConnectorChange"-->
+      <!--            >-->
+      <!--              <a-select-option value="normal">Normal</a-select-option>-->
+      <!--              <a-select-option value="smooth">Smooth</a-select-option>-->
+      <!--              <a-select-option value="rounded">Rounded</a-select-option>-->
+      <!--              <a-select-option value="jumpover">Jumpover</a-select-option>-->
+      <!--            </a-select>-->
+      <!--          </a-col>-->
+      <!--        </a-row>-->
+      <!--      </a-collapse-panel>-->
+    </a-collapse>
+    <div style='height: 80vh'>
+      <selectParticipant ref='sp' @submit='submitParticipant' />
+    </div>
+  </div>
+</template>
+
+<script>
+import FlowGraph from '../../../graph'
+import selectParticipant from '/@/views/flow/stFlowDesign/components/ConfigPanel/selectParticipant.vue'
+import { CloudOutlined } from "@ant-design/icons-vue"
+
+export default {
+  name: 'Index',
+  components: { selectParticipant, CloudOutlined },
+  props: {
+    globalGridAttr: {
+      type: Object,
+      default: null,
+      require: true
+    },
+    id: {
+      type: String,
+      default: null,
+      require: true
+    }
+  },
+  data() {
+    return {
+      curCell: ''
+    }
+  },
+  computed: {
+    edgeIdCpt() {
+      return {
+        id: this.id
+      }
+    }
+  },
+  watch: {
+    // 线条表单属性的初始化
+    edgeIdCpt: {
+      handler(nv) {
+        const { graph } = FlowGraph
+        const cell = graph.getCellById(nv.id)
+        if (!cell || !cell.isEdge()) {
+          return
+        }
+        this.curCell = cell
+        const connector = cell.getConnector() || {
+          name: 'normal'
+        }
+        this.globalGridAttr.stroke = cell.attr('line/stroke')
+        this.globalGridAttr.strokeWidth = cell.attr('line/strokeWidth')
+        this.globalGridAttr.connector = connector.name
+        this.globalGridAttr.label = cell.getLabels()[0]?.attrs.text.text || ''
+        this.globalGridAttr.condition = cell.getData().condition
+      },
+      immediate: false,
+      deep: false
+    }
+  },
+  methods: {
+    onConditionChange(e) {
+      const val = e.target.value
+      this.globalGridAttr.condition = val
+      this.curCell.setData({
+        condition: val
+      })
+      if (val) {
+        this.globalGridAttr.stroke = '#0f89e6'
+        this.curCell.attr('line/stroke', '#0f89e6')
+      } else {
+        this.globalGridAttr.stroke = '#000000'
+        this.curCell.attr('line/stroke', '#000000')
+      }
+
+    },
+    selectBook() {
+      this.$refs.sp.jsonData.participant = this.globalGridAttr.condition
+      this.$refs.sp.showDialog()
+    },
+    submitParticipant(jsonData) {
+      const val = jsonData.participant
+      this.globalGridAttr.condition = val
+      this.curCell.setData({
+        condition: val
+      })
+      if (val) {
+        this.globalGridAttr.stroke = '#0f89e6'
+        this.curCell.attr('line/stroke', '#0f89e6')
+      } else {
+        this.globalGridAttr.stroke = '#000000'
+        this.curCell.attr('line/stroke', '#000000')
+      }
+    },
+    onStrokeWidthChange(val) {
+      this.globalGridAttr.strokeWidth = val
+      this.curCell.attr('line/strokeWidth', val)
+    },
+    onStrokeChange(e) {
+      const val = e.target.value
+      this.globalGridAttr.stroke = val
+      this.curCell.attr('line/stroke', val)
+    },
+    onConnectorChange(val) {
+      this.globalGridAttr.connector = val
+      this.curCell.setConnector(val)
+    },
+    onLabelChange(e) {
+      const val = e.target.value
+      let distance = 0.5
+      if (this.curCell.getLabels().length > 0) {
+        distance = this.curCell.getLabels()[0].position.distance
+      }
+      this.curCell.setLabels([
+        {
+          style: {
+            fill: 'red'
+          },
+          attrs: {
+            fill: '#333',
+            fontSize: 13,
+            text: {
+              text: val
+            }
+          },
+          position: {
+            distance: distance
+          }
+        }
+      ])
+      this.globalGridAttr.label = val
+    }
+  }
+}
+</script>
+
+<style lang='less' scoped>
+.edge-list {
+  width: 100%;
+
+  .nl-header {
+    line-height: 32px;
+    font-size: 20px;
+    font-weight: bold;
+    color: #666;
+    padding-left: 10px;
+    background-color: #e9e9e9 !important;
+    margin-bottom: 6px;
+  }
+}
+</style>

+ 183 - 0
src/views/flow/stFlowDesign/components/ConfigPanel/ConfigGrid/index.vue

@@ -0,0 +1,183 @@
+<template>
+  <div class="grid-list">
+    <div class="nl-header">
+      <span>流程属性</span>
+    </div>
+    <a-collapse accordion defaultActiveKey="1">
+      <a-collapse-panel key="1" header="一般选项">
+        <a-row align="middle" style="margin-bottom: 10px">
+          <a-col :span="10"> 流程名称:</a-col>
+          <a-col :span="12">
+            <a-input :disabled="true" v-model:value="StFlowModal.flowName" placeholder="请输入内容"></a-input>
+          </a-col>
+        </a-row>
+        <a-row align="middle">
+          <a-col :span="10"> 流程编码:</a-col>
+          <a-col :span="12">
+            <a-input :disabled="true" v-model:value="StFlowModal.flowCode" placeholder="请输入内容"></a-input>
+          </a-col>
+        </a-row>
+      </a-collapse-panel>
+      <a-collapse-panel key="2" header="流程动作">
+        <a-row align="middle" style="margin-bottom: 10px">
+          <a-col :span="10"> 流程开始:</a-col>
+          <a-col :span="12">
+            <a-select placeholder='选择业务服务' v-model:value='mainContent.startEvent' style='width: 100%'>
+              <a-select-option v-for='(d, index) in eventOptions' :key='index' :value='d.id'>{{ d.name }}
+              </a-select-option>
+            </a-select>
+          </a-col>
+        </a-row>
+        <a-row align="middle" style="margin-bottom: 10px">
+          <a-col :span="10"> 流程结束:</a-col>
+          <a-col :span="12">
+            <a-select placeholder='选择业务服务' v-model:value='mainContent.endEvent' style='width: 100%'>
+              <a-select-option v-for='(d, index) in eventOptions' :key='index' :value='d.id'>{{ d.name }}
+              </a-select-option>
+            </a-select>
+          </a-col>
+        </a-row>
+        <a-row align="middle">
+          <a-col :span="10"> 流程取消:</a-col>
+          <a-col :span="12">
+            <a-select placeholder='选择业务服务' v-model:value='mainContent.cancelEvent' style='width: 100%'>
+              <a-select-option v-for='(d, index) in eventOptions' :key='index' :value='d.id'>{{ d.name }}
+              </a-select-option>
+            </a-select>
+          </a-col>
+        </a-row>
+      </a-collapse-panel>
+
+    </a-collapse>
+  </div>
+</template>
+
+<script>
+import {gridOpt, backGroundOpt, gridSizeOpt} from "./method";
+import {useFlowStore} from '/@/store/modules/flow';
+// import { mapGetters } from "vuex";
+// import { globalGridAttr } from '../../../models/global'
+
+const GRID_TYPE_ENUM = {
+  DOT: "dot",
+  FIXED_DOT: "fixedDot",
+  MESH: "mesh",
+  DOUBLE_MESH: "doubleMesh",
+};
+const REPEAT_TYPE_ENUM = {
+  NO_REPEAT: "no-repeat",
+  REPEAT: "repeat",
+  REPEAT_X: "repeat-x",
+  REPEAT_Y: "repeat-y",
+  ROUND: "round",
+  SPACE: "space",
+  FLIPX: "flipX",
+  FLIPY: "flipY",
+  FLIPXY: "flipXY",
+  WATERMARK: "watermark",
+};
+export default {
+  name: "Index",
+  props: {
+    globalGridAttr: {
+      type: Object,
+      default: null,
+      required: true,
+    },
+    mainContent: {
+      type: Object,
+      default: {
+        startEvent: ''
+      },
+    },
+    eventOptions: {
+      type: Array,
+    }
+  },
+  data() {
+    return {
+      GRID_TYPE: GRID_TYPE_ENUM,
+      REPEAT_TYPE: REPEAT_TYPE_ENUM,
+    };
+  },
+  mounted() {
+    gridOpt(this.globalGridAttr);
+    gridSizeOpt(this.globalGridAttr);
+    backGroundOpt(this.globalGridAttr);
+  },
+  computed: {
+    StFlowModal() {
+      return useFlowStore().StFlowModal
+    },
+    gridOptCpt() {
+      return {
+        type: this.globalGridAttr.type,
+        color: this.globalGridAttr.color,
+        thickness: this.globalGridAttr.thickness,
+        thicknessSecond: this.globalGridAttr.thicknessSecond,
+        colorSecond: this.globalGridAttr.colorSecond,
+        factor: this.globalGridAttr.factor,
+      };
+    },
+    gridSizeOptCpt() {
+      return {
+        size: this.globalGridAttr.size,
+      };
+    },
+    backGroundOpt() {
+      return {
+        bgColor: this.globalGridAttr.bgColor,
+        showImage: false,
+        repeat: this.globalGridAttr.repeat,
+        angle: this.globalGridAttr.angle,
+        bgSize: this.globalGridAttr.bgSize,
+        position: this.globalGridAttr.position,
+        opacity: this.globalGridAttr.opacity,
+      };
+    },
+  },
+  watch: {
+    // 监听网格变化
+    gridOptCpt: {
+      handler(nv) {
+        gridOpt(nv);
+      },
+      immediate: false,
+      deep: false,
+    },
+    // 监听网格大小变化
+    gridSizeOptCpt: {
+      handler(nv) {
+        gridSizeOpt(nv);
+      },
+      immediate: false,
+      deep: false,
+    },
+    // 监听背景变化
+    backGroundOpt: {
+      handler(nv) {
+        backGroundOpt(nv);
+      },
+      immediate: false,
+      deep: true,
+    },
+  },
+  methods: {}
+};
+</script>
+
+<style lang="less" scoped>
+.grid-list {
+  width: 100%;
+
+  .nl-header {
+    line-height: 32px;
+    font-size: 20px;
+    font-weight: bold;
+    color: #666;
+    padding-left: 10px;
+    background-color: #e9e9e9 !important;
+    margin-bottom: 6px;
+  }
+}
+</style>

+ 60 - 0
src/views/flow/stFlowDesign/components/ConfigPanel/ConfigGrid/method.js

@@ -0,0 +1,60 @@
+import FlowGraph from '../../../graph/index'
+
+export function gridOpt (globalGridAttr) {
+  let options
+  if (globalGridAttr.type === 'doubleMesh') {
+    options = {
+      type: globalGridAttr.type,
+      args: [
+        {
+          color: globalGridAttr.color,
+          thickness: globalGridAttr.thickness
+        },
+        {
+          color: globalGridAttr.colorSecond,
+          thickness: globalGridAttr.thicknessSecond,
+          factor: globalGridAttr.factor
+        }
+      ]
+    }
+  } else {
+    options = {
+      type: globalGridAttr.type,
+      args: [
+        {
+          color: globalGridAttr.color,
+          thickness: globalGridAttr.thickness
+        }
+      ]
+    }
+  }
+  const { graph } = FlowGraph
+  graph.drawGrid(options)
+}
+
+export function gridSizeOpt (globalGridAttr) {
+  const { graph } = FlowGraph
+  graph.setGridSize(globalGridAttr.size)
+}
+
+const tryToJSON = (val) => {
+  try {
+    return JSON.parse(val)
+  } catch (error) {
+    return val
+  }
+}
+
+export function backGroundOpt (globalGridAttr) {
+  const options = {
+    color: globalGridAttr.bgColor,
+    // image: globalGridAttr.showImage ? new URL('/@/assets/logo.svg', import.meta.url).href : undefined,
+    repeat: globalGridAttr.repeat,
+    angle: globalGridAttr.angle,
+    size: tryToJSON(globalGridAttr.bgSize),
+    position: tryToJSON(globalGridAttr.position),
+    opacity: globalGridAttr.opacity
+  }
+  const { graph } = FlowGraph
+  graph.drawBackground(options)
+}

+ 593 - 0
src/views/flow/stFlowDesign/components/ConfigPanel/ConfigNode/index.vue

@@ -0,0 +1,593 @@
+<template>
+  <div class='node-list'>
+    <div class='nl-header'>
+      <span>节点属性</span>
+    </div>
+    <a-collapse accordion defaultActiveKey='1'>
+      <a-collapse-panel key='1' header='一般选项'>
+        <a-row align='middle' style="margin-bottom: 10px">
+          <a-col :span='1'></a-col>
+          <a-col :span='5'>节点名称</a-col>
+          <a-col :span='16'>
+            <a-input
+                :value='globalGridAttr.nodeName'
+                style='width: 100%'
+                @change='onNameChange'
+            />
+          </a-col>
+        </a-row>
+        <a-row align='middle'>
+          <a-col :span='1'></a-col>
+          <a-col :span='5'>节点编码</a-col>
+          <a-col :span='16'>
+            <a-input
+                :value='globalGridAttr.nodeCode'
+                style='width: 100%'
+                @change='onCodeChange'
+                disabled
+            />
+          </a-col>
+        </a-row>
+
+      </a-collapse-panel>
+      <a-collapse-panel
+          v-if='globalGridAttr.nodeType==="2"||globalGridAttr.nodeType==="3"||globalGridAttr.nodeType==="7"' key='2'
+          header='参与者'>
+        <a-row align='middle' style="margin-bottom: 10px">
+          <a-col :span='1'></a-col>
+          <a-col :span='5'>参与者</a-col>
+          <a-col :span='16'>
+            <div style="display: flex; flex-direction: column; align-items: flex-end; gap: 5px">
+              <a-textarea
+                  :auto-size='{ minRows: 5, maxRows: 15 }'
+                  :value='globalGridAttr.participantsName'
+                  style='width: 100%'
+                  :readonly='true'
+              />
+
+              <a-button type='primary' @click='selectBook'>
+                <TeamOutlined/>
+              </a-button>
+            </div>
+          </a-col>
+        </a-row>
+
+        <a-row align='middle' v-if='globalGridAttr.nodeType==="2"||globalGridAttr.nodeType==="3"'
+               style="margin-bottom: 10px">
+          <a-col :span='1'></a-col>
+          <a-col :span='5'>无参与者</a-col>
+          <a-col :span='16'>
+            <a-select
+                style='width: 100%'
+                v-model:value='globalGridAttr.partnerNoUser'
+                @change='onPartnerNoUserChange'
+            >
+              <a-select-option value='0'>不作处理</a-select-option>
+              <a-select-option value='1'>审批通过</a-select-option>
+            </a-select>
+          </a-col>
+        </a-row>
+
+        <a-row align='middle' v-if='globalGridAttr.nodeType==="2"||globalGridAttr.nodeType==="3"'
+               style="margin-bottom: 10px">
+          <a-col :span='1'></a-col>
+          <a-col :span='5'>是发起人</a-col>
+          <a-col :span='16'>
+            <a-select
+                style='width: 100%'
+                v-model:value='globalGridAttr.partnerInitiator'
+                @change='onPartnerInitiatorChange'
+            >
+              <a-select-option value='1'>审批通过</a-select-option>
+              <a-select-option value='0'>不作处理</a-select-option>
+            </a-select>
+          </a-col>
+        </a-row>
+        <a-row align='middle' v-if='globalGridAttr.nodeType==="2"||globalGridAttr.nodeType==="3"'>
+          <a-col :span='1'></a-col>
+          <a-col :span='5'>参与过流程</a-col>
+          <a-col :span='16'>
+            <a-select
+                style='width: 100%'
+                v-model:value='globalGridAttr.partnerAction'
+                @change='onPartnerActionChange'
+            >
+              <a-select-option value='1'>审批通过</a-select-option>
+              <a-select-option value='0'>不作处理</a-select-option>
+            </a-select>
+          </a-col>
+        </a-row>
+      </a-collapse-panel>
+      <a-collapse-panel v-if='globalGridAttr.nodeType==="2"||globalGridAttr.nodeType==="3"' key='3' header='数据权限'>
+        <a-table
+            :columns='DataPowerColumns'
+            :data-source='globalGridAttr.dataPowerData'
+            :pagination="false"
+            :scroll="{ y: 500 }"
+        >
+          <template #bodyCell="{ column, record }">
+
+            <template v-if='column.dataIndex=="view"'>
+              <a-checkbox v-model:checked='record.view'></a-checkbox>
+            </template>
+            <template v-else-if='column.dataIndex=="write"'>
+              <a-checkbox v-model:checked='record.write'></a-checkbox>
+            </template>
+            <template v-else-if='column.dataIndex=="require"'>
+              <a-checkbox v-model:checked='record.require'></a-checkbox>
+            </template>
+          </template>
+        </a-table>
+      </a-collapse-panel>
+      <a-collapse-panel v-if='globalGridAttr.nodeType==="2"||globalGridAttr.nodeType==="3"' key='4' header='操作权限'>
+        <a-checkbox v-model:checked='globalGridAttr.operatePowerData.circulation' @change="circulationChange">传阅
+        </a-checkbox>
+        <a-checkbox v-if="globalGridAttr.nodeType==='2'" v-model:checked='globalGridAttr.operatePowerData.forward'
+                    @change="forwardChange">转发
+        </a-checkbox>
+        <a-checkbox v-if="globalGridAttr.nodeType==='2'" v-model:checked='globalGridAttr.operatePowerData.add'
+                    @change="addChange">加签
+        </a-checkbox>
+        <!--        <a-checkbox v-if="globalGridAttr.nodeType==='2'" v-model:checked='globalGridAttr.operatePowerData.back' @change="backChange">取回</a-checkbox>-->
+        <!--        <a-checkbox v-if="globalGridAttr.nodeType==='2'" v-model:checked='globalGridAttr.operatePowerData.cancel' @change="cancelChange">取消</a-checkbox>-->
+        <a-checkbox v-if="globalGridAttr.nodeType==='2'" v-model:checked='globalGridAttr.operatePowerData.submitToReject'
+                    @change="submitToRejectChange">提交到驳回节点
+        </a-checkbox>
+      </a-collapse-panel>
+      <a-collapse-panel v-if='globalGridAttr.nodeType==="4"' key='6' header='业务服务'>
+        <a-button @click='addEvent'>新增</a-button>
+        <div v-for='(record,index) in globalGridAttr.dataEvent'>
+          <a-select :allowClear="true" placeholder='选择业务服务' v-model:value='record.id' style='width: 100%'>
+            <a-select-option v-for='(d, index) in eventOptions' :key='index' :value='d.id'>{{ d.name }}
+            </a-select-option>
+          </a-select>
+        </div>
+      </a-collapse-panel>
+      <a-collapse-panel v-if='globalGridAttr.nodeType==="5"' key='7' header='参数设置'>
+        <a-row align='middle'>
+          <a-col :span='1'></a-col>
+          <a-col :span='5'>通过条件</a-col>
+          <a-col :span='16'>
+            <a-select
+                style='width: 100%'
+                v-model:value='globalGridAttr.passAction'
+                @change='onPassAction'
+            >
+              <a-select-option value='all'>全部</a-select-option>
+              <a-select-option value='single'>任一</a-select-option>
+            </a-select>
+          </a-col>
+        </a-row>
+      </a-collapse-panel>
+      <a-collapse-panel v-if='globalGridAttr.nodeType==="7"' key='8' header='子流程设置'>
+        <a-row align='middle'>
+          <a-col :span='1'></a-col>
+          <a-col :span='5'>流程模版</a-col>
+          <a-col :span='16'>
+            <a-tree-select placeholder='选择流程模版' v-model:value='globalGridAttr.childFlowData.flowCode'
+                           style='width: 100%'
+                           show-search
+                           :tree-data='flowModules'
+                           tree-node-filter-prop="label"
+                           tree-default-expand-all
+                           @change='handleFlowChange'>
+              <template #title="{ value: val, label }">
+                <b v-if="val === 'parent 1-1'" style="color: #08c">sss</b>
+                <template v-else>{{ label }}</template>
+              </template>
+            </a-tree-select>
+          </a-col>
+        </a-row>
+        <a-row align='middle'>
+          <a-col :span='1'></a-col>
+          <a-col :span='9'>异步</a-col>
+          <a-col :span='12'>
+            <a-checkbox v-model:checked='globalGridAttr.childFlowData.async' @change="asyncChange"></a-checkbox>
+          </a-col>
+        </a-row>
+
+        <a-row align='middle'>
+          <a-col :span='1'></a-col>
+          <a-col :span='9'>结束发起节点</a-col>
+          <a-col :span='12'>
+            <a-checkbox v-model:checked='globalGridAttr.childFlowData.autoSubmit'
+                        @change="autoSubmitChange"></a-checkbox>
+          </a-col>
+        </a-row>
+        <a-row align='middle'>
+          <a-col :span='1'></a-col>
+          <a-col :span='5'>数据映射</a-col>
+          <a-col :span='18'>
+            <a-button @click="addMappingField">添加数据映射</a-button>
+            <div v-for="(map,index) in globalGridAttr.childFlowData.mapping">
+              <a-row class="mapping-field">
+                <a-col :span="20">
+                  主
+                  <a-select
+                      v-model:value="map.mainField"
+                      show-search
+                      placeholder="主表字段"
+                      style="width: 200px"
+                      :options="mainFieldOptions"
+                  ></a-select>
+                  子
+                  <a-select
+                      v-model:value="map.childField"
+                      show-search
+                      placeholder="子表字段"
+                      style="width: 200px"
+                      :options="childFieldOptions"
+                  ></a-select>
+                </a-col>
+                <a-col :span="3">
+                  <a-button shape="circle" @click="delMappingField(index)">
+                    <template #icon>
+                      <DeleteOutlined/>
+                    </template>
+                  </a-button>
+                </a-col>
+
+              </a-row>
+
+            </div>
+          </a-col>
+        </a-row>
+      </a-collapse-panel>
+      <a-collapse-panel key='9' header='显示'>
+        <a-row align='middle'>
+          <a-col :span='8'>线条颜色</a-col>
+          <a-col :span='14'>
+            <a-input
+                type='color'
+                :value='globalGridAttr.nodeStroke'
+                style='width: 100%'
+                @change='onStrokeChange'
+            />
+          </a-col>
+        </a-row>
+        <a-row align='middle'>
+          <a-col :span='8'>线条粗细</a-col>
+          <a-col :span='12'>
+            <a-slider
+                :min='1'
+                :max='5'
+                :step='1'
+                :value='globalGridAttr.nodeStrokeWidth'
+                @change='onStrokeWidthChange'
+            />
+          </a-col>
+          <a-col :span='2'>
+            <div class='result'>{{ globalGridAttr.nodeStrokeWidth }}</div>
+          </a-col>
+        </a-row>
+        <a-row align='middle'>
+          <a-col :span='8'>字体大小</a-col>
+          <a-col :span='12'>
+            <a-slider
+                :min='8'
+                :max='16'
+                :step='1'
+                :value='globalGridAttr.nodeFontSize'
+                @change='onFontSizeChange'
+            />
+          </a-col>
+          <a-col :span='2'>
+            <div class='result'>{{ globalGridAttr.nodeFontSize }}</div>
+          </a-col>
+        </a-row>
+        <a-row align='middle'>
+          <a-col :span='8'>字体颜色</a-col>
+          <a-col :span='14'>
+            <a-input
+                type='color'
+                :value='globalGridAttr.nodeColor'
+                style='width: 100%'
+                @change='onColorChange'
+            />
+          </a-col>
+        </a-row>
+      </a-collapse-panel>
+
+    </a-collapse>
+    <selectParticipant ref='sp' @submit='submitParticipant'/>
+  </div>
+</template>
+
+<script>
+
+const DataPowerColumns = [
+  {title: '数据项', dataIndex: 'field', key: 'field', width: "150px", scopedSlots: {customRender: 'field'}},
+  {title: '可见', dataIndex: 'view', align: "center", scopedSlots: {customRender: 'view'}},
+  {title: '可写', dataIndex: 'write', align: "center", scopedSlots: {customRender: 'write'}},
+  {title: '必填', dataIndex: 'require', align: "center", scopedSlots: {customRender: 'require'}}
+]
+
+
+import {nodeOpt} from './method'
+import selectParticipant from '/@/views/flow/stFlowDesign/components/ConfigPanel/selectParticipant.vue'
+import {getEventAdditiveService} from '/@/api/flow/settingApi'
+import {getChildFlowStFlowMenu} from '/@/api/flow/flowApi'
+import {useFlowStore} from '/@/store/modules/flow'
+import {getFlowDataTemplate} from '/@/api/flow/dataApi'
+import {TeamOutlined} from "@ant-design/icons-vue"
+
+export default {
+  name: 'Index',
+  components: {selectParticipant, TeamOutlined},
+  props: {
+    globalGridAttr: {
+      type: Object,
+      default: null,
+      require: true
+    },
+    id: {
+      type: String,
+      default: null,
+      require: true
+    },
+    eventOptions: {
+      type: Array,
+    }
+  },
+  data() {
+    return {
+      test: '',
+      DataPowerColumns,
+      curCel: '',
+      flowModules: [],
+      mainFieldOptions: [],
+      childFieldOptions: [],
+
+    }
+  },
+  computed: {
+    selectPerson() {
+      return selectPerson
+    },
+    nodeIdCpt() {
+      return {
+        id: this.id
+      }
+    }
+  },
+  watch: {
+    nodeIdCpt: {
+      handler(nv) {
+        nodeOpt(nv, this).then((res) => {
+              this.curCel = res;
+              if (this.globalGridAttr.childFlowData.flowCode && this.childFieldOptions.length == 0) {
+                this.getChildFlowField(this.globalGridAttr.childFlowData.flowCode)
+              }
+            }
+        )
+
+
+      },
+      immediate: false,
+      deep: false
+    }
+  },
+  mounted() {
+    getChildFlowStFlowMenu()
+        .then((res) => {
+          this.flowModules = res.data
+        })
+    if (this.mainFieldOptions.length == 0) {
+      getFlowDataTemplate({
+        flowCode: useFlowStore()['StFlowModal'].flowCode
+      }).then((res) => {
+            if (res.success) {
+              this.mainFieldOptions = []
+              for (let i in res.data) {
+                if (res.data[i].children == null) {
+                  this.mainFieldOptions.push({label: res.data[i].field, value: res.data[i].fieldCode})
+                }
+              }
+            }
+          },
+          (err) => {
+            console.log(err)
+          })
+    }
+
+  },
+  methods: {
+    getChildFlowField(flowCode) {
+      getFlowDataTemplate({
+        flowCode: flowCode
+      }).then((res) => {
+            if (res.success) {
+              this.childFieldOptions = []
+              for (let i in res.data) {
+                if (res.data[i].children == null) {
+                  this.childFieldOptions.push({label: res.data[i].field, value: res.data[i].fieldCode})
+                }
+              }
+            }
+          },
+          (err) => {
+            console.log(err)
+          })
+
+    },
+    addEvent() {
+      this.globalGridAttr.dataEvent.push({id: '', name: '', url: ''})
+    },
+    onChange() {
+    },
+    onStrokeChange(e) {
+      const val = e.target.value
+      this.globalGridAttr.nodeStroke = val
+      this.curCel.attr('body/stroke', val)
+    },
+    onStrokeWidthChange(val) {
+      this.globalGridAttr.nodeStrokeWidth = val
+      this.curCel.attr('body/strokeWidth', val)
+    },
+    onFillChange(e) {
+      const val = e.target.value
+      this.globalGridAttr.nodeFill = val
+      this.curCel.attr('body/fill', val)
+    },
+    onFontSizeChange(val) {
+      this.globalGridAttr.nodeFontSize = val
+      this.curCel.attr('title/fontSize', val)
+    },
+    onColorChange(e) {
+      const val = e.target.value
+      this.globalGridAttr.nodeColor = val
+      this.curCel.attr('title/fill', val)
+    },
+    submitParticipant(jsonData) {
+      const users = []
+      const val = jsonData.participant
+      const valName = jsonData.participantName
+      this.globalGridAttr.participants = val
+      this.globalGridAttr.participantsName = valName
+      users.push(val)
+      this.curCel.setData({
+        participants: users
+      })
+    },
+    selectBook() {
+      this.$refs.sp.jsonData.participant = this.globalGridAttr.participants
+      this.$refs.sp.showDialog()
+    },
+    onNameChange(e) {
+      const val = e.target.value
+      this.globalGridAttr.nodeName = val
+      this.curCel.attr('title/textWrap/text', val)
+    },
+    onCodeChange(e) {
+      const val = e.target.value
+      this.globalGridAttr.nodeCode = val
+      this.curCel.setData({
+        code: val
+      })
+    },
+    onIndexChange(e) {
+      const val = e.target.value
+      this.globalGridAttr.nodeIndex = val
+      this.curCel.setZIndex(val)
+    },
+
+    onPartnerNoUserChange(value) {
+      this.globalGridAttr.partnerNoUser = value
+      this.curCel.setData({
+        partnerNoUser: value
+      })
+    },
+    onPartnerInitiatorChange(value) {
+
+      this.globalGridAttr.partnerInitiator = value
+      this.curCel.setData({
+        partnerInitiator: value
+      })
+    },
+    onPartnerActionChange(value) {
+      this.globalGridAttr.partnerAction = value
+      this.curCel.setData({
+        partnerAction: value
+      })
+    },
+    forwardChange(e) {
+      this.globalGridAttr.operatePowerData.forward = e.target.checked
+      this.curCel.setData({
+        operatePowerData: this.globalGridAttr.operatePowerData
+      })
+    },
+
+    handleFlowChange(flowCode) {
+      this.globalGridAttr.childFlowData.flowCode = flowCode
+      this.curCel.setData({
+        childFlowData: this.globalGridAttr.childFlowData
+      })
+      this.getChildFlowField(flowCode)
+    },
+    asyncChange(e) {
+      this.globalGridAttr.childFlowData.async = e.target.checked
+      this.curCel.setData({
+        childFlowData: this.globalGridAttr.childFlowData
+      })
+    },
+    autoSubmitChange(e) {
+      this.globalGridAttr.childFlowData.autoSubmit = e.target.checked
+      this.curCel.setData({
+        childFlowData: this.globalGridAttr.childFlowData
+      })
+    },
+    addMappingField() {
+      this.globalGridAttr.childFlowData.mapping.push({})
+      this.curCel.setData({
+        childFlowData: this.globalGridAttr.childFlowData
+      })
+    },
+    delMappingField(index) {
+      this.globalGridAttr.childFlowData.mapping.splice(index, 1)
+      this.curCel.setData({
+        childFlowData: this.globalGridAttr.childFlowData
+      })
+    },
+    addChange(e) {
+      this.globalGridAttr.operatePowerData.add = e.target.checked
+      this.curCel.setData({
+        operatePowerData: this.globalGridAttr.operatePowerData
+      })
+    },
+    circulationChange(e) {
+      this.globalGridAttr.operatePowerData.circulation = e.target.checked
+      this.curCel.setData({
+        operatePowerData: this.globalGridAttr.operatePowerData
+      })
+    },
+    backChange(e) {
+      this.globalGridAttr.operatePowerData.back = e.target.checked
+      this.curCel.setData({
+        operatePowerData: this.globalGridAttr.operatePowerData
+      })
+    },
+    cancelChange(e) {
+      this.globalGridAttr.operatePowerData.cancel = e.target.checked
+      this.curCel.setData({
+        operatePowerData: this.globalGridAttr.operatePowerData
+      })
+    },
+    submitToRejectChange(e) {
+      this.globalGridAttr.operatePowerData.submitToReject = e.target.checked
+      this.curCel.setData({
+        operatePowerData: this.globalGridAttr.operatePowerData
+      })
+    },
+
+    onPassAction(value) {
+      this.globalGridAttr.passAction = value
+      this.curCel.setData({
+        passAction: value
+      })
+    },
+
+
+  }
+}
+</script>
+
+<style lang='less' scoped>
+.node-list {
+  width: 100%;
+
+  .nl-header {
+    line-height: 32px;
+    font-size: 20px;
+    font-weight: bold;
+    color: #666;
+    padding-left: 10px;
+    background-color: #e9e9e9 !important;
+    margin-bottom: 6px;
+  }
+}
+
+.mapping-field {
+  margin: 6px;
+  border: 1px solid #d7d7d7; /* 底部边框,点线 */
+}
+</style>

+ 124 - 0
src/views/flow/stFlowDesign/components/ConfigPanel/ConfigNode/method.js

@@ -0,0 +1,124 @@
+import FlowGraph from '../../../graph/index'
+import {getFlowDataTemplate} from '/@/api/flow/dataApi'
+import {getParticipantName} from '/@/api/flow/formApi'
+import {useFlowStore} from '/@/store/modules/flow'
+
+// 节点表单属性的初始化
+export async function nodeOpt(id, self) {
+    let globalGridAttr = self.globalGridAttr
+    let dataPower = []
+    await getFlowDataTemplate({
+        flowCode: useFlowStore()['StFlowModal'].flowCode
+    }).then((res) => {
+            if (res.success) {
+                dataPower = res.data
+            }
+        },
+        (err) => {
+            console.log(err)
+        })
+    let curCel = null
+    if (id) {
+        const {graph} = FlowGraph
+        const cell = graph.getCellById(id)
+        if (!cell || !cell.isNode()) {
+            return
+        }
+        curCel = cell
+        globalGridAttr.nodeStroke = cell.attr('body/stroke')
+        globalGridAttr.nodeStrokeWidth = cell.attr('body/strokeWidth')
+        globalGridAttr.nodeFill = cell.attr('body/fill')
+        globalGridAttr.nodeFontSize = cell.attr('title/fontSize')
+        globalGridAttr.nodeColor = cell.attr('title/fill')
+        globalGridAttr.nodeUsers = cell.attr('approve/users')
+        globalGridAttr.nodeName = cell.attr('title/textWrap/text')
+        globalGridAttr.nodeCode = cell.getData().code
+        globalGridAttr.nodeType = cell.getData().nodeType
+        globalGridAttr.nodeIndex = cell.getZIndex()
+        if (globalGridAttr.nodeType == '4') {
+            //业务动作
+            globalGridAttr.dataEvent = cell.getData().dataEvent !== undefined ? cell.getData().dataEvent : []
+        }
+        if (globalGridAttr.nodeType == '2' || globalGridAttr.nodeType == '3' || globalGridAttr.nodeType == '7') {
+            globalGridAttr.participants = cell.getData().participants != undefined
+            && cell.getData().participants.length > 0 ? cell.getData().participants[0] : ''
+
+            getParticipantName({participantsStr: globalGridAttr.participants}).then((res) => {
+                globalGridAttr.participantsName = res.data
+            })
+        }
+        if (globalGridAttr.nodeType == '2' || globalGridAttr.nodeType == '3') {
+
+            globalGridAttr.dataPowerData = cell.getData().dataPowerData
+            globalGridAttr.operatePowerData = cell.getData().operatePowerData
+            if (globalGridAttr.operatePowerData == null || globalGridAttr.operatePowerData == undefined) {
+                globalGridAttr.operatePowerData = {
+                    forward: false,
+                    add: false,
+                    circulation: false,
+                    back: false,
+                    cancel: false,
+                    submitToReject:false,
+                }
+            }
+
+            const hasAEqualTo = (array, value) => {
+                return array.find(obj => obj.fieldCode === value) || null
+            }
+            for (let i in dataPower) {
+                const some = hasAEqualTo(globalGridAttr.dataPowerData, dataPower[i].fieldCode)
+                if (some == null) {
+                    //如果主表没有这个字段,则插入
+                    globalGridAttr.dataPowerData.push(
+                        {
+                            field: dataPower[i].field,
+                            children: dataPower[i].children,
+                            fieldCode: dataPower[i].fieldCode,
+                            view: true,
+                            write: false,
+                            require: false
+                        })
+                } else {
+                    //如果主表有这个字段,则查看是否有子表
+                    if (some.children) {
+                        //有子表,则查看子表是否有这个字段
+                        for (let j in dataPower[i].children) {
+                            const someChild = hasAEqualTo(some.children, dataPower[i].children[j].fieldCode)
+                            if (someChild == null) {
+                                //如果子表没有这个字段,则插入
+                                some.children.push(
+                                    {
+                                        field: dataPower[i].children[j].field,
+                                        fieldCode: dataPower[i].children[j].fieldCode
+                                    })
+                            }
+                        }
+                    }
+                }
+            }
+            globalGridAttr.partnerNoUser = cell.getData().partnerNoUser
+            globalGridAttr.partnerInitiator = cell.getData().partnerInitiator
+            globalGridAttr.partnerPreAction = cell.getData().partnerPreAction
+            globalGridAttr.partnerAction = cell.getData().partnerAction
+        }
+        if (globalGridAttr.nodeType == '7') {
+            globalGridAttr.childFlowData = cell.getData().childFlowData
+            if (globalGridAttr.childFlowData == null || globalGridAttr.childFlowData == undefined) {
+                globalGridAttr.childFlowData = {
+                    flowCode: '',//子流程编码
+                    async: true,//异步?
+                    autoSubmit: false,//发起环节自动提交
+                    mapping: [],
+                }
+            }
+        }
+
+        if (globalGridAttr.nodeType == '5') {
+            //连接点
+            globalGridAttr.passAction = cell.getData().passAction
+
+        }
+
+    }
+    return curCel
+}

+ 36 - 0
src/views/flow/stFlowDesign/components/ConfigPanel/index.less

@@ -0,0 +1,36 @@
+//.config :global(.ant-row) {
+//  line-height: 32px;
+//  margin: 8px 0;
+//  :global(.result) {
+//    background: #eee;
+//    color: #333333;
+//    padding: 3px 7px;
+//    border-radius: 10px;
+//    display: inline-block;
+//    font-size: 12px;
+//    margin-left: 8px;
+//    line-height: 1.25;
+//    margin-top: 8px;
+//  }
+//}
+
+.config .ant-row {
+  line-height: 32px;
+  margin: 8px 0;
+
+  .result {
+    background: #eee;
+    color: #666;
+    padding: 3px 7px;
+    border-radius: 10px;
+    display: inline-block;
+    font-size: 16px;
+    margin-left: 8px;
+    line-height: 1.25;
+    margin-top: 8px;
+  }
+}
+g{
+  margin: 10px;
+}
+

+ 48 - 0
src/views/flow/stFlowDesign/components/ConfigPanel/index.vue

@@ -0,0 +1,48 @@
+<template>
+  <div class='config'>
+    <ConfigGrid v-show="type === 'grid'" :eventOptions="eventOptions" :mainContent="mainContent" :globalGridAttr="globalGridAttr" :id="id"/>
+    <ConfigNode v-show="type === 'node'" :eventOptions="eventOptions" :globalGridAttr="globalGridAttr" :id="id"/>
+    <ConfigEdge v-show="type === 'edge'" :globalGridAttr="globalGridAttr" :id="id"/>
+  </div>
+</template>
+
+<script setup>
+import {ref, onMounted} from 'vue';
+import ConfigGrid from './ConfigGrid/index.vue';
+import ConfigNode from './ConfigNode/index.vue';
+import ConfigEdge from './ConfigEdge/index.vue';
+import FlowGraph from '../../graph';
+import {globalGridAttr as globalGridConfig} from '../../models/global';
+
+const props = defineProps(['mainContent','eventOptions'])
+const type = ref('grid');
+const id = ref('');
+const globalGridAttr = ref(globalGridConfig);
+const mainContent = ref({});
+const eventOptions = ref([]);
+
+
+onMounted(() => {
+  mainContent.value = props.mainContent
+  eventOptions.value = props.eventOptions
+  setTimeout(boundEvent, 200);
+});
+
+const boundEvent = () => {
+  const {graph} = FlowGraph;
+  graph.on('blank:click', () => {
+    type.value = 'grid';
+  });
+  graph.on('cell:click', ({cell}) => {
+    type.value = cell.isNode() ? 'node' : 'edge';
+    id.value = cell.id;
+
+
+    console.log("id", id.value);
+  });
+};
+</script>
+
+<style lang="less" scoped>
+/* 样式代码保持不变 */
+</style>

+ 257 - 0
src/views/flow/stFlowDesign/components/ConfigPanel/selectParticipant.vue

@@ -0,0 +1,257 @@
+<template>
+  <a-modal
+      title='属性选择'
+      width='60%'
+      :open='modal1Visible'
+      @ok='() => submitDataScope()'
+      @cancel='() => closeDialog()'
+  >
+
+    <a-layout>
+      <a-layout>
+        <a-layout-content>
+          <a-row>
+            <a-col class='sx-col' :span='12'>
+              <a-collapse accordion class='sx-left'>
+                <a-collapse-panel key='1' header='人员组织'>
+                  <a-tree :show-line="true" :show-icon="true"
+                          :fieldNames="{children:'children', title:'label', key:'value' }"
+                          :tree-data='leftParticipantData' @select='onPSelect'
+                          class="tree-height">
+                    <template #icon="{ type }">
+                      <template v-if="type=='emp'">
+                        <UserOutlined/>
+                      </template>
+                      <template v-else-if="type=='post'">
+                        <SolutionOutlined/>
+                      </template>
+                      <template v-else>
+                        <TeamOutlined/>
+                      </template>
+                    </template>
+                  </a-tree>
+                </a-collapse-panel>
+                <a-collapse-panel key='2' header='表单数据'>
+                  <a-tree show-icon :tree-line="true" :fieldNames="{children:'children', title:'name', key:'code' }"
+                          :tree-data='leftFieldData' @select='onFSelect'class="tree-height"></a-tree>
+                </a-collapse-panel>
+                <a-collapse-panel key='3' header='系统数据'>
+                  <a-tree show-icon :tree-line="true" :tree-data='leftSysData' @select='onSSelect'
+                          class="tree-height" />
+                </a-collapse-panel>
+              </a-collapse>
+
+            </a-col>
+            <a-col :span='12'>
+
+              <a-row class='sx-row'>
+                <div class='row-title'>流程参与者修改</div>
+                <a-textarea
+                    spellcheck='false'
+                    class='content-text'
+                    v-model:value='jsonData.participant'
+                    @change='participantChanged'
+                    :auto-size='{ minRows: 5, maxRows: 10 }'
+                />
+              </a-row>
+              <a-row class='sx-row'>
+                <div class='row-title'>流程参与者</div>
+                <a-textarea
+                    :readonly='true'
+                    spellcheck='false'
+                    class='content-text'
+                    v-model:value='jsonData.participantName'
+                    placeholder=''
+                    :auto-size='{ minRows: 5, maxRows: 10 }'
+                />
+              </a-row>
+            </a-col>
+          </a-row>
+
+
+        </a-layout-content>
+      </a-layout>
+      <a-layout-footer></a-layout-footer>
+    </a-layout>
+  </a-modal>
+</template>
+<script>
+import {buildParticipantTree, getParticipantName} from '/@/api/flow/formApi'
+import {getDataTemplate} from '/@/api/flow/dataApi'
+import {useFlowStore} from '/@/store/modules/flow'
+
+
+export default {
+  data() {
+    return {
+      leftSysData: [
+        {
+          title: '发起人',
+          key: '[SySOriginator]'
+        },
+        {
+          title: '发起人部门负责人',
+          key: '[SySOriginatorManager]'
+        },
+        {
+          title: "用户上级岗位:$GetPostByCode([人/部门],'岗位名称')",
+          key: "$GetPostByCode(,'')"
+        },
+        {
+          title: '用户所对应的部门的领导:$UserDeptManager(userCode1)',
+          key: '$UserDeptManager()'
+        },
+        {
+          title: '部门所对应的领导:$DeptManager(deptCode1)',
+          key: '$DeptManager()'
+        },
+        {
+          title: '条件判断IF(?,?,?)',
+          key: 'IF({project_id}==1,[code1]|[code2],[code3]|[code4])'
+        }
+      ],
+      modal1Visible: false,
+      leftParticipantData: [],
+      leftFieldData: [],
+      jsonData: {
+        participant: '',
+        participantName: ''
+      }
+    }
+  },
+  computed: {
+    currentFlowModal() {
+      return useFlowStore().StFlowModal
+    }
+  },
+  created() {
+    const self = this;
+    // TODO 为什么走了两遍created?
+    buildParticipantTree().then((res) => {
+      self.leftParticipantData = res.data
+    })
+
+    getDataTemplate({flowCode: this.currentFlowModal.flowCode}).then(
+        res => {
+          if (res.data !== null) {
+            self.leftFieldData = JSON.parse(res.data.content)
+          }
+        }, err => {
+          console.log(err)
+        })
+  },
+  methods: {
+    participantChanged() {
+      this.initParticipantName()
+    },
+    initParticipantName() {
+      if (this.jsonData.participant && this.jsonData.participant.length > 0) {
+        getParticipantName({participantsStr: this.jsonData.participant}).then((res) => {
+          this.jsonData.participantName = res.data
+        })
+      } else {
+        this.jsonData.participantName = ''
+      }
+    },
+    onPSelect(selectedKeys, info) {
+
+      if (selectedKeys.length > 0) {
+        if (this.jsonData.participant !== undefined && this.jsonData.participant != null && this.jsonData.participant.length > 0) {
+          this.jsonData.participant += ';[' + selectedKeys[0] + ']'
+        } else {
+          this.jsonData.participant = '[' + selectedKeys[0] + ']'
+        }
+        this.initParticipantName()
+      }
+    },
+    onFSelect(selectedKeys, info) {
+      if (selectedKeys.length > 0) {
+        if (this.jsonData.participant !== undefined && this.jsonData.participant != null && this.jsonData.participant.length > 0) {
+          this.jsonData.participant += ';{' + selectedKeys[0] + '}'
+        } else {
+          this.jsonData.participant = '{' + selectedKeys[0] + '}'
+        }
+        this.initParticipantName()
+      }
+    },
+    onSSelect(selectedKeys, info) {
+      if (selectedKeys.length > 0) {
+        if (this.jsonData.participant !== undefined && this.jsonData.participant != null && this.jsonData.participant.length > 0) {
+          this.jsonData.participant += ';' + selectedKeys[0] + ''
+        } else {
+          this.jsonData.participant = '' + selectedKeys[0] + ''
+        }
+        this.initParticipantName()
+      }
+    },
+    submitDataScope() {
+      this.initParticipantName()
+      this.$emit('submit', this.jsonData)
+      this.closeDialog()
+    },
+    showDialog() {
+      this.modal1Visible = true
+      this.initParticipantName()
+    },
+    closeDialog() {
+      this.modal1Visible = false
+    }
+  }
+}
+</script>
+<style lang='less' scoped>
+
+//:deep(.anticon.anticon-file.ant-tree-switcher-line-icon) {
+//  display: none;
+//}
+:deep(.ant-modal-body) {
+  max-height: calc(80vh - 150px);
+  min-height: calc(80vh - 150px);
+  overflow-y: auto;
+
+  &::-webkit-scrollbar {
+    width: 10px;
+    height: 1px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: #e3e3e6;
+    border-radius: 6px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: transparent;
+    border-radius: 5px;
+  }
+}
+
+.sx-col {
+  padding: 10px;
+}
+
+.sx-row {
+  padding: 10px;
+}
+
+.row-title {
+  font-size: 18px;
+  font-weight: bold;
+  padding: 10px 0px;
+}
+
+:deep(.content-text) {
+  font-size: 18px;
+  font-weight: bold;
+  font-family: Consolas;
+}
+
+:deep(.sx-left) {
+  padding: 10px;
+  font-size: 18px;
+  font-weight: bold;
+}
+:deep(.tree-height) {
+  height: 40vh;
+  overflow-y: auto;
+}
+</style>

+ 367 - 0
src/views/flow/stFlowDesign/components/ToolBar/index.vue

@@ -0,0 +1,367 @@
+<template>
+  <div class="bar">
+    <a-tooltip placement="bottom">
+      <template #title>
+        <span>清除 (Cmd + D)</span>
+      </template>
+      <a-button name="delete" @click="handleClick" class="item-space" size="small">
+        <template #icon>
+          <DeleteOutlined/>
+        </template>
+      </a-button>
+    </a-tooltip>
+
+    <a-tooltip placement="bottom">
+      <template #title>
+        <span>撤销 (Cmd + Z)</span>
+      </template>
+      <a-button :disabled="!canUndo" name="undo" @click="handleClick" class="item-space" size="small">
+        <template #icon>
+          <UndoOutlined/>
+        </template>
+      </a-button>
+    </a-tooltip>
+
+    <a-tooltip placement="bottom">
+      <template #title>
+        <span>Redo (Cmd + Shift + Z)</span>
+      </template>
+      <a-button :disabled="!canRedo" name="redo" @click="handleClick" class="item-space" size="small">
+        <template #icon>
+          <RedoOutlined/>
+        </template>
+      </a-button>
+    </a-tooltip>
+
+    <a-tooltip placement="bottom">
+      <template #title>
+        <span>保存PNG (Cmd + S)</span>
+      </template>
+      <a-button name="savePNG" @click="handleClick" class="item-space" size="small">
+        <template #icon>
+          <DownloadOutlined/>
+        </template>
+        png
+      </a-button
+      >
+    </a-tooltip>
+
+<!--    <a-tooltip placement="bottom">-->
+<!--      <template #title>-->
+<!--        <span>保存 (Cmd + P)</span>-->
+<!--      </template>-->
+<!--      <a-button name="toJSON" @click="handleClick" class="item-space" size="small">-->
+<!--        <template #icon>-->
+<!--          <SaveOutlined/>-->
+<!--        </template>-->
+<!--        保存-->
+<!--      </a-button-->
+<!--      >-->
+<!--    </a-tooltip>-->
+
+    <a-tooltip placement="bottom">
+      <template #title>
+        <span>发布</span>
+      </template>
+      <a-button name="submit" @click="handleClick" class="item-space" size="small">
+        <template #icon>
+          <SendOutlined/>
+        </template>
+        发布
+      </a-button
+      >
+    </a-tooltip>
+    <a-tooltip placement="bottom">
+      <template #title>
+        <span>历史版本</span>
+      </template>
+      <a-button name="submit" @click="handleHistory" class="item-space" size="small">
+        <template #icon>
+          <HistoryOutlined/>
+        </template>
+        历史版本
+      </a-button
+      >
+    </a-tooltip>
+    <a-drawer :width="600" v-model:open="openHistory">
+      <a-table :pagination="false" :columns="columns" :data-source="historyData" bordered size="small">
+        <template #bodyCell="{ column, text, record }">
+          <template v-if="column.dataIndex === 'operation'">
+            <a @click="useTemplate(record)">引用</a>
+          </template>
+        </template>
+      </a-table>
+    </a-drawer>
+  </div>
+</template>
+
+<script setup>
+import {ref, onMounted} from 'vue';
+import FlowGraph from '../../graph';
+import {DataUri} from '@antv/x6';
+import {saveOrSubmitStFlowTemp, getFlowTemplateHistory, getFlowTemplateForVersion} from '/@/api/flow/flowApi';
+import {Modal, message} from 'ant-design-vue';
+import {useFlowStore} from '/@/store/modules/flow';
+
+const props = defineProps(['mainContent'])
+import {
+  DeleteOutlined,
+  UndoOutlined,
+  RedoOutlined,
+  DownloadOutlined,
+  PrinterOutlined,
+  SaveOutlined,
+  SendOutlined
+} from '@ant-design/icons-vue'
+
+const columns = [
+  {
+    title: '发布人',
+    dataIndex: 'createBy',
+    align: 'center',
+  },
+  {
+    title: '发布时间',
+    dataIndex: 'createTime',
+    align: 'center',
+  },
+  {
+    title: '版本号',
+    dataIndex: 'version',
+    align: 'center',
+  },
+  {
+    title: '操作',
+    dataIndex: 'operation',
+    align: 'center',
+  },
+];
+const handleHistory = () => {
+  getFlowTemplateHistory({flowCode: useFlowStore()['StFlowModal'].flowCode}).then((res) => {
+    historyData.value = res.data
+    openHistory.value = true
+  })
+}
+const useTemplate = (record) => {
+  let version = record.version.toString()
+  getFlowTemplateForVersion({
+    flowCode: useFlowStore()['StFlowModal'].flowCode,
+    version: version.replace('(当前)', '')
+  }).then((res) => {
+    const {graph} = FlowGraph;
+    const graphData = JSON.parse(res.data.content);
+    graph.fromJSON(graphData);
+    graph.zoomToFit({maxScale: 1});
+  })
+}
+
+const canUndo = ref(false);
+const canRedo = ref(false);
+const openHistory = ref(false);
+const historyData = ref({})
+
+
+onMounted(() => {
+  setTimeout(() => {
+    initEvent();
+  }, 200);
+});
+
+function initEvent() {
+  const {graph} = FlowGraph;
+
+  graph.on('history:change', () => {
+    canUndo.value = graph.canUndo();
+    canRedo.value = graph.canRedo();
+  });
+
+  graph.bindKey('ctrl+z', () => {
+    if (graph.canUndo()) {
+      graph.undo();
+    }
+    return false;
+  });
+  graph.bindKey('ctrl+shift+z', () => {
+    if (graph.canRedo()) {
+      graph.redo();
+    }
+    return false;
+  });
+  graph.bindKey('ctrl+d', () => {
+    graph.clearCells();
+    return false;
+  });
+  graph.bindKey('ctrl+s', () => {
+    graph.toPNG((datauri) => {
+      DataUri.downloadDataUri(datauri, 'chart.png');
+    });
+    return false;
+  });
+  graph.bindKey('ctrl+p', () => {
+    graph.printPreview();
+    return false;
+  });
+  graph.bindKey('ctrl+c', copy);
+  graph.bindKey('ctrl+v', paste);
+  graph.bindKey('ctrl+x', cut);
+}
+
+function copy() {
+  const {graph} = FlowGraph;
+  const cells = graph.getSelectedCells();
+  if (cells.length) {
+    graph.copy(cells);
+  }
+  return false;
+}
+
+function cut() {
+  const {graph} = FlowGraph;
+  const cells = graph.getSelectedCells();
+  if (cells.length) {
+    graph.cut(cells);
+  }
+  return false;
+}
+
+function paste() {
+  const {graph} = FlowGraph;
+  if (!graph.isClipboardEmpty()) {
+    const cells = graph.paste({offset: 32});
+    graph.cleanSelection();
+    graph.select(cells);
+  }
+  return false;
+}
+
+const handleClick = (event) => {
+  const {graph} = FlowGraph;
+  const name = event.currentTarget.name;
+  switch (name) {
+    case 'undo':
+      graph.undo();
+      break;
+    case 'redo':
+      graph.redo();
+      break;
+    case 'delete':
+      graph.clearCells();
+      break;
+    case 'savePNG':
+      graph.toPNG(
+          (dataUri) => {
+            DataUri.downloadDataUri(dataUri, 'chartx.png');
+          },
+          {
+            backgroundColor: 'white',
+            padding: {
+              top: 20,
+              right: 30,
+              bottom: 40,
+              left: 50,
+            },
+            quality: 1,
+          }
+      );
+      break;
+    case 'print':
+      graph.printPreview();
+      break;
+    case 'copy':
+      copy();
+      break;
+    case 'cut':
+      cut();
+      break;
+    case 'paste':
+      paste();
+      break;
+    case 'toJSON':
+      let JSONData = graph.toJSON();
+      checkTargetAndSourceEdges(JSONData);
+      const JSONStr = JSON.stringify(JSONData);
+      StFlowTemp('0', JSONStr, JSON.stringify(props.mainContent));
+      break;
+    case 'submit':
+      let JSONData1 = graph.toJSON();
+      checkTargetAndSourceEdges(JSONData1);
+      const JSONStr1 = JSON.stringify(JSONData1);
+      StFlowTemp('1', JSONStr1, JSON.stringify(props.mainContent));
+      break;
+    default:
+      break;
+  }
+};
+
+function checkTargetAndSourceEdges(graphData) {
+  const cellIds = [];
+  graphData.cells.forEach((item) => {
+    cellIds.push(item.id);
+  });
+
+  graphData.cells.forEach((item) => {
+    let tArr = [];
+    let sArr = [];
+    if (item.data.targetEdges && item.data.targetEdges.length > 0) {
+      cellIds.forEach((zItem) => {
+        item.data.targetEdges.forEach((el, index) => {
+          if (el == zItem) {
+            tArr.push(el);
+          }
+        });
+      });
+      item.data.targetEdges = tArr;
+    }
+    if (item.data.sourceEdges && item.data.sourceEdges.length > 0) {
+      cellIds.forEach((zItem) => {
+        item.data.sourceEdges.forEach((el, index) => {
+          if (el == zItem) {
+            sArr.push(el);
+          }
+        });
+      });
+      item.data.sourceEdges = sArr;
+    }
+  });
+}
+
+function StFlowTemp(type, jsonStr, mainContent) {
+  const self = this;
+  Modal.confirm({
+    title: '提示',
+    content: `确认要${type == '0' ? '保存' : '发布'}?`,
+    okText: '确认',
+    cancelText: '取消',
+    onOk() {
+      saveOrSubmitStFlowTemp({
+        saveOrPublish: type,
+        body: {
+          mainContent: mainContent,
+          content: jsonStr,
+          flowCode: useFlowStore()['StFlowModal'].flowCode,
+        },
+      }).then((resData) => {
+        if (resData.success) {
+          message.success('操作成功');
+        } else {
+          message.error(resData.message);
+        }
+      });
+    },
+    onCancel() {
+      message.warning('取消操作');
+    },
+  });
+}
+</script>
+
+<style lang="less" scoped>
+button {
+  margin-right: 8px;
+}
+
+.bar {
+  margin-left: 16px;
+  margin-right: 16px;
+}
+</style>

File diff suppressed because it is too large
+ 0 - 0
src/views/flow/stFlowDesign/data.js


+ 702 - 0
src/views/flow/stFlowDesign/graph/index.js

@@ -0,0 +1,702 @@
+import {Graph, FunctionExt, Shape} from '@antv/x6';
+import './shape';
+import {getStFlowTemplate} from '/@/api/flow/flowApi';
+import {Stencil} from '@antv/x6-plugin-stencil';
+import {Keyboard} from '@antv/x6-plugin-keyboard';
+import {Scroller} from '@antv/x6-plugin-scroller';
+import {Selection} from '@antv/x6-plugin-selection';
+import {History} from '@antv/x6-plugin-history';
+import {Clipboard} from '@antv/x6-plugin-clipboard';
+import {MiniMap} from '@antv/x6-plugin-minimap';
+import {Snapline} from '@antv/x6-plugin-snapline';
+import {Export} from '@antv/x6-plugin-export';
+import {useFlowStore} from '/@/store/modules/flow';
+import {message} from 'ant-design-vue';
+
+
+const NodeType = Object.freeze({
+    // 开始
+    Start: {
+        name: '开始',
+        nodeType: '0',
+    },
+    // 填写
+    Fillout: {
+        name: '填写',
+        nodeType: '1',
+    },
+    // 审核
+    check: {
+        name: '审核',
+        nodeType: '2',
+    },
+    // 传阅
+    Circulate: {
+        name: '传阅',
+        nodeType: '3',
+    },
+    // 业务动作
+    BizAction: {
+        name: '业务动作',
+        nodeType: '4',
+    },
+    // 连接点
+    Junction: {
+        name: '连接点',
+        nodeType: '5',
+    },
+    // 连接线
+    Line: {
+        name: '连接线',
+        nodeType: '6',
+    },
+    // 子流程
+    ChildFlow: {
+        name: '子流程',
+        nodeType: '7',
+    },
+    // 结束
+    End: {
+        name: '结束',
+        nodeType: '9',
+    },
+
+
+});
+
+export default class FlowGraph {
+    _graphData = {};
+
+    static init(content) {
+        this.graph = new Graph({
+            container: document.getElementById('flowContainer'),
+            background: {color: '#ffffff'},
+            width: 1400,
+            height: 1800,
+            interacting: {
+                edgeLabelMovable: true, // 允许边标签拖拽
+                nodeMovable: true
+            },
+            panning: {
+                enabled: true,
+                modifiers: ['ctrl'],
+                eventTypes: ['leftMouseDown'],
+            },
+            // 网格配置
+            grid: {
+                size: 10,
+                visible: true,
+                type: 'doubleMesh',
+                args: [
+                    {
+                        color: '#cccccc',
+                        thickness: 1,
+                    },
+                    {
+                        color: '#5F95FF',
+                        thickness: 1,
+                        factor: 4,
+                    },
+                ],
+            },
+            //
+            mousewheel: {
+                enabled: true,
+                modifiers: ['ctrl', 'meta'],
+                minScale: 0.3,
+                maxScale: 3,
+            },
+            connecting: {
+                anchor: 'center',
+                allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点,
+                allowMulti: false, // 是否允许在相同的起始节点和终止之间创建多条边
+                connectionPoint: 'anchor',
+                allowBlank: false, // 是否允许连接到空白点
+                highlight: true, // 拖动边时,是否高亮显示所有可用的连接桩或节点
+                snap: true,
+                createEdge() {
+                    return new Shape.Edge({
+                        attrs: {
+                            line: {
+                                stroke: '#1f313f',
+                                strokeWidth: 2,
+                                targetMarker: {
+                                    name: 'classic',
+                                    size: 12,
+                                },
+                            },
+                        },
+                        router: {
+                            name: 'manhattan',
+                        },
+                        zIndex: 0,
+                    });
+                },
+                validateConnection({sourceView, targetView, sourceMagnet, targetMagnet}) {
+                    if (sourceView === targetView) {
+                        return false;
+                    }
+                    if (!sourceMagnet) {
+                        return false;
+                    }
+                    if (!targetMagnet) {
+                        return false;
+                    }
+                    return true;
+                },
+            },
+            highlighting: {
+                magnetAvailable: {
+                    name: 'stroke',
+                    args: {
+                        padding: 4,
+                        attrs: {
+                            strokeWidth: 4,
+                            stroke: '#ffff00',
+                        },
+                    },
+                },
+            },
+        });
+
+
+        this.initScroller();
+        this.initSelection();
+        this.initHistory();
+        this.initStencil();
+        this.initClipboard();
+        this.initKeyboard();
+        this.initMiniMap();
+        this.initSnapline();
+        this.initExport();
+        this.initShape();
+        this.initGraphShape(content);
+        this.initEvent();
+        return this.graph;
+    }
+
+    // 初始化滑板滚动条
+    static initScroller() {
+        const {graph} = this;
+
+        graph.use(
+            new Scroller({
+                // 画布调整
+                enabled: false,
+                pageVisible: true,
+                pageBreak: true,
+                // pannable: true,
+                autoResize: true,
+            })
+        );
+    }
+
+    static initSelection() {
+        const {graph} = this;
+
+        graph.use(
+            new Selection({
+                enabled: true,
+                multiple: true,
+                rubberband: true,
+                movable: true,
+                showNodeSelectionBox: true,
+                // showEdgeSelectionBox: true,
+            })
+        );
+    }
+
+    static initHistory() {
+        const {graph} = this;
+        graph.use(
+            new History({
+                enabled: true,
+            })
+        );
+    }
+
+    static initClipboard() {
+        const {graph} = this;
+        graph.use(
+            new Clipboard({
+                enabled: true,
+            })
+        );
+    }
+
+    static initKeyboard() {
+        const {graph} = this;
+        graph.use(
+            new Keyboard({
+                enabled: true,
+                global: true,
+            })
+        );
+    }
+
+    static initMiniMap() {
+        const {graph} = this;
+        graph.use(
+            new MiniMap({
+                container: document.getElementById('minimap'),
+            })
+        );
+    }
+
+    static initSnapline() {
+        const {graph} = this;
+        graph.use(
+            new Snapline({
+                enabled: true,
+            })
+        );
+    }
+
+    static initExport() {
+        const {graph} = this;
+        graph.use(new Export({}));
+    }
+
+    // 初始化流程图左侧工具栏模块
+    static initStencil() {
+        this.stencil = new Stencil({
+            // 标题
+            title: '流程节点',
+            target: this.graph,
+            stencilGraphWidth: 280,
+            search: {rect: false},
+            collapsable: true,
+            groups: [
+                {
+                    name: 'basic',
+                    title: '',
+                    graphHeight: 700,
+                    layoutOptions: {
+                        columns: 1,
+                        marginX: 70,
+                    },
+                },
+
+            ],
+        });
+        const stencilContainer = document.querySelector('#flowStencil');
+        stencilContainer.appendChild(this.stencil.container);
+        stencilContainer.querySelector('.x6-widget-stencil-search').style.display = 'none';
+        stencilContainer.querySelector('.x6-widget-stencil-group-title').style.display = 'none';
+        stencilContainer.querySelector('.x6-widget-stencil-title').style.cssText = 'font-size: 20px;';
+    }
+
+    // 初始化工具栏里面的内容
+    static initShape() {
+        const {graph} = this;
+        // 开始
+        const r1 = graph.createNode({
+            shape: 'flow-chart-image-rect',
+            attrs: {
+                'body': {
+                    class: 'rotating-node'
+                },
+                title: {
+                    textWrap: {
+                        width: -55,
+                        text: NodeType.Start.name,
+                    },
+                    refX: '50%',
+                },
+                image: {
+                    'xlink:href': `https://www.dcxxb-xcx.top:8072/dcApp/antX6/node_start.svg`,
+                },
+            },
+            data: {
+                code: '',
+                targetEdges: [],
+                sourceEdges: [],
+                nodeType: NodeType.Start.nodeType,
+            },
+        });
+        // 子流程
+        const r8 = graph.createNode({
+            shape: 'flow-chart-image-rect',
+            attrs: {
+                image: {
+                    'xlink:href': `https://www.dcxxb-xcx.top:8072/dcApp/antX6/node_zi.svg`,
+                },
+                title: {
+                    textWrap: {
+                        width: -55,
+                        text: NodeType.ChildFlow.name,
+                    },
+                    refX: '50%',
+                },
+            },
+            data: {
+                code: '',
+                targetEdges: [],
+                sourceEdges: [],
+                nodeType: NodeType.ChildFlow.nodeType,
+                // 参与者
+                participants: [],
+                dataPowerData: [],
+
+                childFlowData: {
+                    flowCode: '',//子流程编码
+                    async: true,//异步?
+                    autoSubmit: false,//发起环节自动提交
+                    mapping: [],
+                },
+            },
+        });
+        // 填写
+        const r2 = graph.createNode({
+            shape: 'flow-chart-image-rect',
+            attrs: {
+                title: {
+                    textWrap: {
+                        width: -55,
+                        text: NodeType.Fillout.name,
+                    },
+                    refX: '50%',
+                },
+                image: {
+                    'xlink:href': `https://www.dcxxb-xcx.top:8072/dcApp/antX6/node_fillin.svg`,
+                },
+            },
+            data: {
+                code: '',
+                targetEdges: [],
+                sourceEdges: [],
+                nodeType: NodeType.Fillout.nodeType,
+                // 参与者
+                participants: [],
+                dataPowerData: [],
+            },
+        });
+        // 传阅
+        const r6 = graph.createNode({
+            shape: 'flow-chart-image-rect',
+            attrs: {
+                image: {
+                    'xlink:href': `https://www.dcxxb-xcx.top:8072/dcApp/antX6/node_read.svg`,
+                },
+                title: {
+                    textWrap: {
+                        width: -55,
+                        text: NodeType.Circulate.name,
+                    },
+                    refX: '50%',
+                },
+            },
+            data: {
+                code: '',
+                targetEdges: [],
+                sourceEdges: [],
+                nodeType: NodeType.Circulate.nodeType,
+                // 参与者
+                participants: [],
+                dataPowerData: [],
+
+                // 无参与者 1-不作处理 2-使用上次审批结果 3-审批通过
+                partnerNoUser: '1',
+                partnerInitiator: '1',
+                partnerPreAction: '1',
+                partnerAction: '1',
+            },
+        });
+        // 连接点
+        const r3 = graph.createNode({
+            shape: 'flow-chart-image-rect',
+            attrs: {
+                title: {
+                    textWrap: {
+                        width: -55,
+                        text: NodeType.Junction.name,
+                    },
+                    refX: '50%',
+                },
+                image: {
+                    'xlink:href': `https://www.dcxxb-xcx.top:8072/dcApp/antX6/node_link.svg`,
+                },
+            },
+            data: {
+                code: '',
+                targetEdges: [],
+                sourceEdges: [],
+                nodeType: NodeType.Junction.nodeType,
+                passAction: 'all',
+            },
+        });
+        // 业务动作
+        const r4 = graph.createNode({
+            shape: 'flow-chart-image-rect',
+            attrs: {
+                image: {
+                    'xlink:href': `https://www.dcxxb-xcx.top:8072/dcApp/antX6/node_bizAction.svg`,
+                },
+                title: {
+                    textWrap: {
+                        width: -55,
+                        text: NodeType.BizAction.name,
+                    },
+                    refX: '50%',
+                },
+            },
+            data: {
+                code: '',
+                targetEdges: [],
+                sourceEdges: [],
+                nodeType: NodeType.BizAction.nodeType,
+                dataEvent: [],
+            },
+        });
+        // 结束
+        const r5 = graph.createNode({
+            shape: 'flow-chart-image-rect',
+            attrs: {
+                title: {
+                    textWrap: {
+                        width: -55,
+                        text: NodeType.End.name,
+                    },
+                    refX: '50%',
+                },
+                image: {
+                    'xlink:href': `https://www.dcxxb-xcx.top:8072/dcApp/antX6/node_end.svg`,
+                },
+            },
+            data: {
+                code: '',
+                targetEdges: [],
+                sourceEdges: [],
+                nodeType: NodeType.End.nodeType,
+            },
+        });
+        // 审核
+        const r7 = graph.createNode({
+            shape: 'flow-chart-image-rect',
+            attrs: {
+                image: {
+                    'xlink:href': `https://www.dcxxb-xcx.top:8072/dcApp/antX6/node_audit.svg`,
+                },
+                title: {
+                    textWrap: {
+                        width: -55,
+                        text: NodeType.check.name,
+                    },
+                    refX: '50%',
+                },
+                approve: {
+                    code: '',
+                },
+            },
+            resizable: true,
+            data: {
+                code: '',
+                targetEdges: [],
+                sourceEdges: [],
+                nodeType: NodeType.check.nodeType,
+                // 参与者
+                participants: [],
+                dataPowerData: [],
+
+                // 无参与者 1-不作处理 2-使用上次审批结果 3-审批通过
+                partnerNoUser: '1',
+                partnerInitiator: '1',
+                partnerPreAction: '1',
+                partnerAction: '1',
+            },
+        });
+
+        this.stencil.load([r7, r6, r4, r8, r3], 'basic');
+    }
+
+    static initGraphShape(content) {
+        this.graph.centerContent();
+        setTimeout(()=>{
+            this.graph.fromJSON(content);
+            this.graph.zoomToFit({maxScale: 1});
+        })
+    }
+
+    static showPorts(ports, show) {
+        for (let i = 0, len = ports.length; i < len; i = i + 1) {
+            ports[i].style.visibility = show ? 'visible' : 'hidden';
+        }
+    }
+
+    static initEvent() {
+        const {graph} = this;
+
+        let resizing = false
+        let crtlKeying = false;
+        document.addEventListener('keydown', function (event) {
+            // 检查按下的是否是 Ctrl 键
+            if (event.key == 'Control') {
+                crtlKeying = true;
+            }
+        });
+        document.addEventListener('keyup', function (event) {
+            // 检查抬起的是否是 Ctrl 键
+            if (event.key == 'Control') {
+                crtlKeying = false;
+            }
+        });
+        let xs = 0;
+        let ys = 0;
+        const container = document.getElementById('flowContainer');
+        graph.on('node:mousedown', ({e, x, y, node, view}) => {
+            console.log(crtlKeying, 'crtlKeying')
+            if (crtlKeying) {
+                resizing = true;
+            }
+            xs = node.position().x;
+            ys = node.position().y;
+        })
+        let x1 = 0;
+        let y1 = 0;
+        graph.on('node:mousemove', ({e, x, y, node, view}) => {
+            //这里写根据鼠标拖动,改变node大小的逻辑
+            if (resizing) {
+                if (x1 == 0) {
+                    x1 = x;
+                } else {
+                    const dx = x - x1;
+                    node.position(xs, ys)
+                    x1 = x;
+                    node.resize(
+                        node.size().width + dx,
+                        node.size().height
+                    );
+
+                }
+                if (y1 == 0) {
+                    y1 = y;
+                } else {
+                    const dy = y - y1;
+                    node.position(xs, ys)
+                    y1 = y;
+                    node.resize(
+                        node.size().width,
+                        node.size().height + dy
+                    );
+                }
+
+            }
+        })
+        graph.on('node:mouseup', ({e, x, y, node, view}) => {
+            x1 = 0
+            y1 = 0
+            resizing = false;
+        })
+        graph.on(
+            'node:mouseenter',
+            FunctionExt.debounce(() => {
+                const ports = container.querySelectorAll('.x6-port-body');
+                this.showPorts(ports, true);
+            }),
+            500
+        );
+        graph.on('node:mouseleave', () => {
+            const ports = container.querySelectorAll('.x6-port-body');
+            this.showPorts(ports, false);
+        });
+
+        // 栅格触发事件
+        graph.bindKey('delete', () => {
+            const cells = graph.getSelectedCells();
+            let canDel = true
+            for (let i in cells) {
+                const cell = cells[i]
+                const data = cell.getData()
+                if (data && (data.code == "Task1" || data.code == "Task2" || data.code == "Task3")) {
+                    canDel = false
+                }
+            }
+
+            if (cells.length && canDel) {
+                graph.removeCells(cells);
+            }
+            if (!canDel) {
+                message.warning("无法删除【开始、填写、结束】三个节点")
+            }
+        });
+
+        graph.on('edge:mouseenter', ({cell}) => {
+            cell.addTools([
+                {
+                    name: 'segments',
+                    args: {
+                        snapRadius: 10,
+                        precision: 10000,
+                        attrs: {
+                            fill: '#5F95FF',
+                        },
+                        stopPropagation: false,
+                    },
+                },
+            ]);
+            //edge可拖拽
+            cell.addTools([
+                {
+                    name: 'source-arrowhead',
+                    args: {
+                        attrs: {
+                            d: 'M -5, 0 a 5, 5 0 1,0 10,0 a 5, 5 0 1,0 -10,0',
+                            fill: '#5F95FF',
+                        },
+                    },
+                },
+                {
+                    name: 'target-arrowhead',
+                    args: {
+                        attrs: {
+                            fill: '#5F95FF',
+                        },
+                    },
+                },
+            ]);
+        });
+
+        graph.on('edge:mouseleave', ({cell}) => {
+            cell.removeTool('source-arrowhead');
+            cell.removeTool('target-arrowhead');
+            cell.removeTool('segments');
+        });
+
+        // 线条连接成功触发事件
+        graph.on('edge:connected', ({e, isNew, edge}) => {
+            if (isNew) {
+                edge.setData({
+                    nodeType: NodeType.Line.nodeType,
+                    condition: '',
+                });
+
+                const sourceNode = edge.getSourceCell();
+                const targetNode = edge.getTargetCell();
+                let targetEdges = sourceNode.getData().targetEdges;
+                let sourceEdges = targetNode.getData().sourceEdges;
+                targetEdges.push(edge.id);
+                sourceEdges.push(edge.id);
+                sourceNode.setData({
+                    targetEdges: targetEdges,
+                });
+                targetNode.setData({
+                    sourceEdges: sourceEdges,
+                });
+            }
+        });
+        // 节点拖到画布之后触发事件
+        graph.on('node:added', ({node, index, options}) => {
+            node.setData({
+                code: `Task${node.getZIndex()}`,
+            });
+        });
+    }
+
+    // 销毁
+    static destroy() {
+        this.graph.dispose();
+    }
+
+    static getGraphData() {
+    }
+}

+ 127 - 0
src/views/flow/stFlowDesign/graph/shape.js

@@ -0,0 +1,127 @@
+import { Graph, Dom, Node } from '@antv/x6'
+
+export const FlowChartImageRect = Graph.registerNode('flow-chart-image-rect', {
+  inherit: 'rect',
+  width: 180,
+  height: 47,
+  attrs: {
+    body: {
+      stroke: '#000000',
+      strokeWidth: 1,
+      fill: '#FFFFFF'
+    },
+    image: {
+      width: 20,
+      height: 20,
+      x: 8,
+      y: 13
+    },
+    title: {
+      text: '审核',
+      refX: 160,
+      refY: 15,
+      fill: 'black',
+      fontSize: '16px',
+      fontFamily: 'PingFang SC, sans-serif',
+      fontWeight: '500',
+      'text-anchor': 'middle',
+    },
+  },
+  markup: [
+    {
+      tagName: 'rect',
+      selector: 'body'
+    },
+    {
+      tagName: 'image',
+      selector: 'image'
+    },
+    {
+      tagName: 'text',
+      selector: 'title'
+    },
+    {
+      tagName: 'text',
+      selector: 'text'
+    }
+  ],
+  ports: {
+    groups: {
+      top: {
+        position: 'top',
+        attrs: {
+          circle: {
+            r: 3,
+            magnet: true,
+            stroke: '#5F95FF',
+            strokeWidth: 1,
+            fill: '#fff',
+            style: {
+              visibility: 'hidden'
+            }
+          }
+        }
+      },
+      right: {
+        position: 'right',
+        attrs: {
+          circle: {
+            r: 3,
+            magnet: true,
+            stroke: '#5F95FF',
+            strokeWidth: 1,
+            fill: '#fff',
+            style: {
+              visibility: 'hidden'
+            }
+          }
+        }
+      },
+      bottom: {
+        position: 'bottom',
+        attrs: {
+          circle: {
+            r: 3,
+            magnet: true,
+            stroke: '#5F95FF',
+            strokeWidth: 1,
+            fill: '#fff',
+            style: {
+              visibility: 'hidden'
+            }
+          }
+        }
+      },
+      left: {
+        position: 'left',
+        attrs: {
+          circle: {
+            r: 3,
+            magnet: true,
+            stroke: '#5F95FF',
+            strokeWidth: 1,
+            fill: '#fff',
+            style: {
+              visibility: 'hidden'
+            }
+          }
+        }
+      }
+    },
+    items: [
+      {
+        group: 'top'
+      },
+      {
+        group: 'right'
+      },
+      {
+        group: 'bottom'
+      },
+      {
+        group: 'left'
+      }
+    ]
+  }
+})
+

+ 91 - 0
src/views/flow/stFlowDesign/index.less

@@ -0,0 +1,91 @@
+.wrap {
+  width: 100%;
+  height: 100%;
+
+  .header {
+    display: flex;
+    justify-content: space-between;
+    height: 48px;
+    line-height: 48px;
+    padding-left: 16px;
+    padding-right: 32px;
+    background: #fff;
+    box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.1);
+    position: relative;
+    color: rgba(0, 0, 0, 0.45);
+  }
+
+  .content {
+    display: flex;
+    height: calc(100% - 48px);
+
+    .sider {
+      position: relative;
+      width: 290px;
+      // height: 100%;
+      // height: 750px;
+      border-right: 1px solid rgba(0, 0, 0, 0.08);
+    }
+
+    .panel {
+      height: 100%;
+
+      .toolbar {
+        display: flex;
+        align-items: center;
+        height: 39px;
+        background-color: #f7f9fb;
+        border-bottom: 1px solid rgba(0, 0, 0, 0.08);
+      }
+    }
+
+    .config {
+      box-sizing: border-box;
+      width: 410px;
+      height: 100%;
+    }
+  }
+}
+
+// 调整边界
+.ant-drawer-body {
+  padding: 0 !important;
+  overflow: hidden!important;
+}
+
+//解决左侧遮罩的问题
+.x6-widget-dnd {
+  z-index: 1000;
+}
+
+// 左侧动画
+@keyframes stroke {
+  100% {
+    stroke-dashoffset: -400;
+  }
+}
+
+.animate-text1,
+.animate-text2,
+.animate-text3,
+.animate-text4 {
+  font-weight: bold;
+  fill: none;
+  stroke-width: 2px;
+  stroke-dasharray: 90 310;
+  animation: stroke 3s infinite linear;
+}
+
+.animate-text1 {
+  stroke: #873bf4;
+  text-shadow: 0 0 2px #873bf4;
+  animation-delay: -1.5s;
+}
+
+.animate-text2 {
+  stroke: #ff6e06;
+  text-shadow: 0 0 2px #ff6e06;
+  animation-delay: -3s;
+}
+
+

+ 118 - 0
src/views/flow/stFlowDesign/index.vue

@@ -0,0 +1,118 @@
+<template>
+  <div class="wrap">
+    <div class="content">
+      <!--左侧工具栏-->
+      <div id="flowStencil" class="sider"/>
+      <div class="panel">
+        <!--流程图工具栏-->
+        <div class="toolbar">
+          <tool-bar :mainContent="mainContent" v-if="isReady"/>
+        </div>
+        <!--流程图画板-->
+        <div class="x6-graph">
+          <div id="flowContainer"/>
+          <div id="minimap" class="x6-minimap"/>
+        </div>
+      </div>
+      <!--右侧工具栏-->
+      <div class="config">
+        <config-panel :mainContent="mainContent" :eventOptions="eventOptions" v-if="isReady"/>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import './index.less';
+import FlowGraph from './graph/index.js';
+import ToolBar from './components/ToolBar/index.vue';
+import ConfigPanel from './components/ConfigPanel/index.vue';
+import {onMounted, ref} from 'vue';
+import {getStFlowTemplate} from "/@/api/flow/flowApi.js";
+import {useFlowStore} from "/@/store/modules/flow.js";
+import {message} from "ant-design-vue";
+import {getEventAdditiveService} from "/@/api/flow/settingApi.js";
+
+const isReady = ref(false);
+const visible = ref(false);
+const mainContent = ref({})
+const eventOptions = ref([])
+
+onMounted(() => {
+  setTimeout(() => {
+    getStFlowTemplate({
+      flowCode: useFlowStore()['StFlowModal'].flowCode,
+    }).then(
+        (res) => {
+          if (res.success) {
+            if (res.data) {
+              initGraph(JSON.parse(res.data.content));
+              if (res.data.mainContent == null) {
+                mainContent.value = {
+                  startEvent: '',
+                  endEvent: '',
+                  cancelEvent: '',
+                }
+              } else {
+                mainContent.value = JSON.parse(res.data.mainContent)
+              }
+            }
+          } else {
+            message.error(res.message);
+          }
+        },
+        (err) => {
+          console.log(err);
+        }
+    );
+    getEventAdditiveService({flowCode: useFlowStore()['StFlowModal'].flowCode})
+        .then((res) => {
+          for (let i = 0; i < res.data.length; i++) {
+            eventOptions.value.push({
+              id: res.data[i].id,
+              name: res.data[i].name,
+              url: res.data[i].url
+            })
+          }
+        })
+
+  }, 50);
+});
+
+const getContainerSize = () => {
+  return {
+    width: document.body.offsetWidth - 690,
+    height: document.body.offsetHeight - 38,
+  };
+};
+
+const initGraph = (content) => {
+  const graph = FlowGraph.init(content);
+  isReady.value = true;
+  const resizeFn = () => {
+    const {width, height} = getContainerSize();
+    graph.resize(width, height);
+  };
+  resizeFn();
+  window.addEventListener('resize', resizeFn);
+  return () => {
+    window.removeEventListener('resize', resizeFn);
+  };
+};
+</script>
+
+
+<style scoped lang="less">
+.x6-graph {
+  position: relative;
+}
+
+.x6-minimap {
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  width: 200px;
+  height: 200px;
+  background-color: #fff;
+}
+</style>

+ 67 - 0
src/views/flow/stFlowDesign/models/global.js

@@ -0,0 +1,67 @@
+export const globalGridAttr = {
+    type: 'mesh',
+    size: 10,
+    color: '#e5e5e5',
+    thickness: 1,
+    colorSecond: '#d0d0d0',
+    thicknessSecond: 1,
+    factor: 4,
+    bgColor: '#e5e5e5',
+    showImage: true,
+    repeat: 'watermark',
+    angle: 30,
+    position: 'center',
+    bgSize: JSON.stringify({width: 150, height: 150}),
+    opacity: 0.1,
+    // 先
+    stroke: '#5F95FF',
+    strokeWidth: 1,
+    connector: '',//默认使用跳线
+    label: '',
+    nodeType: '',
+    // 节点自定义属性
+    nodeStroke: '#5F95FF',
+    nodeStrokeWidth: 1,
+    nodeFill: '#ffffff',
+    nodeFontSize: 12,
+    nodeColor: '#080808',
+    nodeUsers: '',
+    // 节点名称
+    nodeName: '',
+    // 节点编码
+    nodeCode: '',
+    // 排序码
+    nodeIndex: 0,
+    // 流程名称
+    flowName: '',
+    // 流程编码
+    flowCode: '',
+    // 参与者
+    participants: '',
+    participantsName: '',
+    dataPowerData: [],
+    operatePowerData: {
+        forward: false,
+        add: false,
+        circulation: false,
+        back: false,
+        cancel: false,
+        submitToReject: false,
+    },
+    dataEvent: [],
+    partnerNoUser: '',
+    partnerInitiator: '',
+    partnerPreAction: '',
+    partnerAction: '',
+    passAction: 'all',
+
+    // 线条自定义属性
+    condition: '',
+    childFlowData: {
+        flowCode: '',//子流程编码
+        async: true,//异步?
+        autoSubmit: false,//发起环节自动提交
+        mapping: [],
+    }
+
+}

+ 5 - 0
src/views/flow/stFlowSetting/index.vue

@@ -0,0 +1,5 @@
+<template>
+    <div>
+        stFlowSetting
+    </div>
+</template>

+ 199 - 0
src/views/flow/stFlowView/adjustFlow.vue

@@ -0,0 +1,199 @@
+<template>
+  <a-modal
+      :open='open'
+      :footer='null'
+      width='70%'
+      height='500px'
+      :closable='false'
+      :maskStyle="{
+      backgroundColor: '#eceff7',
+    }"
+  >
+    <div class='header-top'>
+
+      <a-button class='header-child' @click='active' type="primary">
+        <template #icon>
+          <ApiOutlined/>
+        </template>
+        激活节点
+      </a-button>
+      <a-button class='header-child' @click='close' type="dashed">
+        <template #icon>
+          <CloseSquareOutlined/>
+        </template>
+        关闭
+      </a-button>
+    </div>
+    <a-divider>基础信息</a-divider>
+    <div>
+      <a-row>
+        <a-col class='col' span='4'>选择节点</a-col>
+        <a-col class='col' span='10'>
+          <a-select placeholder='请选择' v-model:value='selectedNodeId' style='width: 100%' allow-clear
+                    @select='select'>
+            <a-select-option v-for='(d, index) in nodeList' :key='index' :value='d.id'>{{ d.name }}
+            </a-select-option>
+          </a-select>
+        </a-col>
+      </a-row>
+      <a-row v-show='userDealVisible'>
+        <a-col class='col' span='4'>审批人</a-col>
+        <a-col class='col' span='10'>
+          {{ selectedUsersNames }}
+        </a-col>
+        <a-col class='col' span='4'>
+          <a-button @click='choseUser'>选择人员</a-button>
+        </a-col>
+      </a-row>
+    </div>
+    <select-user selectModel='multi' v-model='selectedUserIds' @callBack='selectU' v-show='false' ref='selectUserRef'/>
+  </a-modal>
+</template>
+<script>
+import {getNodeList, adjustActivate, getUsersMsg, finishInstance} from '/@/api/flow/flowApi'
+import SelectUser from '/@/components/StSelectUser/index.vue'
+import {message, Modal} from 'ant-design-vue';
+
+export default {
+
+  components: {SelectUser},
+  data() {
+    return {
+      open: false,
+      selectedNodeId: '',
+      selectedUsersNames: '',
+      selectedUsersCodes: '',
+      selectedUserIds: '',
+      selectedUserParentDeptIds: '',
+      selectedUserSubtitles: '',
+      nodeList: [],
+      selectedNode: {},
+      userDealVisible: false
+    }
+  },
+  props: ['instance'],
+  methods: {
+    active() {
+      let participants = this.selectedUsersCodes.split(';').filter(Boolean);
+      if (participants.length > 1 && this.selectedNode.nodeType == '1') {
+        message.warning("填写节点只能选择单人审批");
+      } else if (participants.length == 0 && this.selectedNode.nodeType == '2') {
+        message.warning("至少选择一位审批人");
+      } else {
+        adjustActivate({
+          instanceId: this.instance.objectid,
+          nodeId: this.selectedNode.id,
+          activityCode: this.selectedNode.code,
+          participants: participants
+        }).then(res => {
+          console.log(res, 'active')
+          window.location.reload()
+        })
+      }
+    },
+    choseUser() {
+      this.$refs.selectUserRef.open = true
+      const participants=this.selectedUserIds.split(';').filter(Boolean)
+      this.$refs.selectUserRef.userCheckedList = participants
+      let list = []
+      for (let i = 0; i < this.$refs.selectUserRef.userCheckedList.length; i++) {
+        list.push({
+          id: participants[i],
+          code: this.selectedUsersCodes.split(';')[i],
+          name: this.selectedUsersNames.split(';')[i],
+          subtitle: this.selectedUserSubtitles.split(';')[i],
+          parentIds: this.selectedUserParentDeptIds.split(';')[i],
+          icon: 'user'
+        })
+        console.log(list, 'list')
+      }
+
+      this.$refs.selectUserRef.userdata = list
+      this.$refs.selectUserRef.checkedKeys = participants
+      this.$refs.selectUserRef.selectedKeys = participants
+    },
+    selectU(user) {
+      this.selectedUsersNames = user.names
+      this.selectedUsersCodes = user.codes
+      this.selectedUserIds = user.ids
+      this.selectedUserParentDeptIds = user.parentIds
+      this.selectedUserSubtitles = user.subtitles
+    },
+    openPopUp() {
+      this.open = true
+    },
+    select(value) {
+      console.log(this.nodeList)
+      for (let i = 0; i < this.nodeList.length; i++) {
+        if (this.nodeList[i].id == value) {
+          this.selectedNode = this.nodeList[i]
+          break
+        }
+      }
+
+      if (this.selectedNode.nodeType == '1' || this.selectedNode.nodeType == '2') {
+        this.userDealVisible = true
+        this.dealUserMsg(this.instance.objectid, this.selectedNode.participants)
+      } else {
+        this.userDealVisible = false
+        this.selectedUsersNames = ''
+        this.selectedUsersCodes = ''
+        this.selectedUserIds = ''
+        this.selectedUserParentDeptIds = ''
+        this.selectedUserSubtitles = ''
+      }
+    },
+
+    dealUserMsg(instanceId, userCodes) {
+      getUsersMsg({instanceId: instanceId, userCodes: userCodes, nodeType: this.selectedNode.nodeType}).then((res) => {
+        let users = res.data
+        this.selectedUsersNames = users.map(item => item.userName).join(';')
+        this.selectedUsersCodes = users.map(item => item.userCode).join(';')
+        this.selectedUserIds = users.map(item => item.userId).join(';')
+        this.selectedUserParentDeptIds = users.map(item => item.parentIds).join(';')
+        this.selectedUserSubtitles = users.map(item => item.userDept).join(';')
+      })
+    },
+    getNodeList() {
+      if (this.instance.flowCode && this.instance.flowVersion) {
+        getNodeList({flowCode: this.instance.flowCode, flowVersion: this.instance.flowVersion}).then((res) => {
+          this.nodeList = res.data
+          this.selectedNodeId = this.nodeList[this.nodeList.length - 1].id
+          this.selectedNode = this.nodeList[this.nodeList.length - 1]
+        })
+      } else {
+        setTimeout(() => {
+          this.getNodeList()
+        }, 100)
+      }
+
+    },
+    close() {
+      this.open = false
+    }
+  },
+  mounted() {
+    this.getNodeList()
+  }
+}
+</script>
+<style lang='less' scoped>
+.header-top {
+  display: flex;
+  flex-wrap: wrap; /* 允许项目换行 */
+  width: 100%;
+  height: 44px;
+  box-sizing: border-box;
+  padding-bottom: 50px;
+}
+
+.header-child {
+  margin-left: 30px;
+  text-align: center;
+}
+
+.col {
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+</style>

+ 700 - 0
src/views/flow/stFlowView/flowView.vue

@@ -0,0 +1,700 @@
+<template>
+  <a-watermark class="form-view" :content="watermarkContent">
+    <a-affix :offset-top="0">
+      <div class="view-header" v-show="flowManager">
+        <div class="btn-group">
+          <a-button type="primary" :disabled="adjustBtn" @click="adjustFlow">
+            <template #icon>
+              <NodeIndexOutlined/>
+            </template>
+            节点调整
+          </a-button>
+          <a-button type="primary" :disabled="activeBtn" @click="activeFlow">
+            <template #icon>
+              <ApiOutlined/>
+            </template>
+            激活流程
+          </a-button>
+          <a-button type="primary" :disabled="cancelBtn" @click="cancelFlow">
+            <template #icon>
+              <MinusSquareOutlined/>
+            </template>
+            取消流程
+          </a-button>
+          <a-button type="dashed" :disabled="cancelBtn" @click="closePage">
+            <template #icon>
+              <CloseSquareOutlined/>
+            </template>
+            关闭
+          </a-button>
+        </div>
+
+        <div class="btn-group">
+
+          <a-button @click="openCB" type="dashed">
+            催办
+          </a-button>
+
+          <a-button type="primary" @click="openFlowForm">
+            <template #icon>
+              <FormOutlined/>
+            </template>
+            打开流程表单
+          </a-button>
+          <a-button type="primary" @click="recordFlow">
+            <template #icon>
+              <SnippetsOutlined/>
+            </template>
+            流程操作日志
+          </a-button>
+        </div>
+      </div>
+    </a-affix>
+
+    <div class="view-content">
+      <div class="view-c-part1">
+        <a-card v-if="headerInfo">
+          <template #title>
+            <div class="vcs-title">
+              <img src="/@/assets/form/view_a.svg" alt=""/>
+              <span>{{ headerInfo.instanceName || '-' }}</span>
+            </div>
+          </template>
+          <a-descriptions :column="3">
+            <a-descriptions-item>
+              <template #label>流水号</template>
+              <span class="cc-ll-val">{{ headerInfo.sequenceNo || '-' }}</span>
+            </a-descriptions-item>
+            <a-descriptions-item>
+              <template #label>发起人</template>
+              <span class="cc-ll-val">{{ headerInfo.originator || '-' }}</span>
+            </a-descriptions-item>
+
+            <a-descriptions-item>
+              <template #label>流程模板</template>
+              <span class="cc-ll-val">{{ headerInfo.flowModel || '-' }}</span>
+            </a-descriptions-item>
+
+            <a-descriptions-item>
+              <template #label>状态</template>
+              <div class="cc-ll-val" v-if="instance.exceptional == '1'&&flowManager">
+                <a-button size="small" type="primary" danger @click="repair">
+                  <template #icon>
+                    <close-circle-outlined/>
+                  </template>
+                  {{ headerInfo.state || '-' }}
+                </a-button>
+              </div>
+              <div class="cc-ll-val" style="color: red" v-else-if="instance.exceptional == '1'&&!flowManager">
+                {{ headerInfo.state || '-' }}
+              </div>
+              <div v-else class="cc-ll-val">
+                {{ headerInfo.state || '-' }}
+              </div>
+            </a-descriptions-item>
+
+            <a-descriptions-item>
+              <template #label>开始日期</template>
+              <span class="cc-ll-val">{{ headerInfo.startTime || '-' }}</span>
+            </a-descriptions-item>
+
+            <a-descriptions-item>
+              <template #label>结束日期</template>
+              <span class="cc-ll-val">{{ headerInfo.finishTime || '-' }}</span>
+            </a-descriptions-item>
+          </a-descriptions>
+
+          <a-table
+              size="small"
+              bordered
+              :pagination="false"
+              :columns="acColumns"
+              :data-source="acData"
+              :rowKey="(record) => record.objectid"></a-table>
+        </a-card>
+      </div>
+
+      <div class="view-c-part3">
+        <!--流程图画板-->
+        <div id="Container" class="x6-graph"/>
+        <div id="minimap" class="x6-minimap"/>
+
+        <div class="cc-remark">
+          <div class="cc-rm-z" v-for="(remark, index) in processTagList" :key="index">
+            <div class="crmm-box"
+                 :style="{ backgroundColor: remark.bgColor, border: `1px solid ${remark.borderColor}` }"></div>
+            <span class="crmm-txt">{{ remark.name }}</span>
+          </div>
+        </div>
+
+        <div class="cc-rr-l">
+          <div class="ccr-l-it">
+            <img src="/@/assets/form/view_b.svg" alt=""/>
+            <span>缩放:ctrl+滚轮</span>
+          </div>
+          <div class="ccr-l-it">
+            <img src="/@/assets/form/view_c.svg" alt=""/>
+            <span>拖拽:ctrl+左键滑行</span>
+          </div>
+        </div>
+
+        <div class="follow-mouse" v-show="visibleFuture || visibleZi || visibleAll" @mouseenter="mouseenter"
+             @mouseleave="mouseleave">
+          <a-divider>长按鼠标左键划到此处</a-divider>
+          <div v-show="(visibleFuture || visibleAll) && futureData && futureData.length > 0" class="float-div">
+            <a-card title="预测审批人">
+              <a-card-grid style="width: 25%; text-align: center" v-for="(item, index) in futureData" :key="index">
+                <span style="color: #0097ff"> {{ item }}</span>
+              </a-card-grid>
+            </a-card>
+          </div>
+          <div v-show="visibleZi || (visibleAll && taskListZi && taskListZi.length > 0)" class="float-div">
+            <a-card class="card-history" title="审批历史">
+              <a-table size="small" :columns="columnsZi" :data-source="taskListZi" :rowKey="(record) => record.objectid"
+                       :scroll="{ x: 'max-content' }"></a-table>
+            </a-card>
+          </div>
+        </div>
+      </div>
+
+      <div class="view-c-part2">
+        <a-collapse v-model:activeKey="activeKey">
+          <a-collapse-panel key="1" :showArrow="false">
+            <template #header>
+              <div class="vc2-header">
+                <div class="vc2-h1">日志</div>
+                <a-button type="link">
+                  <template #icon>
+                    <UpOutlined v-if="!activeKey.indexOf('1')"/>
+                    <DownOutlined v-if="activeKey.indexOf('1')"/>
+                  </template>
+                  <span v-if="!activeKey.indexOf('1')">收起</span>
+                  <span v-if="activeKey.indexOf('1')">展开</span>
+                </a-button>
+              </div>
+            </template>
+            <div>
+              <a-table
+                  size="small"
+                  v-if="taskList.length > 0"
+                  bordered
+                  :pagination="false"
+                  :columns="columns"
+                  :data-source="taskList"
+                  :rowKey="(record) => record.objectid"
+              >
+                <template #bodyCell="{ column, text, record, index }">
+                  <div v-if="column.dataIndex === 'seq'">{{ index + 1 }}</div>
+                </template>
+              </a-table>
+
+              <a-empty v-if="taskList.length == 0"/>
+            </div>
+          </a-collapse-panel>
+        </a-collapse>
+      </div>
+    </div>
+    <AdjustFlow :instance="instance" ref="popUpAdjust"></AdjustFlow>
+    <UserLogs :instance="instance" ref="userLogs"></UserLogs>
+
+    <PressDialog :instance="instance" :instanceId="instanceID" ref="pressDialog"></PressDialog>
+
+    <a-modal width="80%" v-model:open="repairOpen" title="流程异常信息查看" okText="修复异常" @ok="handleRepair">
+      <a-textarea :rows="12" :maxlength="26" v-model:value="exceptionMsg"></a-textarea>
+    </a-modal>
+  </a-watermark>
+</template>
+
+<script>
+import FlowGraph from '/@/views/flow/stFlowView/script/index.js';
+import UserLogs from './log.vue';
+import AdjustFlow from './adjustFlow.vue';
+import PressDialog from "./pressDialog.vue"
+import {activateInstance, cancelInstance, getStTaskItemList, getViewTop} from '/@/api/flow/flowApi';
+import {getFlowException, getUserManageAdmit, updateFlowException} from '/@/api/flow/settingApi';
+import {useRoute, useRouter} from 'vue-router';
+import {message} from 'ant-design-vue';
+import {DownOutlined, UpOutlined} from '@ant-design/icons-vue';
+import {setTitle} from "../stFormWork/componentsConfig";
+import {useWatermark} from '/@/hooks/useWatermark';
+
+const columnsZi = [
+  {
+    title: '接收时间',
+    dataIndex: 'receiveTime',
+    key: 'receiveTime',
+    ellipsis: true,
+  },
+  {
+    title: '完成时间',
+    dataIndex: 'finishTime',
+    key: 'finishTime',
+    ellipsis: true,
+    align: 'center',
+  },
+  {
+    title: '使用时间',
+    dataIndex: 'useTime',
+    key: 'useTime',
+    ellipsis: true,
+    align: 'center',
+  },
+  {
+    title: '参与人员',
+    dataIndex: 'participant',
+    key: 'participant',
+    ellipsis: true,
+    align: 'center',
+  },
+  {
+    title: '操作',
+    dataIndex: 'stateDisplay',
+    key: 'stateDisplay',
+    ellipsis: true,
+    align: 'center',
+  },
+];
+const columns = [
+  {
+    title: '序号',
+    dataIndex: 'seq',
+    key: 'seq',
+    align: 'center',
+    width: 70,
+  },
+  {
+    title: '节点名称',
+    dataIndex: 'activityName',
+    key: 'activityName',
+    ellipsis: true,
+    align: 'center',
+  },
+  {
+    title: '接收时间',
+    dataIndex: 'receiveTime',
+    key: 'receiveTime',
+    ellipsis: true,
+    align: 'center',
+  },
+  {
+    title: '完成时间',
+    dataIndex: 'finishTime',
+    key: 'finishTime',
+    ellipsis: true,
+    align: 'center',
+  },
+  {
+    title: '使用时间',
+    dataIndex: 'useTime',
+    key: 'useTime',
+    ellipsis: true,
+    align: 'center',
+  },
+  {
+    title: '参与人员',
+    dataIndex: 'participant',
+    key: 'participant',
+    ellipsis: true,
+    align: 'center',
+  },
+  {
+    title: '操作',
+    dataIndex: 'stateDisplay',
+    key: 'stateDisplay',
+    ellipsis: true,
+    align: 'center',
+  },
+];
+
+const acColumns = [
+  {
+    title: '当前活动名称',
+    dataIndex: 'activityName',
+    key: 'activityName',
+    align: 'center',
+    width: '350px',
+  },
+  {
+    title: '当前活动未完成的审批人',
+    dataIndex: 'participantsStr',
+    key: 'participantsStr',
+    align: 'left',
+  },
+];
+export default {
+  name: 'flowView',
+  components: {AdjustFlow, UserLogs, UpOutlined, DownOutlined, PressDialog},
+  data: () => ({
+    repairOpen: false,
+    exceptionMsg: '',
+    exceptionId: 0,
+    activeKey: ['1'],
+    instanceID: '',
+    instance: {},
+    taskList: [],
+    columns,
+    columnsZi,
+    visibleAll: false,
+    visibleZi: false,
+    visibleFuture: false,
+    taskListZi: [],
+    futureData: [],
+    mouseX: 0,
+    mouseY: 0,
+    adjustBtn: false,
+    activeBtn: false,
+    cancelBtn: false,
+    nodeList: [],
+    $router: useRouter(),
+    $route: useRoute(),
+    flowManager: false,
+    processTagList: [
+      {
+        name: '处理过的节点',
+        bgColor: '#98c6ff',
+        borderColor: '#79c0f1',
+      },
+      {
+        name: '进行中的节点',
+        bgColor: '#fae694',
+        borderColor: '#f5ce84',
+      },
+      {
+        name: '预测后续节点',
+        bgColor: '#ecfff9',
+        borderColor: '#497063',
+      },
+      {
+        name: '异常中的节点',
+        bgColor: '#f22e07',
+        borderColor: '#f2907c',
+      },
+
+    ],
+    acColumns,
+    acData: [],
+    headerInfo: null,
+    watermarkContent: useWatermark().watermarkContent
+  }),
+  methods: {
+    openCB() {
+      this.$refs.pressDialog.show();
+    },
+
+    repair() {
+      if (this.flowManager) {
+        getFlowException({instanceId: this.instanceID}).then((res) => {
+          this.exceptionMsg = '事件名称:' + res.data.eventName
+              + '\r\n错误信息:' + res.data.rootErrorMessage
+              + '\r\n详细信息:' + res.data.errorMessage
+              + '\r\n发生时间:' + res.data.createTime;
+          this.exceptionId = res.data.id
+        })
+
+        this.repairOpen = true;
+      }
+    },
+    handleRepair() {
+      updateFlowException({instanceId: this.instanceID, id: this.exceptionId}).then(() => {
+        this.initGraph();
+        this.initData();
+        this.repairOpen = false;
+      })
+    },
+    closePage() {
+      this.$confirm({
+        title: '确认要关闭当前页面吗?',
+        onOk: () => {
+          // 关闭当前代码
+          message.success('关闭当前页面');
+          window.close();
+        },
+        onCancel() {
+          message.warning('已取消操作');
+        },
+      });
+    },
+    adjustFlow(vm) {
+      this.$refs.popUpAdjust.openPopUp();
+    },
+    activeFlow() {
+      //激活流程
+      activateInstance({instanceId: this.instanceID}).then((res) => {
+        this.initData();
+      });
+    },
+    cancelFlow() {
+      //取消流程
+      cancelInstance({instanceId: this.instanceID}).then((res) => {
+        window.location.reload();
+      });
+    },
+    recordFlow() {
+      //历史记录
+      this.$refs.userLogs.open();
+    },
+    openFlowForm() {
+      const router = this.$router.resolve({
+        path: '/MvcSheet/formWork',
+        query: {
+          flowCode: this.$route.query.flowCode,
+          flowVersion: this.$route.query.flowVersion,
+          bizObjectID: this.$route.query.bizObjectID,
+          taskID: this.$route.query.taskID,
+          instanceID: this.$route.query.instanceID,
+        },
+      });
+      window.open(router.href, '_blank');
+    },
+
+    initGraph() {
+      const graph = FlowGraph.init(this);
+    },
+
+    initData() {
+      getStTaskItemList({instanceid: this.instanceID}).then((res) => {
+        this.taskList = res.data.taskList;
+        this.instance = res.data.instance;
+        this.handlerState();
+      });
+
+      getViewTop({instanceid: this.instanceID}).then((res) => {
+        if (res.success) {
+          this.acData = res.data.taskList;
+          if (this.acData && this.acData.length > 0) {
+            this.acData.forEach(f => {
+              if (f.participants && f.participants.length > 0) {
+                f.participantsStr = f.participants.join(",");
+              }
+            })
+          }
+          this.headerInfo = res.data;
+          setTitle(`【流程详情】` + this.headerInfo.instanceName)
+        }
+      });
+    },
+    handlerState() {
+      /**
+       * 状态
+       * 3:流程异常
+       */
+      if (this.instance.exceptional == '1') {
+        this.adjustBtn = true;
+        this.activeBtn = true;
+        this.cancelBtn = true;
+      }
+      /**
+       * 状态
+       * 0:保存未提交
+       * 1:驳回到发起人
+       * 2:运行中
+       */
+      else if (this.instance.state === 0 || this.instance.state === 1 || this.instance.state === 2) {
+        this.adjustBtn = false;
+        this.activeBtn = true;
+        this.cancelBtn = false;
+      }
+      /**
+       * 状态
+       * 4:完成
+       */
+      else if (this.instance.state === 4) {
+        this.adjustBtn = true;
+        this.activeBtn = false;
+        this.cancelBtn = true;
+      }
+      /**
+       * 状态
+       * 5:取消
+       */
+      else if (this.instance.state === 5) {
+        this.adjustBtn = true;
+        this.activeBtn = false;
+        this.cancelBtn = true;
+      }
+
+
+    },
+    mouseenter(event) {
+      if (this.visibleZi) {
+        this.visibleAll = true;
+      }
+      if (this.visibleFuture) {
+        this.visibleAll = true;
+      }
+    },
+    mouseleave() {
+      this.visibleAll = false;
+    },
+  },
+  mounted() {
+    getUserManageAdmit({flowCode: this.$route.query.flowCode}).then((res) => {
+      this.flowManager = res.data;
+    });
+    this.instanceID = this.$route.query.instanceID;
+    setTimeout(() => {
+      this.initGraph();
+    }, 500);
+
+    this.initData();
+  },
+};
+</script>
+
+<style lang="less" scoped>
+
+.form-view {
+  width: 100%;
+  height: 100%;
+
+  .view-header {
+    padding: 20px 15px;
+    background-color: #fff;
+    display: flex;
+    justify-content: space-between;
+
+    .btn-group {
+      display: flex;
+      gap: 10px;
+    }
+  }
+
+  .view-content {
+    width: 100%;
+
+    .view-c-part1 {
+      padding: 15px;
+
+      .cc-ll-val {
+        color: rgba(#000, 0.9);
+        font-size: 14px;
+        font-weight: 700;
+      }
+
+      .vcs-title {
+        display: flex;
+        align-items: center;
+        color: rgba(#000, 0.9);
+        font-size: 16px;
+        font-weight: 700;
+        gap: 10px;
+      }
+    }
+
+    .view-c-part2 {
+      padding: 15px;
+
+      .vc2-header {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        font-size: 16px;
+        color: rgba(#000, 0.9);
+
+        .vc2-h1 {
+          color: #467af8;
+          font-size: 16px;
+          font-weight: 700;
+          margin-left: 25px;
+
+          &::before {
+            content: '';
+            left: 15px;
+            top: 20px;
+            width: 15px;
+            height: 15px;
+            background-color: #608df9;
+            border: 4px solid #c6d7fd;
+            border-radius: 50%;
+            position: absolute;
+          }
+        }
+      }
+    }
+
+    .view-c-part3 {
+      width: 100%;
+      position: relative;
+      background-color: #fff;
+
+      #Container {
+        width: 100%;
+        height: 1000px;
+        margin: 0 auto; /* 上下边距为0,左右自动,实现水平居中 */
+        overflow-y: hidden; /* 隐藏纵向滚动条 */
+      }
+
+      .x6-minimap {
+        position: absolute;
+        bottom: 20px;
+        right: 0px;
+        width: 300px;
+        height: 180px;
+        background-color: #fff;
+      }
+
+      .cc-remark {
+        position: absolute;
+        top: 30px;
+        left: 30px;
+        display: flex;
+        flex-direction: column;
+        gap: 10px;
+
+        .cc-rm-z {
+          display: flex;
+          align-items: center;
+          gap: 10px;
+
+          .crmm-box {
+            width: 14px;
+            height: 14px;
+          }
+
+          .crmm-txt {
+            color: #575757;
+            font-size: 12px;
+            font-weight: 500;
+          }
+        }
+      }
+
+      .cc-rr-l {
+        position: absolute;
+        top: 30px;
+        right: 30px;
+        display: flex;
+        flex-direction: column;
+        gap: 10px;
+
+        .ccr-l-it {
+          color: #666666;
+          font-size: 12px;
+          font-weight: 500;
+          display: flex;
+          align-items: center;
+          gap: 10px;
+        }
+      }
+    }
+  }
+}
+
+:deep(.ant-collapse) {
+  background: #fff;
+}
+
+.follow-mouse {
+  position: fixed;
+  top: 32px;
+  right: 20px;
+  width: 660px;
+  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+  z-index: 1000;
+  background-color: #eceff7;
+}
+
+:deep(.card-history) {
+  min-height: 560px;
+}
+</style>

+ 23 - 0
src/views/flow/stFlowView/index.less

@@ -0,0 +1,23 @@
+.node-steped {
+    fill: #fae694;
+    stroke: #000000;
+    animation-name: grow;
+    animation-duration: 1s;
+    animation-fill-mode: forwards;
+    animation-iteration-count: infinite;
+    animation-direction: alternate;
+    /* 使动画在每个周期后反向播放 */
+    /* 确保 transform-origin 为元素的中心点 */
+    transform-box: fill-box;
+    transform-origin: center;
+}
+
+@keyframes grow {
+    0% {
+        transform: scale(.95);
+    }
+
+    100% {
+        transform: scale(1.05);
+    }
+}

+ 71 - 0
src/views/flow/stFlowView/log.vue

@@ -0,0 +1,71 @@
+<template>
+  <div>
+    <a-drawer title="单据操作记录" placement="right" :closable="false" :visible="visible" width="80%" @close="onClose">
+      <a-table :columns="columns" :data-source="data">
+        <a slot="createTime" slot-scope="text">{{ text.split('T')[0] }} {{ text.split('T')[1] }}</a>
+      </a-table>
+    </a-drawer>
+  </div>
+</template>
+<script>
+  import { getUserLogs } from '/@/api/flow/flowApi';
+
+  export default {
+    props: ['instance'],
+    data() {
+      return {
+        visible: false,
+        columns: [
+          {
+            title: '操作时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+            align: 'center',
+          },
+          {
+            title: '操作人',
+            dataIndex: 'userName',
+            key: 'userName',
+            align: 'center',
+          },
+          {
+            title: '动作',
+            dataIndex: 'type',
+            key: 'type',
+            align: 'center',
+          },
+          {
+            title: '任务名称',
+            dataIndex: 'taskName',
+            key: 'taskName',
+            align: 'center',
+          },
+          {
+            title: 'ip',
+            dataIndex: 'ip',
+            key: 'ip',
+            align: 'center',
+          },
+          {
+            title: '设备',
+            dataIndex: 'platform',
+            key: 'platform',
+            align: 'center',
+          },
+        ],
+        data: [],
+      };
+    },
+    methods: {
+      open() {
+        getUserLogs({ instanceId: this.instance.objectid }).then((res) => {
+          this.data = res.data;
+        });
+        this.visible = true;
+      },
+      onClose() {
+        this.visible = false;
+      },
+    },
+  };
+</script>

+ 117 - 0
src/views/flow/stFlowView/pressDialog.vue

@@ -0,0 +1,117 @@
+<template>
+  <a-modal :open="open" :footer="null" width="70%" :closable="true" @cancel="close" :title="title">
+    <div class="pager">
+      <a-form :model="formData">
+        <a-form-item label="催办内容" name="content" :rules="[{ required: true, message: '请输入催办内容' }]">
+          <a-textarea v-model:value="formData.content" :autosize="{ minRows: 5 }" placeholder="请输入催办内容" :maxlength="1000" showCount />
+        </a-form-item>
+        <a-form-item>
+          <a-button type="primary" @click="onSubmit" :loading="isSubmiting">一键催办</a-button>
+        </a-form-item>
+      </a-form>
+      <a-card title="催办历史">
+        <a-table :dataSource="dataSource" :columns="columns" :scroll="{ y: 200 }" size="middle" :loading="tableLoading">
+          <template #bodyCell="{ column, text, record, index }">
+            <div v-if="column.dataIndex === 'seq'">
+              {{ index + 1 }}
+            </div>
+          </template>
+        </a-table>
+      </a-card>
+    </div>
+  </a-modal>
+</template>
+<script>
+  import { insertUrge, getUrges } from '/@/api/flow/flowApi';
+  export default {
+    props: ['instance', 'instanceId'],
+    data() {
+      return {
+        open: false,
+        title: '催办',
+        infoValue: {},
+        formData: {
+          content: '',
+        },
+        isSubmiting: false,
+        tableLoading: false,
+        dataSource: [],
+        columns: [
+          {
+            title: '序号',
+            dataIndex: 'seq',
+            key: 'seq',
+            width: "80px",
+            align: 'center',
+          },
+          {
+            title: '催办内容',
+            dataIndex: 'content',
+            key: 'content',
+          },
+          {
+            title: '创建人',
+            dataIndex: 'createBy',
+            key: 'createBy',
+          },
+          {
+            title: '创建时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+          },
+        ],
+      };
+    },
+    methods: {
+      show(value) {
+        this.infoValue = value;
+        this.open = true;
+        this.initPage();
+      },
+
+      initPage() {
+        this.formData.content = '';
+        this.tableLoading = true;
+        getUrges({ instanceId: this.instanceId }).then((res) => {
+          this.tableLoading = false;
+          this.dataSource = res.data;
+        });
+      },
+
+      close() {
+        this.open = false;
+      },
+      async onSubmit() {
+        if (!this.formData.content) {
+          this.$message.error('请输入催办内容');
+          return;
+        }
+
+        this.isSubmiting = true;
+
+        await insertUrge({
+          instanceId: this.instanceId,
+          content: this.formData.content,
+        });
+
+        this.isSubmiting = false;
+        this.$message.success('催办成功');
+
+        this.initPage();
+
+        setTimeout(() => {
+          this.close();
+        }, 2000);
+      },
+    },
+    mounted() {
+      console.log(this.instance, 'this.instance');
+    },
+  };
+</script>
+<style lang="less" scoped>
+  .pager {
+    width: 100%;
+    overflow-y: auto;
+  }
+</style>

+ 153 - 0
src/views/flow/stFlowView/script/index.js

@@ -0,0 +1,153 @@
+import { Graph, FunctionExt } from '@antv/x6'
+import '/@/views/flow/stFlowDesign/graph/shape'
+import { instanceView } from '/@/api/flow/flowApi'
+import { MiniMap } from '@antv/x6-plugin-minimap'
+import "../index.less"
+
+export default class FlowGraph {
+
+  _graphData = {}
+
+  _vm = {}
+
+  static init(vm) {
+    this._vm = vm
+    this.graph = new Graph({
+      container: document.getElementById('Container'),
+      connecting: {
+        connector: 'jumpover',
+        anchor: 'center'
+      },
+      interacting: false,
+      panning: {
+        enabled: true,
+        modifiers: ['ctrl'],
+        eventTypes: ['leftMouseDown']
+      },
+      // 网格配置
+      grid: {
+        visible: false,
+      },
+      mousewheel: {
+        enabled: true,
+        modifiers: ['ctrl', 'meta'],
+        minScale: 0.5,
+        maxScale: 2
+      }
+
+    })
+    this.initGraphShape()
+    this.initEvent()
+    this.initMiniMap()
+    return this.graph
+  }
+  static initMiniMap() {
+    const { graph } = this
+    graph.use(
+      new MiniMap({
+        container: document.getElementById('minimap'),
+      }),
+    )
+  }
+
+  static initGraphShape() {
+    const instanceID = this._vm.$route.query.instanceID
+    instanceView({
+      instanceid: instanceID
+    }).then((res) => {
+      if (res.success) {
+        const graphData = res.data
+        this._graphData = graphData
+        this.graph.fromJSON(graphData)
+        this.graph.zoomToFit({maxScale:1})
+        this.handleGraphData()
+      }
+    })
+  }
+  static initEvent() {
+    const { graph } = this
+    const container = document.getElementById('Container')
+    graph.on(
+      'node:mouseenter',
+      FunctionExt.debounce(({ e, node, view }) => {
+        const data = node.getData()
+        console.log(data, 'dataaaaa')
+        const code = data.code
+        const escParticipant = data.escParticipant
+        if(escParticipant&&escParticipant.length==0){
+          escParticipant.push('未找到流程参与者')
+        }
+        const steped = data.steped
+        const nodeType = data.nodeType
+        if (nodeType == 1 || nodeType == 2 || nodeType == 3) {
+          this._vm.futureData = escParticipant
+          this._vm.taskListZi = this._vm.taskList.filter(task => task.activityCode == code)
+          if (this._vm.futureData && this._vm.futureData.length > 0) {
+            this._vm.visibleFuture = true
+          }
+          if (this._vm.taskListZi && this._vm.taskListZi.length > 0) {
+            this._vm.visibleZi = true
+          }
+        }
+      }),
+      500
+    )
+    graph.on('node:mouseleave', ({ e, node, view }) => {
+      this._vm.visibleZi = false
+      this._vm.visibleFuture = false
+    })
+  }
+
+  // 处理画布数据
+  static handleGraphData() {
+
+    const nodes = this.graph.getNodes()
+    nodes.forEach(node => {
+      const steped = node.getData().steped
+      // 0-正在此节点 1-经过此节点 2-预测未来节点
+      switch (steped) {
+        case 0:
+          node.setAttrs({
+            body: {
+              class: "node-steped",
+            }
+          })
+          break
+        case 1:
+          node.setAttrs({
+            body: {
+              fill: '#98c6ff',
+              stroke: '#000000'
+            }
+          })
+          break
+        case 2:
+          node.setAttrs({
+            body: {
+              fill: '#ecfff9',
+              stroke: '#000000'
+            }
+          })
+          break
+        case 3:
+          node.setAttrs({
+            body: {
+              fill: '#f83d3d',
+              stroke: '#000000'
+            }
+          })
+          break
+        default:
+          break
+      }
+    })
+    this.graph.drawBackground({
+      color: '#ffffff'
+    })
+  }
+
+  // 销毁
+  static destroy() {
+    this.graph.dispose()
+  }
+}

+ 210 - 0
src/views/flow/stFormDesign/packages/PopUpMapping/index.vue

@@ -0,0 +1,210 @@
+<template>
+  <div class="page">
+    <a-select v-model:value='queryServiceData' style='width: 100%' placeholder='选择查询服务' @change='handleChange'>
+      <a-select-option v-for='d in data' :key='d.id'>
+        {{ d.queryName }}
+      </a-select-option>
+    </a-select>
+    <a-card size='small' title='字段映射:' :bordered='true'>
+      <div v-for='(m,index) in queryMapFieldData'>
+        <div v-show='m.mapping===true'>
+          {{ m.field }}:
+          <a-tree-select
+              v-model:value='m.mapField'
+              style='width: 100%'
+              show-search
+              :tree-data='formFields'
+              tree-node-filter-prop="name"
+              :filter-option='filterOption'
+              :allowClear='true'
+              :fieldNames='{children:"children",label:"name",key:"code",value:"code"}'
+              :placeholder='m.fieldName'
+              @change='handleChangeField'
+              tree-default-expand-all
+          >
+          </a-tree-select>
+        </div>
+      </div>
+    </a-card>
+
+    <a-card size='small' title='查询映射' :bordered='true'>
+
+      <div class="query-map-content">
+        <div class="query-map" v-if="queryMapKeyValData.length > 0">
+          <div class="query-map-item" v-for="(qItm, qIdx) in queryMapKeyValData" :key="qIdx">
+            <div class="query-map-item-header">
+              <div class="query-no">查询映射{{  qIdx + 1  }}</div>
+              <a-button danger size="small" type="dashed" @click="deleteChangeQueryMapKeyVal(qIdx)">
+                <template #icon>
+                  <DeleteOutlined />
+                </template>
+              </a-button>
+            </div>
+
+            <span>查询Key</span>
+            <a-input placeholder="请输入查询Key" v-model:value="qItm.queryKey"></a-input>
+
+            <span>查询Value</span>
+            <a-tree-select
+              v-model:value='qItm.queryValue'
+              style='width: 100%'
+              show-search
+              :tree-data='queryMapKeyValOpts'
+              tree-node-filter-prop="name"
+              :filter-option='filterOption'
+              :allowClear='true'
+              :fieldNames='{children:"children",label:"name",key:"code",value:"code"}'
+              placeholder='请输入查询Value'
+              tree-default-expand-all
+            >
+            </a-tree-select>
+          </div>
+        </div>
+
+        <a-button type="dashed" @click="handleAddQueryMap">
+          <template #icon>
+            <PlusOutlined />
+          </template>
+          新增
+        </a-button>
+      </div>
+    </a-card>
+  </div>
+
+</template>
+<script>
+import {getQueryMainListForMapping} from '/@/api/flow/settingApi'
+import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue'
+
+export default {
+  components: { PlusOutlined, DeleteOutlined },
+  props: {
+    formDataList: {
+      type: Array
+    },
+    formFields: {
+      type: Array,
+      default: () => []
+    },
+    queryMapField: {
+      type: Array,
+      default: () => []
+    },
+    selectItem: {
+      type: Object
+    },
+    queryService: {
+      type: Number,
+      default: 0
+    },
+    queryMapKeyValData: {
+      type: Array,
+      default: () => []
+    }
+  },
+  data() {
+    return {
+      data: [],
+      queryMapFieldData: [],
+      queryServiceData: null,
+    }
+  },
+  mounted() {
+    getQueryMainListForMapping().then((res) => {
+      this.data = res.data
+      this.queryServiceData = this.queryService
+      if ( this.data.find(item => item.id == this.queryServiceData)){
+        const newData = this.data.find(item => item.id == this.queryServiceData).queryColumnList
+        for (let i in newData) {
+          const exists = this.queryMapField.some(item => item.field == newData[i]["field"]);
+          if (exists) {
+            this.queryMapFieldData.push(this.queryMapField.find(item => item.field == newData[i]["field"]))
+          } else {
+            this.queryMapFieldData.push(newData[i])
+          }
+        }
+        this.$emit('changeMap',  this.queryServiceData, this.queryMapFieldData, this.queryMapKeyValData)
+      }
+    })
+
+  },
+  methods: {
+    filterOption(input, option) {
+      return (
+          option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
+      )
+    },
+    handleChange(value) {
+      this.queryMapFieldData = this.data.find(item => item.id == value).queryColumnList
+      this.$emit('changeMap', value, this.queryMapFieldData)
+    },
+    handleChangeField(value) {},
+    handleAddQueryMap() {
+      this.queryMapKeyValData.push({
+        queryKey: undefined,
+        queryValue: undefined
+      })
+
+      this.$emit("update:queryMapKeyValData", this.queryMapKeyValData);
+    },
+    deleteChangeQueryMapKeyVal(index) {
+      this.queryMapKeyValData.splice(index, 1);
+      this.$emit("update:queryMapKeyValData", this.queryMapKeyValData);
+    },
+    changeQueryMapKeyVal() {
+      this.$emit("update:queryMapKeyValData", this.queryMapKeyValData);
+    }
+  },
+  computed: {
+    queryMapKeyValOpts() {
+      const customFields = [{
+        code: '[CurrentLoginUser]',
+        name: '当前登录人'
+      },]
+      return [...customFields,...this.formFields.map(v => ({
+        ...v,
+        code: `{${v.code}}`
+      }))]
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.page {
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+
+  .query-map-content {
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+    .query-map {
+      display: flex;
+      flex-direction: column;
+      gap: 10px;
+      padding: 10px;
+      border-radius: 10px;
+      border: 1px dotted #ccc;
+      .query-map-item {
+        display: flex;
+        flex-direction: column;
+        gap: 10px;
+
+        .query-map-item-header {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+
+          .query-no {
+           font-size: 12px;
+           color: #000;
+           font-weight: bold;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 346 - 0
src/views/flow/stFormDesign/packages/PopUpQuery/index.vue

@@ -0,0 +1,346 @@
+<template>
+  <div>
+    <a-form-item
+        :label-col="
+        formConfig.layout === 'horizontal'
+          ? formConfig.labelLayout === 'flex'
+            ? { style: `width:${formConfig.labelWidth}px` }
+            : formConfig.labelCol
+          : {}
+      "
+        :wrapper-col="
+        formConfig.layout === 'horizontal' ? (formConfig.labelLayout === 'flex' ? { style: 'width:auto;flex:1' } : formConfig.wrapperCol) : {}
+      "
+        :style="formConfig.layout === 'horizontal' && formConfig.labelLayout === 'flex' ? { display: 'flex' } : {}"
+        :name="record.model"
+        :rules="record.rules"
+    >
+      <template #label>
+        <a-tooltip>
+          <div>
+            <span>{{ record.label || '' }}</span>
+            <span v-if='record.help' style="margin-left: 4px">
+              <QuestionCircleOutlined/>
+            </span>
+          </div>
+
+          <template #title v-if="record.help">
+            {{ record.help }}
+          </template>
+
+        </a-tooltip>
+      </template>
+      <a-input-search
+          enter-button="选择"
+          @search="onSearch"
+          :options="options"
+          :disabled="disabled"
+          :readonly="readonly"
+          :placeholder="placeholder"
+          @change="handleChange(formData[record.model], record.model,record)"
+          v-model:value="formData[record.model]"
+          class="inputSearchPop"
+      />
+    </a-form-item>
+    <a-modal :width="isMobile ? '100%' : '70%'" title="数据查询" :open="visible" :confirm-loading="confirmLoading"
+             @ok="handleOk"
+             @cancel="handleCancel">
+      <div v-if="!isMobile">
+        <a-row>
+          <a-col :span="24">
+            <div class="queryMain">
+              <div class="queryChild" v-for="(h, i) in headerQuery">
+                <a-input v-model:value="h.value" :placeholder="h.fieldName"></a-input>
+              </div>
+              <a-button class="queryBut" @click="search">查询</a-button>
+            </div>
+          </a-col>
+        </a-row>
+        <a-row>
+          <a-col :span="24">
+            <a-table :columns="columns" :data-source="tableData" :customRow="handleRow"
+                     :pagination="{ defaultPageSize: 50 }"
+                     :scroll="{ x: 1300, y: 500 }" size="small" @resizeColumn="handleResizeColumn">
+              <template #bodyCell="{ text, record, index, column }">
+                <template v-if="column.key === 'isChecked'">
+                  <a-radio :checked="record[column.key]" @click="clickRadio($event, record, column, index)"></a-radio>
+                </template>
+              </template>
+            </a-table>
+          </a-col>
+        </a-row>
+      </div>
+
+      <div v-else>
+        <a-row>
+          <a-col :span="24" v-for="(h, i) in isFold ? headerQuery.slice(0, 1) : headerQuery">
+            <div class="queryMain" style="padding: 5px 0">
+              <div class="queryChild" style="width: 100%">
+                <a-input v-model:value="h.value" :placeholder="h.fieldName"></a-input>
+              </div>
+            </div>
+          </a-col>
+
+          <a-col span="24">
+            <a-button class="queryBut" @click="search" type="primary">查询</a-button>
+            <a-button class='queryBut' @click='isFold = false' style="margin-left: 10px;" v-show="isFold">
+              <template #icon>
+                <DownOutlined/>
+              </template>
+              <span>展开</span>
+            </a-button>
+            <a-button class='queryBut' @click='isFold = true' style="margin-left: 10px;" v-show="!isFold">
+              <template #icon>
+                <UpOutlined/>
+              </template>
+              <span>折叠</span>
+            </a-button>
+          </a-col>
+        </a-row>
+        <a-row>
+          <a-col :span="24">
+            <a-table :columns="columns" :data-source="tableData" :customRow="handleRow" size="small">
+              <template #bodyCell="{ text, record, index, column }">
+                <template v-if="column.key === 'isChecked'">
+                  <a-radio :checked="record[column.key]"
+                           @click="clickRadio($event, record, column, index)"></a-radio>
+                </template>
+              </template>
+            </a-table>
+          </a-col>
+        </a-row>
+      </div>
+    </a-modal>
+  </div>
+</template>
+<script lang="jsx">
+import {getQueryData} from '/@/api/flow/settingApi';
+import {message} from 'ant-design-vue';
+import {isMobile} from '/@/utils'
+import {DownOutlined, UpOutlined} from '@ant-design/icons-vue';
+
+export default {
+  props: {
+    rowData: {},
+    childTableFieldRecord: {
+      type: Object,
+    },
+    dynamicData: {
+      type: Object,
+      default: () => ({}),
+    },
+    record: {
+      type: Object,
+    },
+    options: {
+      type: Array,
+    },
+    formConfig: {
+      type: Object,
+    },
+    disabled: {
+      type: Boolean,
+    },
+    readonly: {
+      type: Boolean,
+      default: false,
+    },
+    placeholder: {
+      type: String,
+    },
+    content: {
+      type: Array,
+    },
+    formData: {
+      type: Object,
+      required: false,
+    },
+  },
+  data() {
+    return {
+      columns: [], //列表头
+      tableData: [], //列表数据
+      visible: false, //弹出框是否可见
+      confirmLoading: false, //数据加载
+      selectRecord: {}, //选中内容
+      selectIndex: null, //选中index
+      headerQuery: [], //搜索内容
+      isFold: true
+    };
+  },
+  components: {
+    DownOutlined,
+    UpOutlined
+  },
+  computed: {
+    isMobile() {
+      return isMobile()
+    }
+  },
+  mounted() {
+    if (!this.disabled) {
+      //只有可编辑状态才加载数据
+      const queryMapField = this.record.queryMapField;
+      this.columns.unshift({
+        width: '60px',
+        title: '选择',
+        key: 'isChecked',
+        dataIndex: 'isChecked',
+        fixed: 'left',
+      });
+
+      for (const key in queryMapField) {
+        if (queryMapField[key].visible) {
+          this.columns.push({
+            width: '160px',
+            resizable: true,
+            title: queryMapField[key].fieldName,
+            dataIndex: queryMapField[key].field,
+            key: queryMapField[key].field,
+            ellipsis: true,
+          });
+        }
+        if (queryMapField[key].search) {
+          this.headerQuery.push({
+            fieldName: queryMapField[key].fieldName,
+            field: queryMapField[key].field,
+            value: '',
+          });
+        }
+      }
+
+    }
+  },
+  methods: {
+
+    handleRow(record, index) {
+      return {
+        onDblclick: (event) => {
+          this.resetCheckedState();
+          record['isChecked'] = true;
+          this.selectRecord = record;
+          this.selectIndex = index;
+          this.handleOk();
+        }
+      }
+    },
+
+    clickRadio(event, record, column, index) {
+      this.resetCheckedState();
+      record[column.key] = true;
+      this.selectRecord = record;
+      this.selectIndex = index;
+    },
+
+    resetCheckedState() {
+      this.tableData.forEach((item) => {
+        item.isChecked = false;
+      });
+    },
+
+    search() {
+      const queryService = this.record.queryService;
+      const queryMapField = []
+      if (this.record.queryMapKeyValData) {
+        this.record.queryMapKeyValData.forEach((item, index) => {
+          const keyVal = {}
+          keyVal.key = item.queryKey
+          if (item.queryValue.indexOf("{") >= 0) {
+            keyVal.value = this.formData[item.queryValue.substring(1, item.queryValue.length - 1)];
+          } else {
+            keyVal.value = item.queryValue
+          }
+          queryMapField.push(keyVal)
+        })
+      }
+      getQueryData({
+        queryMainId: queryService,
+        query: JSON.stringify(this.headerQuery),
+        queryMapField: JSON.stringify(queryMapField),
+      }).then((res) => {
+        this.tableData = res.data.map((el) => {
+          return {
+            ...el,
+            isChecked: false,
+          };
+        });
+      });
+    },
+    showModal() {
+      this.selectRecord = {};
+      this.selectIndex = undefined;
+      this.visible = true;
+      this.search()
+    },
+    onSearch(r) {
+      this.showModal();
+    },
+    handleChange(a, b, c) {
+
+      this.$emit('change', a, b, c);
+    },
+    rowClassName(record, index) {
+      return index === this.selectIndex ? 'active' : '';
+    },
+    linkFieldChange(field) {
+      const linkMapStr = sessionStorage.getItem('_linkMap_' + field)
+      if (linkMapStr) {
+        const linkMap = JSON.parse(linkMapStr)
+        this.dynamicData[linkMap.linkedKey] = linkMap.initialValue
+            .filter(f => f.parentValue == this.formData[field])
+      }
+    },
+    handleOk(e) {
+      this.confirmLoading = true;
+      if (Reflect.ownKeys(this.selectRecord).length === 0) {
+        message.warning('请选择一条数据');
+      } else {
+        const queryMapField = this.record.queryMapField;
+        for (const key in queryMapField) {
+          const mapField = queryMapField[key].mapField;
+          if (mapField) {
+            // TODO: 优化
+            this.formData[mapField] = this.selectRecord[queryMapField[key].field];
+            this.linkFieldChange(mapField)
+          }
+        }
+        this.visible = false;
+        this.$emit('change', this.formData[this.record.model], this.record.model, this.record)
+      }
+      this.confirmLoading = false;
+    },
+    handleCancel(e) {
+      this.visible = false;
+    },
+    handleResizeColumn(w, col) {
+      col.width = w;
+    }
+  },
+};
+</script>
+<style scoped>
+:deep(.active td) {
+  background: #c8e7ff !important;
+}
+
+.queryMain {
+  display: flex;
+  flex-wrap: wrap; /* 允许项目换行 */
+  padding: 10px;
+}
+
+.queryChild {
+  margin-right: 10px;
+}
+
+.queryBut {
+}
+
+.inputSearchPop {
+  input {
+    color: red;
+  }
+}
+
+
+</style>

+ 291 - 0
src/views/flow/stFormDesign/packages/PopUpQueryChild/index.vue

@@ -0,0 +1,291 @@
+<template>
+  <div>
+    <a-input-search
+        enter-button='选择'
+        @search='onSearch'
+        :options='options'
+        :disabled='disabled'
+        :placeholder='placeholder'
+        @change='handleChange'
+        v-model:value='rowData[record.model]'
+    />
+
+    <a-modal
+        :width="isMobile ? '100%' : '70%'"
+        title='数据查询'
+        :open='visible'
+        :confirm-loading='confirmLoading'
+        @ok='handleOk'
+        @cancel='handleCancel'
+    >
+      <div v-if='!isMobile'>
+        <a-row>
+          <a-col :span='24'>
+            <div class='queryMain'>
+              <div class='queryChild' v-for='(h,i) in headerQuery'>
+                <a-input v-model:value='h.value' :placeholder='h.fieldName'></a-input>
+              </div>
+              <a-button class='queryBut' @click='search'>查询</a-button>
+            </div>
+          </a-col>
+        </a-row>
+        <a-row>
+          <a-col :span='24'>
+            <a-table :columns="columns" :data-source="tableData" :customRow="handleRow"
+                     :scroll="{ x: 1300, y: 1000 }" size="small" @resizeColumn="handleResizeColumn">
+              <template #bodyCell="{ text, record, index, column }">
+                <template v-if="column.key === 'isChecked'">
+                  <a-radio :checked="record[column.key]"
+                           @click="clickRadio($event, record, column, index)"></a-radio>
+                </template>
+              </template>
+            </a-table>
+          </a-col>
+
+        </a-row>
+      </div>
+      <div v-else>
+        <a-row>
+          <a-col :span="24" v-for="(h, i) in isFold ? headerQuery.slice(0, 1) : headerQuery">
+            <div class="queryMain" style="padding: 5px 0">
+              <div class="queryChild" style="width: 100%">
+                <a-input v-model:value="h.value" :placeholder="h.fieldName"></a-input>
+              </div>
+            </div>
+          </a-col>
+
+          <a-col span="24">
+            <a-button class="queryBut" @click="search" type="primary">查询</a-button>
+            <a-button class='queryBut' @click='isFold = false' style="margin-left: 10px;" v-show="isFold">
+              <template #icon>
+                <DownOutlined/>
+              </template>
+              <span>展开</span>
+            </a-button>
+            <a-button class='queryBut' @click='isFold = true' style="margin-left: 10px;" v-show="!isFold">
+              <template #icon>
+                <UpOutlined/>
+              </template>
+              <span>折叠</span>
+            </a-button>
+          </a-col>
+        </a-row>
+        <a-row>
+          <a-col :span="24">
+            <a-table :scroll="{x: '100%'}" :columns="columns" :data-source="tableData" :customRow="handleRow">
+              <template #bodyCell="{ text, record, index, column }">
+                <template v-if="column.key === 'isChecked'">
+                  <a-radio :checked="record[column.key]"
+                           @click="clickRadio($event, record, column, index)"></a-radio>
+                </template>
+              </template>
+            </a-table>
+          </a-col>
+        </a-row>
+      </div>
+    </a-modal>
+  </div>
+</template>
+<script>
+import {getQueryData} from '/@/api/flow/settingApi'
+// import Template from '@/views/tool/gen/genconfigtemplate/index.vue'
+import {isMobile} from '/@/utils'
+import {DownOutlined, UpOutlined} from '@ant-design/icons-vue';
+
+export default {
+  // components: { Template },
+  props: {
+    rowData: {},
+    childTableFieldRecord: {
+      type: Object
+    },
+    record: {
+      type: Object
+    },
+    options: {
+      type: Array
+    },
+    formConfig: {
+      type: Object
+    },
+    disabled: {
+      type: Boolean
+    },
+    placeholder: {
+      type: String
+    },
+    formData: {
+      type: Object,
+      required: false
+    }
+  },
+  data() {
+    return {
+      columns: [],//列表头
+      tableData: [],//列表数据
+      visible: false,//弹出框是否可见
+      confirmLoading: false,//数据加载
+      selectRecord: {},//选中内容
+      selectIndex: null,//选中index
+      headerQuery: [],//搜索内容
+      isFold: true,
+    }
+  },
+  components: {
+    DownOutlined,
+    UpOutlined
+  },
+  computed: {
+    isMobile() {
+      return isMobile()
+    }
+  },
+  mounted() {
+    this.columns.unshift({
+      width: '60px',
+      title: '选择',
+      key: 'isChecked',
+      dataIndex: 'isChecked',
+      fixed: 'left',
+    });
+    if (!this.disabled) {
+      //只有可编辑状态才加载数据
+
+      const queryMapField = this.record.queryMapField
+      for (const key in queryMapField) {
+        if (queryMapField[key].visible) {
+          this.columns.push({
+            width: '160px',
+            resizable: true,
+            title: queryMapField[key].fieldName,
+            dataIndex: queryMapField[key].field,
+            key: queryMapField[key].field,
+            ellipsis: true
+          })
+        }
+        if (queryMapField[key].search) {
+          this.headerQuery.push({
+            fieldName: queryMapField[key].fieldName,
+            field: queryMapField[key].field,
+            value: ''
+          })
+        }
+      }
+    }
+  },
+  methods: {
+    handleRow(record, index) {
+      return {
+        onDblclick: (event) => {
+          this.resetCheckedState();
+          record['isChecked'] = true;
+          this.selectRecord = record;
+          this.selectIndex = index;
+          this.handleOk();
+        }
+      }
+    },
+    clickRadio(event, record, column, index) {
+      this.resetCheckedState();
+      record[column.key] = true;
+      this.selectRecord = record;
+      this.selectIndex = index;
+    },
+    resetCheckedState() {
+      this.tableData.forEach((item) => {
+        item.isChecked = false;
+      });
+    },
+    search() {
+      const queryService = this.record.queryService;
+      const queryMapField = []
+      if (this.record.queryMapKeyValData) {
+        this.record.queryMapKeyValData.forEach((item, index) => {
+          const keyVal = {}
+          keyVal.key = item.queryKey
+          if (item.queryValue.indexOf("{") >= 0) {
+            keyVal.value = this.formData[item.queryValue.substring(1, item.queryValue.length - 1)];
+            if (keyVal.value == undefined) {
+              keyVal.value = this.rowData[item.queryValue.substring(1, item.queryValue.length - 1)]
+            }
+          } else {
+            keyVal.value = item.queryValue
+          }
+          queryMapField.push(keyVal)
+        })
+      }
+      getQueryData({
+        queryMainId: queryService,
+        query: JSON.stringify(this.headerQuery),
+        queryMapField: JSON.stringify(queryMapField),
+      }).then((res) => {
+        this.tableData = res.data.map((el) => {
+          return {
+            ...el,
+            isChecked: false,
+          };
+        });
+      })
+    },
+    showModal() {
+      this.selectRecord = {};
+      this.selectIndex = undefined;
+      this.visible = true
+      this.search()
+    },
+    onSearch(r) {
+      this.showModal()
+    },
+    handleChange(a, b, c) {
+      this.$emit('change', a, b, c)
+    },
+    handleOk(e) {
+      this.confirmLoading = true
+      setTimeout(() => {
+        this.visible = false
+        const queryMapField = this.record.queryMapField
+        console.log(queryMapField, 'queryMapField')
+        console.log(this.rowData, 'this.rowData')
+        console.log(this.selectRecord, 'this.selectRecord')
+        for (const key in queryMapField) {
+          const mapField = queryMapField[key].mapField
+          if (mapField) {
+            if (this.rowData) {
+              this.rowData[mapField] = this.selectRecord[queryMapField[key].field]
+            }
+          }
+        }
+        this.confirmLoading = false
+      }, 11)
+    }
+    ,
+    handleCancel(e) {
+      this.visible = false
+    },
+    handleResizeColumn(w, col) {
+      col.width = w;
+    }
+  }
+}
+</script>
+<style scoped lang="less">
+:deep(.active td) {
+  background: #c8e7ff !important;
+}
+
+.queryMain {
+  display: flex;
+  flex-wrap: wrap; /* 允许项目换行 */
+  padding: 10px;
+
+}
+
+.queryChild {
+  margin-right: 10px;
+}
+
+.queryBut {
+
+}
+
+</style>

+ 214 - 0
src/views/flow/stFormDesign/packages/PopUpQueryGlobal/index.vue

@@ -0,0 +1,214 @@
+<template>
+  <div>
+    <a-modal
+        :width='isMobile ? "100%" : "70%"'
+        :title='title'
+        :open='visible'
+        :confirm-loading='confirmLoading'
+        @ok='handleOkMain'
+        @cancel='handleCancel'
+    >
+      <div v-if="!isMobile">
+        <a-row>
+          <a-col :span='24'>
+            <div class='queryMain'>
+              <div class='queryChild' v-for='(h,i) in headerQuery'>
+                <a-input v-model:value='h.value' :placeholder='h.fieldName'></a-input>
+              </div>
+              <a-button class='queryBut' @click='search'>查询</a-button>
+            </div>
+          </a-col>
+        </a-row>
+        <a-row>
+          <a-col :span='24'>
+            <a-table :row-selection='{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }' :columns='columns'
+                     :data-source='tableData'
+            >
+            </a-table>
+          </a-col>
+
+        </a-row>
+      </div>
+      <div v-else>
+        <a-row>
+          <a-col :span='24' v-for='(h,i) in isFold ? headerQuery.slice(0, 1) : headerQuery'>
+            <div class='queryMain' style="padding: 5px 0">
+              <div class='queryChild' style="width: 100%">
+                <a-input v-model:value='h.value' :placeholder='h.fieldName'></a-input>
+              </div>
+            </div>
+          </a-col>
+          <a-col :span='24'>
+            <a-button class='queryBut' @click='search' type="primary">查询</a-button>
+            <a-button class='queryBut' @click='isFold = false' style="margin-left: 10px;" v-show="isFold">
+              <template #icon>
+                <DownOutlined/>
+              </template>
+              <span>展开</span>
+            </a-button>
+            <a-button class='queryBut' @click='isFold = true' style="margin-left: 10px;" v-show="!isFold">
+              <template #icon>
+                <UpOutlined/>
+              </template>
+              <span>折叠</span>
+            </a-button>
+          </a-col>
+        </a-row>
+        <a-row>
+          <a-col :span='24'>
+            <a-table :row-selection='{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }'
+                     :columns='columns'
+                     :data-source='tableData'
+            >
+            </a-table>
+          </a-col>
+        </a-row>
+      </div>
+    </a-modal>
+  </div>
+</template>
+<script>
+import {getPopUpData, getQueryPopupData} from '/@/api/flow/settingApi'
+import {isMobile} from '/@/utils'
+import {DownOutlined, UpOutlined} from '@ant-design/icons-vue';
+
+export default {
+  data() {
+    return {
+      title: '数据查询',
+      queryCode: '',//列表头
+      queryMapField: [],
+      columns: [],//列表头
+      tableData: [],//列表数据
+      visible: false,//弹出框是否可见
+      confirmLoading: false,//数据加载
+      selectRecord: {},//选中内容
+      selectIndex: null,//选中index
+      headerQuery: [],//搜索内容
+      selectedRows: [],
+      selectedRowKeys: [],
+      isFold: true
+    }
+  },
+  components: {
+    DownOutlined,
+    UpOutlined
+  },
+  watch: {
+    visible(newVal, oldVal) {
+      if (newVal == true) {
+        this.search()
+      }
+    },
+    queryCode(newVal, oldVal) {
+      this.handleQueryCodeChange(newVal)
+    }
+  },
+  computed: {
+    rowSelection() {
+      return {
+        onChange: (selectedRowKeys, selectedRows) => {
+          this.selectedRows = selectedRows
+        },
+        getCheckboxProps: record => ({
+          props: {
+            disabled: record.name === 'Disabled User',
+            name: record.name
+          }
+        })
+      }
+    },
+    isMobile() {
+      return isMobile()
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    handleToggle(code) {
+      if (code === 'unfold') {
+        // 展开
+      } else {
+        // 折叠
+      }
+    },
+    onSelectChange(selectedRowKeys, selectedRows) {
+      this.selectedRowKeys = selectedRowKeys
+      this.selectedRows = selectedRows
+    },
+    handleQueryCodeChange(newQueryCode) {
+      getPopUpData({queryCode: newQueryCode}).then((res) => {
+        let j = res.data
+        this.columns = j.columns
+        this.headerQuery = j.headerQuery
+        this.headerQuery.forEach(h => {
+          h.value = ''
+        })
+      })
+    },
+    search() {
+      getQueryPopupData({
+            queryCode: this.queryCode,
+            query: JSON.stringify(this.headerQuery),
+            queryMapField: JSON.stringify(this.queryMapField)
+          }
+      ).then((res) => {
+        this.tableData = res.data
+        this.tableData.forEach((t, index) => {
+          if (!t.key) {
+            t.key = `row-${index}`; // 使用索引生成唯一的 key
+          }
+        })
+      })
+    },
+    showModal() {
+      this.visible = true
+    },
+    onSearch(r) {
+      this.showModal()
+    },
+    handleChange(a, b, c) {
+      this.$emit('change', a, b, c)
+    },
+    handleOkMain(e) {
+      this.visible = false
+      this.handleOk(e)
+      this.selectedRows = []
+      this.selectedRowKeys = []
+    },
+    handleOk(e) {
+    }
+    ,
+    handleCancel(e) {
+      this.visible = false
+      this.selectedRows = []
+      this.selectedRowKeys = []
+    }
+  }
+}
+</script>
+<style scoped>
+:deep(.active td) {
+  background: #c8e7ff !important;
+}
+
+.queryMain {
+  display: flex;
+  flex-wrap: wrap; /* 允许项目换行 */
+  padding: 10px;
+
+}
+
+.queryChild {
+  margin-right: 10px;
+}
+
+.queryBut {
+
+}
+
+:deep(.ant-table) {
+  height: calc(100vh - 450px);
+  overflow: auto;
+}
+</style>

+ 81 - 0
src/views/flow/stFormDesign/packages/PreviewCode/index.vue

@@ -0,0 +1,81 @@
+<template>
+  <div>
+    <div class="json-box-9136076486841527">
+      <!-- <codemirror
+        style="height:100%;"
+        ref="myEditor"
+        :value="editorJson"
+      ></codemirror> -->
+    </div>
+    <div class="copy-btn-box-9136076486841527">
+      <a-button
+        @click="handleCopyJson"
+        type="primary"
+        class="copy-btn"
+        data-clipboard-action="copy"
+        :data-clipboard-text="editorJson"
+      >
+        复制数据
+      </a-button>
+      <a-button @click="handleExportJson" type="primary">
+        导出代码
+      </a-button>
+    </div>
+  </div>
+</template>
+<script>
+// 剪切板组件
+import Clipboard from "clipboard";
+// import { codemirror } from "vue-codemirror-lite";
+export default {
+  name: "PreviewCode",
+  props: {
+    fileFormat: {
+      type: String,
+      default: "json"
+    },
+    editorJson: {
+      type: String,
+      default: ""
+    }
+  },
+  data() {
+    return {
+      visible: false
+    };
+  },
+
+  components: {
+    // codemirror
+  },
+  methods: {
+    exportData(data, fileName = `demo.${this.fileFormat}`) {
+      let content = "data:text/csv;charset=utf-8,";
+      content += data;
+      var encodedUri = encodeURI(content);
+      var actions = document.createElement("a");
+      actions.setAttribute("href", encodedUri);
+      actions.setAttribute("download", fileName);
+      actions.click();
+    },
+    handleExportJson() {
+      // 导出JSON
+      this.exportData(this.editorJson);
+    },
+    handleCopyJson() {
+      // 复制数据
+      const clipboard = new Clipboard(".copy-btn");
+      clipboard.on("success", () => {
+        this.$message.success("复制成功");
+      });
+      clipboard.on("error", () => {
+        this.$message.error("复制失败");
+      });
+      setTimeout(() => {
+        // 销毁实例
+        clipboard.destroy();
+      }, 122);
+    }
+  }
+};
+</script>

+ 606 - 0
src/views/flow/stFormDesign/packages/StBatch/batch.vue

@@ -0,0 +1,606 @@
+<!--
+ * @Description: 动态表格 用于批量填入数据
+ -->
+<template>
+  <div>
+    <a-form ref="dynamicValidateForm" :model="dynamicValidateForm">
+      <a-table v-if="!isMobile"
+               class="batch-table"
+               :rowKey="(column) => column.key"
+               :columns="columns"
+               :dataSource="dynamicValidateForm.domains"
+               bordered
+               :pagination="pagination"
+               @change="tablePaginationChange"
+               :scroll="{x: 'max-content'}"
+      >
+        <template #headerCell="{ column }">
+          <div>
+          <span
+              v-if="
+              record.list.find((item) => item.model == column.dataIndex) &&
+              record.list.find((item) => item.model == column.dataIndex).rules[0].required
+            "
+              style="color: #ff4d4f; margin-right: 4px"
+          >*</span
+          >
+            <span>{{ column.title }}</span>
+          </div>
+        </template>
+
+        <template #bodyCell="scope">
+          <div v-if="scope.column.dataIndex === 'sequence_index_number'">
+            {{ getSerialNumber(scope.index) }}
+          </div>
+
+          <div v-if="record.list.find((item) => item.model == scope.column.dataIndex)">
+            <StFormModelItem
+                :record="record.list.find((item) => item.model == scope.column.dataIndex)"
+                :config="config"
+                :parentDisabled="disabled"
+                :index="record.list.findIndex((item) => item.model == scope.column.dataIndex)"
+                :domains="dynamicValidateForm.domains"
+                :dynamicData="dynamicData"
+                :formData="formData"
+                :formConfig="formConfig"
+                :childTableFieldRecord="record.list"
+                :rowIndex="scope.index"
+                v-model:value="scope.record[record.list.find((item) => item.model == scope.column.dataIndex).model]"
+                @input="handleInput"
+                :tableScope="scope"
+                :columns="columns"
+                :pagination="pagination"
+                :currentTableData="currentTableData"
+            />
+          </div>
+
+          <div v-if="scope.column.dataIndex === 'dynamic-opr-button'">
+            <a-button v-if="!disabled" type="dashed" @click="removeDomain(scope.record)" danger>
+              <template #icon>
+                <DeleteOutlined/>
+              </template>
+            </a-button>
+          </div>
+        </template>
+      </a-table>
+
+      <div v-if="isMobile">
+        <a-card v-for="(item, rowIdx) in firstData" :key="rowIdx" style="margin-bottom: 10px"
+                class="mobile-table">
+          <div v-for="(col, colIdx) in columns" :key="colIdx" class="mobile-table-item">
+            <div class="mobile-table-item-content" v-if="col.dataIndex === 'sequence_index_number'">
+              <div>
+              <span>
+                {{ col.title }}
+              </span>
+              </div>
+              <div>
+                <a-tag>{{ rowIdx + 1 }}</a-tag>
+              </div>
+            </div>
+            <div class="mobile-table-item-content" v-if="col.dataIndex === 'dynamic-opr-button'">
+              <div class="mob-label">
+              <span>
+                {{ col.title }}
+              </span>
+              </div>
+              <a-button v-if="!disabled" type="dashed" @click="removeDomain(item)" danger>
+                <template #icon>
+                  <DeleteOutlined/>
+                </template>
+              </a-button>
+            </div>
+            <div class="mobile-table-item-content"
+                 v-if="['sequence_index_number', 'dynamic-opr-button'].indexOf(col.dataIndex) === -1">
+              <div class="mob-label">
+              <span
+                  v-if="
+                  record.list.find((item) => item.model == col.dataIndex) &&
+                  record.list.find((item) => item.model == col.dataIndex).rules[0].required
+                "
+                  style="color: #ff4d4f; margin-right: 4px"
+              >*</span
+              >
+                <span>
+                {{ col.title }}
+              </span>
+              </div>
+
+              <div class="mobile-tanle-form-item">
+                <StFormModelItem
+                    :record="record.list.find((item) => item.model == col.dataIndex)"
+                    :config="config"
+                    :parentDisabled="disabled"
+                    :index="record.list.findIndex((item) => item.model == col.dataIndex)"
+                    :domains="dynamicValidateForm.domains"
+                    :dynamicData="dynamicData"
+                    :formData="formData"
+                    :formConfig="formConfig"
+                    :childTableFieldRecord="record.list"
+                    :rowIndex="rowIdx"
+                    v-model:value="item[record.list.find((item) => item.model == col.dataIndex).model]"
+                    @input="handleInput"
+                    :columns="columns"
+                    :pagination="pagination"
+                    :currentTableData="currentTableData"
+                    :colIndex="colIdx"
+                />
+              </div>
+            </div>
+          </div>
+        </a-card>
+        <a-button type="dashed" style="width: 100%"
+                  v-show="this.dynamicValidateForm.domains&&this.dynamicValidateForm.domains.length>1"
+                  @click="openChild=true">查看更多 (共有{{ this.dynamicValidateForm.domains.length}}条数据)
+        </a-button>
+      </div>
+
+      <a-button type="dashed" :disabled="disabled" @click="addDomain" v-if="!disabled">
+        <template #icon>
+          <PlusOutlined/>
+        </template>
+        增加
+      </a-button>
+    </a-form>
+
+    <a-modal
+        v-if="isMobile"
+        v-model:open="openChild"
+        :title="null"
+        width="100%"
+        wrapClassName="fullscreen-modal"
+        :footer="null"
+        style="top: 0; padding: 0"
+        :closable="false"
+
+    >
+      <!-- 悬浮在顶部的按钮 -->
+      <div class="floating-top-button">
+        <a-button
+            type="text"
+            @click="openChild = false"
+            style="color: #1890ff"
+        >
+          <template #icon>
+            <LeftOutlined /> <!-- 返回图标 -->
+          </template>
+          返回
+        </a-button>
+      </div>
+      <div class="modal-content-wrapper">
+        <div class="scrollable-content">
+          <div v-if="isMobile">
+            <a-card v-for="(item, rowIdx) in dynamicValidateForm.domains" :key="rowIdx" style="margin-bottom: 10px"
+                    class="mobile-table">
+              <div v-for="(col, colIdx) in columns" :key="colIdx" class="mobile-table-item">
+                <div class="mobile-table-item-content" v-if="col.dataIndex === 'sequence_index_number'">
+                  <div>
+              <span>
+                {{ col.title }}
+              </span>
+                  </div>
+                  <div>
+                    <a-tag>{{ rowIdx + 1 }}</a-tag>
+                  </div>
+                </div>
+                <div class="mobile-table-item-content" v-if="col.dataIndex === 'dynamic-opr-button'">
+                  <div class="mob-label">
+              <span>
+                {{ col.title }}
+              </span>
+                  </div>
+                  <a-button v-if="!disabled" type="dashed" @click="removeDomain(item)" danger>
+                    <template #icon>
+                      <DeleteOutlined/>
+                    </template>
+                  </a-button>
+                </div>
+                <div class="mobile-table-item-content"
+                     v-if="['sequence_index_number', 'dynamic-opr-button'].indexOf(col.dataIndex) === -1">
+                  <div class="mob-label">
+              <span
+                  v-if="
+                  record.list.find((item) => item.model == col.dataIndex) &&
+                  record.list.find((item) => item.model == col.dataIndex).rules[0].required
+                "
+                  style="color: #ff4d4f; margin-right: 4px"
+              >*</span
+              >
+                    <span>
+                {{ col.title }}
+              </span>
+                  </div>
+
+                  <div class="mobile-tanle-form-item">
+                    <StFormModelItem
+                        :record="record.list.find((item) => item.model == col.dataIndex)"
+                        :config="config"
+                        :parentDisabled="disabled"
+                        :index="record.list.findIndex((item) => item.model == col.dataIndex)"
+                        :domains="dynamicValidateForm.domains"
+                        :dynamicData="dynamicData"
+                        :formData="formData"
+                        :formConfig="formConfig"
+                        :childTableFieldRecord="record.list"
+                        :rowIndex="rowIdx"
+                        v-model:value="item[record.list.find((item) => item.model == col.dataIndex).model]"
+                        @input="handleInput"
+                        :columns="columns"
+                        :pagination="pagination"
+                        :currentTableData="currentTableData"
+                        :colIndex="colIdx"
+                    />
+                  </div>
+                </div>
+              </div>
+            </a-card>
+          </div>
+          <a-button type="dashed" :disabled="disabled" @click="addDomain" v-if="!disabled">
+            <template #icon>
+              <PlusOutlined/>
+            </template>
+            增加
+          </a-button>
+        </div>
+      </div>
+
+    </a-modal>
+  </div>
+</template>
+
+<script>
+import StFormModelItem from './module/StFormModelItem.vue';
+import {v1 as uuidv1} from 'uuid';
+import {DeleteOutlined, CopyOutlined, PlusOutlined} from '@ant-design/icons-vue';
+import {isMobile} from '/@/utils';
+
+export default {
+  name: 'StBatch',
+  props: {
+    record: {
+      type: Object,
+    },
+    value: {},
+    dynamicData: {},
+    config: {},
+    parentDisabled: {},
+    formData: {},
+    formConfig: {},
+  },
+  components: {
+    StFormModelItem,
+    DeleteOutlined,
+    CopyOutlined,
+    PlusOutlined,
+  },
+  watch: {
+    value: {
+      // value 需要深度监听及默认先执行handler函数
+      handler(val) {
+        this.dynamicValidateForm.domains = val || [];
+      },
+      immediate: true,
+      deep: true,
+    },
+  },
+  mounted() {
+    this.initTable();
+  },
+  data() {
+    return {
+      top: 0,
+      openChild: false,
+      dynamicValidateForm: {
+        domains: [],
+      },
+      page: 1, //初始页
+      size: 5, //每页数据
+      pagination: {
+        current: 1, //分页的索引
+        total: 0,
+        pageSize: 10, //每页中显示10条数据
+        size: 'small',
+        pageSizeOptions: ['10', '20', '50', '100'], //每页中显示的数据
+        showSizeChanger: true,
+        showQuickJumper: false,
+        showTotal: (total) => `共 ${total} 条数据`,
+      },
+      currentTableData: [],
+      mobileTableData: [],
+      mobilePage: {
+        page: 1,
+        size: 5
+      },
+    };
+  },
+  computed: {
+    firstData() {
+      if (this.dynamicValidateForm.domains.length == 0) {
+        return []
+      } else {
+        return this.dynamicValidateForm.domains.slice(0, 1)
+      }
+    },
+    listLength() {
+      return this.record.list.filter((item) => !item.options.hidden).length;
+    },
+    columns() {
+      const columns = [];
+      if (!this.record.options.hideSequence) {
+        columns.push({
+          title: '序号',
+          dataIndex: 'sequence_index_number',
+          width: '80px',
+          align: 'center',
+        });
+      }
+
+      columns.push(
+          ...this.record.list
+              .filter((item) => !item.options.hidden)
+              .map((item, index) => {
+                return {
+                  title: item.label,
+                  dataIndex: item.model,
+                  width: index === this.record.list.length - 1 ? '' : '300px',
+                };
+              })
+      );
+
+      if (!this.disabled) {
+        columns.push({
+          title: '操作',
+          dataIndex: 'dynamic-opr-button',
+          fixed: 'right',
+          width: '80px',
+          align: 'center',
+        });
+      }
+
+      return columns;
+    },
+    disabled() {
+      return this.record.options.disabled || this.parentDisabled;
+    },
+    isMobile() {
+      return isMobile();
+    },
+  },
+  methods: {
+    getSerialNumber(index) {
+      const {current, pageSize} = this.pagination;
+      return (current - 1) * pageSize + index + 1;
+    },
+    loadMoreHandle() {
+      const start = (this.mobilePage.page) * this.mobilePage.size;
+      const end = start + this.mobilePage.size;
+      this.mobileTableData = this.dynamicValidateForm.domains.slice(0, end);
+      this.mobilePage.page++;
+    },
+    initTable() {
+      this.page = this.pagination.current;
+      this.size = this.pagination.pageSize;
+      this.refreshCurrentTableData();
+    },
+
+    initMobileTable() {
+      this.mobilePage.page = 1;
+      this.mobilePage.size = 5;
+      this.mobileTableData = this.dynamicValidateForm.domains.length > this.mobilePage.size ? this.dynamicValidateForm.domains.slice(0, this.mobilePage.size) : this.dynamicValidateForm.domains;
+    },
+
+    getPageData(array, pageNumber, pageSize) {
+      // 计算开始索引
+      const start = (pageNumber - 1) * pageSize;
+      // 计算结束索引
+      const end = start + pageSize;
+      // 返回该页的数据
+      return array.slice(start, end);
+    },
+    tablePaginationChange(pagination) {
+      this.pagination.current = pagination.current;
+      this.pagination.pageSize = pagination.pageSize;
+      this.page = pagination.current;
+      this.size = pagination.pageSize;
+
+      this.refreshCurrentTableData();
+      this.pagination.total = pagination.total
+    },
+    validationSubform() {
+      return new Promise((resolve, reject) => {
+        this.$refs.dynamicValidateForm
+            .validateFields()
+            .then((res) => {
+              resolve(res);
+            })
+            .catch((err) => {
+              reject(err);
+            });
+      });
+    },
+    resetForm() {
+      this.$refs.dynamicValidateForm.resetFields();
+    },
+    removeDomain(item) {
+      const index = this.dynamicValidateForm.domains.indexOf(item);
+      if (index !== -1) {
+        this.dynamicValidateForm.domains.splice(index, 1);
+      }
+
+      this.refreshCurrentTableData();
+
+      this.handleInput('DELETE');
+    },
+    addDomain() {
+      const data = {};
+      this.record.list.forEach((item) => {
+        data[item.model] = item.options.defaultValue;
+      });
+
+      this.dynamicValidateForm.domains.push({
+        ...data,
+        key: uuidv1(),
+      });
+
+      this.refreshCurrentTableData();
+
+      // 跳转至最后一页;
+      if (this.dynamicValidateForm.domains.length % this.pagination.pageSize === 0) {
+        this.gotoPageNum(Math.floor(this.dynamicValidateForm.domains.length / this.pagination.pageSize));
+      } else {
+        this.gotoPageNum(Math.floor(this.dynamicValidateForm.domains.length / this.pagination.pageSize) + 1);
+      }
+
+      this.handleInput('ADD');
+    },
+    handleInput(...args) {
+      let params = {};
+      if (args.length == 1 && !['ADD', 'DELETE'].includes(args[0])) {
+        return false;
+      }
+      if (args.length == 4) {
+        let [value, key, record, tableInfo] = args;
+        tableInfo.optType = 'UPDATE';
+        params = {value, key, record, tableInfo};
+      } else {
+        params = {
+          value: this.dynamicValidateForm.domains,
+          key: this.record.model,
+          record: this.record,
+          tableInfo: {optType: args[0]}
+        };
+      }
+
+      this.$emit('update:value', this.dynamicValidateForm.domains);
+      this.$emit('change', params.value, this.record.model, this.record, params.tableInfo);
+    },
+    refreshCurrentTableData() {
+      this.currentTableData = this.getPageData(this.dynamicValidateForm.domains, this.pagination.current, this.pagination.pageSize);
+    },
+    // 跳转至某一页
+    gotoPageNum(num) {
+      if (num == this.pagination.current) return;
+      this.pagination.current = num;
+    }
+  },
+};
+</script>
+<style scoped lang="less">
+
+.dynamic-opr-button {
+  margin-left: 0px;
+}
+
+:deep(.css-dev-only-do-not-override-1r8ax8d).ant-table-wrapper .ant-table-thead > tr > th,
+:deep(.css-dev-only-do-not-override-1r8ax8d).ant-table-wrapper .ant-table-tbody > tr > td,
+:deep(.css-dev-only-do-not-override-1r8ax8d).ant-table-wrapper tfoot > tr > th,
+:deep(.css-dev-only-do-not-override-1r8ax8d).ant-table-wrapper tfoot > tr > td {
+  padding: 5px 5px;
+}
+
+:deep(.css-dev-only-do-not-override-1r8ax8d).ant-table-wrapper .ant-table-pagination.ant-pagination {
+  margin: 6px 0;
+}
+
+.dynamic-opr-button {
+  cursor: pointer;
+  position: relative;
+  top: 4px;
+  font-size: 16px;
+  color: #999;
+  transition: all 0.3s;
+  margin-left: 6px;
+}
+
+.dynamic-opr-button:hover {
+  color: #e89;
+}
+
+.dynamic-opr-button[disabled] {
+  cursor: not-allowed;
+  opacity: 0.5;
+}
+
+.batch-table {
+  display: flex;
+  flex-direction: column;
+
+  :deep(.ant-form-item) {
+    margin: 0;
+  }
+}
+
+.mobile-table {
+  :deep(.ant-card-body) {
+    padding: 15px;
+  }
+
+  .mobile-table-item {
+    width: 100%;
+
+    .mobile-table-item-content {
+      width: 100%;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 10px 0;
+      border-bottom: 1px solid #f0f0f0;
+
+      .mob-label {
+        width: 100px;
+      }
+
+      .mobile-tanle-form-item {
+        width: calc(100% - 100px);
+      }
+    }
+  }
+}
+
+/* 全屏模态框样式 */
+:deep(.fullscreen-modal) {
+  .ant-modal {
+    max-width: 100%;
+    width: 100% !important;
+    margin: 0;
+    top: 0;
+    padding-bottom: 0;
+    height: 100vh;
+
+    .ant-modal-content {
+      height: 100vh;
+      display: flex;
+      flex-direction: column;
+
+      .ant-modal-body {
+        padding: 0; /* 移除默认 padding */
+      }
+    }
+  }
+}
+
+/* 滚动容器 */
+.modal-scroll-container {
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+
+/* 可滚动的内容区域 */
+.scrollable-content {
+  flex: 1;
+  overflow-y: auto; /* 允许内容滚动 */
+  padding: 1px;
+}
+/* 悬浮在顶部的按钮容器 */
+.floating-top-button {
+  position: sticky;
+  top: 0;
+  z-index: 1000;
+  background: #fff;
+  padding: 6px 0px;
+  border-bottom: 1px solid #f0f0f0;
+  display: flex;
+  justify-content: flex-end; /* 按钮靠右 */
+}
+</style>

+ 2 - 0
src/views/flow/stFormDesign/packages/StBatch/index.js

@@ -0,0 +1,2 @@
+import StBatch from "./batch.vue";
+export default StBatch;

+ 299 - 0
src/views/flow/stFormDesign/packages/StBatch/module/StFormModelItem.vue

@@ -0,0 +1,299 @@
+<!--
+ * @Description: 传入record数据,通过判断record.type,来渲染对应的组件
+ -->
+<template>
+  <a-form-item
+    v-if="
+      [
+        'input',
+        'textarea',
+        'date',
+        'time',
+        'number',
+        'radio',
+        'checkbox',
+        'select',
+        'rate',
+        'switch',
+        'slider',
+        'uploadImg',
+        'uploadFile',
+        'cascader',
+        'treeSelect',
+        'popUpSelect',
+        'deptAndPersonSelect',
+      ].includes(record.type)
+    "
+    :name="['domains', rowIndex, record.model]"
+    :rules='record.rules'
+  >
+    <!-- 多行文本 -->
+    <a-textarea
+      :style='`width:${record.options.width}`'
+      v-if="record.type === 'textarea'"
+      :autoSize='{
+        minRows: record.options.minRows,
+        maxRows: record.options.maxRows
+      }'
+      :disabled='record.options.disabled || parentDisabled'
+      :placeholder="(record.options.disabled || parentDisabled)? '' : record.options.placeholder"
+      :allowClear='record.options.clearable'
+      :maxLength='record.options.maxLength'
+      :rows='4'
+      @change='handleChange($event, record.model, record, {...tableScope, rowIndex, row: domains[rowIndex], rows: domains, colIndex: tableScope.index || colIndex })'
+      v-model:value='domains[rowIndex][record.model]'
+    />
+    <!-- 弹出框 -->
+    <PopUpQueryChild
+      v-else-if="record.type === 'popUpSelect'"
+      :options='
+!record.options.dynamic
+          ? record.options.options
+          :record.options.dynamicOptions?
+          record.options.dynamicOptions:
+          (dynamicData[record.options.dynamicKey]
+          ? dynamicData[record.options.dynamicKey]
+          : [])
+      '
+      :disabled='parentDisabled ||record.options.disabled'
+      :placeholder="(record.options.disabled || parentDisabled)? '' : record.options.placeholder"
+      @change='handleChange($event, record.model, record, {...tableScope, rowIndex, row: domains[rowIndex], rows: domains, colIndex: tableScope.index || colIndex })'
+      :formConfig='formConfig'
+      :rowData='domains[rowIndex]'
+      :childTableFieldRecord='childTableFieldRecord'
+      :record='record'
+      :formData='formData'
+      :content='value'
+    />
+    <!-- 单选框 -->
+    <a-radio-group
+      v-else-if="record.type === 'radio'"
+      :options='
+        !record.options.dynamic
+          ? record.options.options
+          :record.options.dynamicOptions?
+          record.options.dynamicOptions:
+          (dynamicData[record.options.dynamicKey]
+          ? dynamicData[record.options.dynamicKey]
+          : [])
+      '
+      :disabled='record.options.disabled || parentDisabled'
+      :placeholder="(record.options.disabled || parentDisabled)? '' : record.options.placeholder"
+      v-model:value='domains[rowIndex][record.model]'
+      v-model:checked='domains[rowIndex][record.model]'
+      @change='handleChange($event, record.model, record, {...tableScope, rowIndex, row: domains[rowIndex], rows: domains, colIndex: tableScope.index || colIndex })'
+    />
+
+    <!-- 多选框 -->
+    <a-checkbox-group
+      v-else-if="record.type === 'checkbox'"
+      :options='
+        !record.options.dynamic
+          ? record.options.options
+          :record.options.dynamicOptions?
+          record.options.dynamicOptions:
+          (dynamicData[record.options.dynamicKey]
+          ? dynamicData[record.options.dynamicKey]
+          : [])
+      '
+      :disabled='record.options.disabled || parentDisabled'
+      :placeholder="(record.options.disabled || parentDisabled)? '' : record.options.placeholder"
+      v-model:value='domains[rowIndex][record.model]'
+      @change='handleChange($event, record.model, record, {...tableScope, rowIndex, row: domains[rowIndex], rows: domains, colIndex: tableScope.index || colIndex })'
+    />
+
+    <!-- 滑块 -->
+    <div
+      v-else-if="record.type === 'slider'"
+      :style='`width:${record.options.width}`'
+      class='slider-box'
+    >
+      <div class='slider'>
+        <a-slider
+          :disabled='record.options.disabled || parentDisabled'
+          :min='record.options.min'
+          :max='record.options.max'
+          :step='record.options.step'
+          v-model:value='domains[rowIndex][record.model]'
+          @change='handleChange($event, record.model, record, {...tableScope, rowIndex, row: domains[rowIndex], rows: domains, colIndex: tableScope.index || colIndex })'
+        />
+      </div>
+      <div class='number' v-if='record.options.showInput'>
+        <a-input-number
+          style='width:100%'
+          :disabled='record.options.disabled || parentDisabled'
+          :placeholder="(record.options.disabled || parentDisabled)? '' : record.options.placeholder"
+          :min='record.options.min'
+          :max='record.options.max'
+          :step='record.options.step'
+          v-model:value='domains[rowIndex][record.model]'
+          @change='handleChange($event, record.model, record, {...tableScope, rowIndex, row: domains[rowIndex], rows: domains, colIndex: tableScope.index || colIndex })'
+        />
+      </div>
+    </div>
+
+    <component
+      v-else
+      :style='`width:${record.options.width}`'
+      v-bind='componentOption'
+      :min='
+        record.options.min || record.options.min === 0
+          ? record.options.min
+          : -Infinity
+      '
+      :max='
+        record.options.max || record.options.max === 0
+          ? record.options.max
+          : Infinity
+      '
+      :count='record.options.max'
+      :precision='record.options.precision || 0'
+      :formatter="value =>record.options.thousandth==true&&value!=''?
+                 `${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ','):value"
+      :record='record'
+      :config='config'
+      :disabled='record.options.disabled || parentDisabled'
+      :parentDisabled='record.options.disabled || parentDisabled'
+      :allowClear='record.options.clearable'
+      :dynamicData='dynamicData'
+      :filterOption='
+        record.options.showSearch
+          ? (inputValue, option) => {
+            return (
+              option.label.includes(inputValue)
+            );
+          }
+        : false
+      '
+      :treeData='
+!record.options.dynamic
+          ? record.options.options
+          :record.options.dynamicOptions?
+          record.options.dynamicOptions:
+          (dynamicData[record.options.dynamicKey]
+          ? dynamicData[record.options.dynamicKey]
+          : [])
+      '
+      :options='
+!record.options.dynamic
+          ? record.options.options
+          :record.options.dynamicOptions?
+          record.options.dynamicOptions:
+          (dynamicData[record.options.dynamicKey]
+          ? dynamicData[record.options.dynamicKey]
+          : [])
+      '
+      :mode="record.options.multiple ? 'multiple' : ''"
+      v-model:checked='currentVal'
+      v-model:value='currentVal'
+      @change='handleChange($event, record.model, record, {...tableScope, rowIndex, row: currentTableData[rowIndex], rows: domains, columns, pagination })'
+      :is='componentItem'
+      :placeholder="(record.options.disabled || parentDisabled) ? '' : record.options.placeholder"
+    ></component>
+  </a-form-item>
+  <!-- 文本 -->
+  <a-form-item v-else-if="record.type === 'text'">
+    <div :style='{ textAlign: record.options.textAlign }'>
+      <label
+        :class="{ 'ant-form-item-required': record.options.showRequiredMark }"
+        :style='{
+          fontFamily: record.options.fontFamily,
+          fontSize: record.options.fontSize,
+          color: record.options.color
+        }'
+        v-text='record.label'
+      ></label>
+    </div>
+  </a-form-item>
+
+  <!-- html -->
+  <div
+    v-else-if="record.type === 'html'"
+    v-html='record.options.defaultValue'
+  ></div>
+
+  <div v-else>
+    <!-- 空 -->
+  </div>
+
+</template>
+<script>
+
+// import moment from "moment";
+
+import UploadFile from '../../UploadFile/uploadFile.vue'
+import PopUpQueryChild from '../../PopUpQueryChild/index.vue'
+import UploadImg from '../../UploadImg/uploadImg.vue'
+import StDatePicker from '../../StDatePicker/datePicker.vue'
+import StTimePicker from '../../StTimePicker/timePicker.vue'
+import ComponentArray from '../../core/components_use'
+
+import _ from "lodash/object"
+
+export default {
+  name: 'StFormItem',
+  props: [
+    'record',
+    'domains',
+    'index',
+    'value',
+    'parentDisabled',
+    'dynamicData',
+    'config',
+    'formData',
+    'formConfig',
+    'childTableFieldRecord',
+    'rowIndex',
+    'tableScope',
+    'columns',
+    'currentTableData',
+    'pagination',
+    'colIndex'
+  ],
+  mounted() {
+  },
+  components: {
+    UploadImg,
+    UploadFile,
+    StDatePicker,
+    StTimePicker,
+    PopUpQueryChild
+  },
+
+  computed: {
+    componentItem() {
+      return ComponentArray[this.record.type]
+    },
+    componentOption() {
+      return _.omit(this.record.options, ['defaultValue', 'disabled'])
+    },
+    currentVal() {
+      return this.value;
+    }
+  },
+  methods: {
+    handleChange(e, model, record, tableInfo) {
+      let value = e
+      if (e?.target) {
+        value = e.target.value
+      }
+      this.$emit('update:value', value);//用来支持绑定,更不能删除
+      this.$emit('input', value, model, record, tableInfo)//用来给父传值,不可删除
+    }
+  },
+}
+</script>
+<style lang='less' scoped>
+.slider-box {
+  display: flex;
+
+  > .slider {
+    flex: 1;
+    margin-right: 16px;
+  }
+
+  > .number {
+    width: 70px;
+  }
+}
+</style>

+ 225 - 0
src/views/flow/stFormDesign/packages/StChangeOption/index.vue

@@ -0,0 +1,225 @@
+<template>
+  <div class="option-change-container">
+    <a-row v-if="type === 'option' || type === 'tab'" :gutter="8">
+      <div class="option-change-box" v-for="(val, index) in value" :key="index">
+        <a-row>
+          <a-col :span="9">
+            <a-input v-model:value="val.label" placeholder="名称" />
+          </a-col>
+          <a-col :span="9">
+            <a-input v-model:value="val.value" placeholder="值" />
+          </a-col>
+          <a-col :span="6">
+            <div @click="handleDelete(index)" class="option-delete-box">
+              <DeleteOutlined />
+            </div>
+          </a-col>
+        </a-row>
+      </div>
+      <a-col :span="24"><a @click="handleAdd">添加</a></a-col>
+    </a-row>
+
+    <a-row v-if="type === 'rules'" :gutter="8">
+      <div class="option-change-box" v-for="(val, index) in value" :key="index">
+        <a-row v-if="index !== 0 ">
+          <a-col :span="24" v-if="val.flag == 'Internal'">
+            <a-input v-model:value="val.message" placeholder="提示信息" />
+          </a-col>
+          <a-col :span="24">
+            <div class="rule-item">
+              <div v-if="val.flag === 'Internal'">内置校验【{{ val.text }}】</div>
+              <a-button type="primary" v-if="val.flag !== 'Internal'"  @click="customHandle(val)">自定义校验代码</a-button>
+              <div @click="handleDelete(index, val)" class="option-delete-box">
+                <DeleteOutlined />
+              </div>
+            </div>
+          </a-col>
+        </a-row>
+      </div>
+      <a-col :span="24">
+        <a-popover placement="leftBottom">
+          <template #content>
+            <div class="btns-list">
+              <a-button @click="handleAddRules('phoneNumber', 'Internal')">手机号</a-button>
+              <a-button @click="handleAddRules('idCard', 'Internal')">身份证号
+              </a-button>
+              <a-button @click="handleAddRules('email', 'Internal')">邮箱
+              </a-button>
+              <a-button @click="handleAddRules('custom', 'Custom')" :disabled="value.findIndex(v => v.type == 'Custom') > -1">自定义
+              </a-button>
+            </div>
+          </template>
+          <template #title>
+            <span>常用校验</span>
+          </template>
+          <a-button type="link">增加校验</a-button>
+        </a-popover>
+      </a-col>
+
+    </a-row>
+
+    <a-row v-else-if="type === 'colspan'" :gutter="8">
+      <div class="option-change-box" v-for="(val, index) in value" :key="index">
+        <a-row align="middle">
+          <a-col :span="20">
+            <a-input-number style="width: 100%" :max="24" v-model:value="val.span" placeholder="名称" />
+          </a-col>
+          <a-col :span="4">
+            <div @click="handleDelete(index)" class="option-delete-box" style="margin-left: 10px">
+              <DeleteOutlined />
+            </div>
+          </a-col>
+        </a-row>
+      </div>
+      <a-col :span="24"><a @click="handleAddCol">添加</a></a-col>
+    </a-row>
+  </div>
+</template>
+<script>
+/*
+ * description 修改多选、下拉、单选等控件options的组件,添加移除校验规制的组件
+ */
+import { DeleteOutlined } from '@ant-design/icons-vue';
+import { v1 as uuidv1 } from "uuid";
+import { deleteScriptCode } from "/@/api/flow/flowApi"
+
+export default {
+  name: 'StChangeOption',
+  components: {
+    DeleteOutlined,
+  },
+  props: {
+    value: {
+      type: Array,
+      required: true,
+    },
+    type: {
+      type: String,
+      default: 'option',
+    },
+  },
+  methods: {
+    customHandle(obj) {
+      this.$emit('customRuleHandle', obj)
+    },
+
+    handleAdd() {
+      // 添加
+      const addData = [
+        ...this.value,
+        {
+          value: `${this.value.length + 1}`,
+          label: '选项' + (this.value.length + 1),
+          list: this.type === 'tab' ? [] : undefined,
+        },
+      ];
+      this.$emit('update:value', addData);
+    },
+    handleAddCol() {
+      // 添加栅格Col
+      const addData = [
+        ...this.value,
+        {
+          span: 8,
+          list: [],
+        },
+      ];
+      this.$emit('update:value', addData);
+    },
+    handleAddRules(mode, flag) {
+      let keyCode = mode;
+      let message = '';
+      let text = '';
+      let eventKey = "";
+      switch (mode) {
+        case 'phoneNumber':
+          message = '请输入正确的手机号';
+          text = '手机号';
+          break;
+        case 'idCard':
+          message = '请输入正确的身份证号';
+          text = '身份证号';
+          break;
+        case 'email':
+          message = '请输入正确的邮箱';
+          text = '邮箱';
+          break;
+        case 'custom':
+          eventKey = uuidv1();
+        default:
+          break;
+      }
+
+      if (mode) {
+        const addData = [
+          ...this.value,
+          {
+            keyCode,
+            message,
+            flag:flag,
+            text,
+            eventKey
+          },
+        ];
+        console.log(addData,'add')
+        this.$emit('update:value', addData);
+      }
+    },
+    handleDelete(deleteIndex, val) {
+
+      if(val && val.type === 'Custom') {
+        deleteScriptCode(val.eventKey)
+      }
+
+      // 删除
+      this.$emit(
+        'update:value',
+        this.value.filter((val, index) => index !== deleteIndex)
+      );
+    },
+  },
+};
+</script>
+<style lang="less" scoped>
+.option-change-container {
+  width: calc(100% - 8px);
+}
+
+.option-change-box {
+  // height: 38px;
+  padding-bottom: 6px;
+
+  .option-delete-box {
+    width: 100%;
+    margin-top: 3px;
+    background: #ffe9e9;
+    color: #f22;
+    width: 32px;
+    height: 32px;
+    line-height: 32px;
+    text-align: center;
+    border-radius: 50%;
+    overflow: hidden;
+    transition: all 0.3s;
+    cursor: pointer;
+
+    &:hover {
+      background: #f22;
+      color: #fff;
+    }
+  }
+}
+
+.rule-item {
+  width: 100%;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.btns-list {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+</style>

+ 35 - 0
src/views/flow/stFormDesign/packages/StCheckbox/index.vue

@@ -0,0 +1,35 @@
+<template>
+  <a-checkbox @change="handleChange" :checked="value">
+    {{ label }}
+  </a-checkbox>
+</template>
+<script>
+/*
+ * description 多选框组件,改成v-model Boolean值
+ */
+import { defineComponent } from "vue"
+export default defineComponent({
+  name: "stCheckbox",
+  data() {
+    return {
+      
+    };
+  },
+  props: {
+    value: {
+      type: Boolean,
+      default: false
+    },
+    label: {
+      type: String,
+      default: ""
+    }
+  },
+  methods: {
+    handleChange(e) {
+      const target = e.target;
+      this.$emit('update:value', target.checked);
+    }  
+  }
+});
+</script>

+ 60 - 0
src/views/flow/stFormDesign/packages/StDatePicker/datePicker.vue

@@ -0,0 +1,60 @@
+<!--
+ * @Description: 日期选择器
+ -->
+<template>
+  <div>
+    <!-- 日期选择 -->
+    <a-date-picker
+      :style="`width:${record.options.width}`"
+      v-if="record.type === 'date' && record.options.range === false"
+      :disabled="record.options.disabled || parentDisabled"
+      :show-time="record.options.showTime"
+      :allowClear="record.options.clearable"
+      :placeholder="(record.options.disabled || parentDisabled)? '' : record.options.placeholder"
+      :format="record.options.format"
+      :valueFormat="record.options.format"
+      @change="handleSelectChange"
+      :value="value"
+    />
+    <!-- 范围日期选择 -->
+    <a-range-picker
+      :style="`width:${record.options.width}`"
+      v-else-if="record.type === 'date' && record.options.range === true"
+      :show-time="record.options.showTime"
+      :disabled="record.options.disabled || parentDisabled"
+      :allowClear="record.options.clearable"
+      :placeholder="(record.options.disabled || parentDisabled)? '' : record.options.rangePlaceholder"
+      :format="record.options.format"
+      :valueFormat="record.options.format"
+      @change="handleSelectChange"
+      :value="value"
+    />
+  </div>
+</template>
+
+<script setup>
+  import dayjs from 'dayjs';
+  const emit = defineEmits(['update:value', 'change', 'input']);
+  const props = defineProps({
+    record: {
+      type: Object,
+      required: true,
+    },
+    value: {
+      type: String,
+      required: true,
+    },
+    parentDisabled: {
+      type: Boolean,
+      default: false,
+    },
+  });
+
+  const handleSelectChange = (value) => {
+    emit('update:value', value);
+    emit('change', value);
+    emit('input', value)
+  };
+</script>
+
+<style lang="less" scoped></style>

+ 2 - 0
src/views/flow/stFormDesign/packages/StDatePicker/index.js

@@ -0,0 +1,2 @@
+import DatePicker from "./datePicker.vue";
+export default DatePicker;

+ 153 - 0
src/views/flow/stFormDesign/packages/StDeptSelector/index.vue

@@ -0,0 +1,153 @@
+<template>
+  <a-tree-select
+      :value="value"
+      style="width: 100%"
+      :tree-data="newTreeData"
+      allow-clear
+      :placeholder="record.options.disabled || parentDisabled ? '' : record.options.placeholder"
+      :disabled="record.options.disabled || parentDisabled"
+      show-search
+      :multiple="isMultiple"
+      :tree-checkable="isTreeCheckable"
+      :field-names="{
+      children: 'children',
+      label: 'label',
+      value: 'value',
+    }"
+      :height="300"
+      tree-default-expand-all
+      tree-node-filter-prop="label"
+      @change="handleChange"
+  >
+    <template #title="{ icon, label, type }">
+      <div style="display: flex; align-items: center; gap: 5px">
+        <TeamOutlined v-if="type == 'dept'"/>
+        <IdcardOutlined v-if="type == 'post'"/>
+        <UserOutlined v-if="type == 'emp'"/>
+        <span>{{ label }}</span>
+      </div>
+    </template>
+  </a-tree-select>
+</template>
+<script setup>
+import {computed, nextTick, onMounted, ref, watch, toRefs} from 'vue';
+import {TreeSelect} from 'ant-design-vue';
+import _ from 'lodash';
+import {UserOutlined, IdcardOutlined, TeamOutlined} from '@ant-design/icons-vue';
+
+const props = defineProps({
+  record: {
+    type: Object,
+    required: true,
+  },
+  value: {
+    type: String,
+    required: true,
+  },
+  parentDisabled: {
+    type: Boolean,
+    default: false,
+  },
+  dynamicData: {
+    type: Array,
+    required: true,
+  },
+  treeData: {
+    type: Array,
+    required: true,
+  },
+});
+const newTreeData = ref([]);
+
+const {record, treeData, value, parentDisabled, dynamicData} = toRefs(props);
+
+const emit = defineEmits(['changeHandle']);
+
+const removeTypeItems = (data, types, filterCodes) => {
+  // 映射函数,用于递归处理每个部门
+  const processDepartment = (department) => {
+    department.disableCheckbox = department.type && department.type === 'dept' && !isDept.value;
+    department.disabled = department.type && department.type === 'dept' && !isDept.value;
+    // 如果有子元素,过滤并递归处理
+    if (Array.isArray(department.children)) {
+      department.children = department.children
+          .filter((child) => types.findIndex((el) => el === child.type) == -1)
+          .map((child) => processDepartment(child)); // 递归处理子元素
+    }
+    return department;
+  };
+  // 使用 map 处理顶层数组,确保不直接修改原始数据
+  let result = data && data.map((department) => processDepartment(department));
+  //处理过滤的情况
+  let arrFilter = []
+  if (filterCodes) {
+    arrFilter = filterCodes.split(/[,,]/);
+  }
+  if (arrFilter.length > 0) {
+    let filterAllData = []
+    for (const item of arrFilter) {
+      filterAllData = filterAllData.concat(findItemsByValue(result, item))
+    }
+    result = filterAllData
+  }
+  return result;
+};
+const findItemsByValue = (data, targetValue) => {
+  // 定义一个数组来存储所有匹配的项
+  const result = [];
+
+  // 定义递归函数
+  function recursiveSearch(items) {
+    // 遍历当前层级的每一项
+    for (const item of items) {
+      // 检查当前项的 value 是否匹配目标值
+      if (item.value === targetValue) {
+        result.push(item); // 如果匹配,将该项加入结果数组
+      } else {
+        // 如果当前项有子项,递归调用
+        if (item.children && item.children.length > 0) {
+          recursiveSearch(item.children);
+        }
+      }
+    }
+  }
+
+  // 从顶层数据开始递归搜索
+  recursiveSearch(data);
+
+  // 返回最终结果
+  return result;
+}
+const isPost = computed(() => props.record.options.post);
+const isEmp = computed(() => props.record.options.emp);
+const isDept = computed(() => props.record.options.dept);
+const isFilterOrg = computed(() => props.record.options.parentCodes);
+
+const isMultiple = computed(() => props.record.options.multiple);
+const isTreeCheckable = computed(() => props.record.options.treeCheckable);
+
+const handleType = () => {
+  if (isEmp.value && !isPost.value && !isDept.value) {
+    return ['post'];
+  } else if (isPost.value && !isEmp.value && !isDept.value) {
+    return ['emp'];
+  } else if (isDept.value && !isPost.value && !isEmp.value) {
+    return ['post', 'emp'];
+  } else {
+    return [];
+  }
+};
+
+watch(treeData, (newVal, oldVal) => {
+  // 在这里处理 treeData 的变化
+  if (newVal.length > 0) {
+    const types = handleType();
+    newTreeData.value = removeTypeItems(_.cloneDeep(newVal), types, isFilterOrg.value);
+  }
+}, {immediate: true, deep: true});
+
+const handleChange = (value) => {
+  emit('changeHandle', value);
+  emit("update:value", value);
+};
+</script>

+ 136 - 0
src/views/flow/stFormDesign/packages/StEditor/StEditor.vue

@@ -0,0 +1,136 @@
+<!--
+ * @Description: 对富文本封装
+ -->
+<template>
+  <div
+      :style="{ minHeight: '300px', // 设置最小高度为 300px
+       height: record.options.height ? `${record.options.height}px` : 'auto' }"
+  >
+    <Toolbar
+        v-show="!record.options.disabled"
+        :editor="editorRef"
+        :defaultConfig="toolbarConfig"
+        style="border-bottom: 1px solid #ccc;"
+    />
+    <Editor
+        ref="wangEditEle"
+        :value="value"
+        :defaultConfig="editorConfig"
+        style="height: calc(100% - 40px)  "
+        @onChange="onEditorChange($event)"
+        @onCreated="handleCreated"
+        :disabled="record.options.disabled"
+        :options="editorOption"
+    />
+  </div>
+</template>
+<script setup>
+import {ref, shallowRef, onBeforeUnmount, watch, onMounted, getCurrentInstance} from 'vue';
+import "@wangeditor/editor/dist/css/style.css";
+import {Editor, Toolbar} from "@wangeditor/editor-for-vue";
+import {localRead} from "/@/utils/local-util.js";
+import LocalStorageKeyConst from "/@/constants/local-storage-key-const.js";
+
+const props = defineProps({
+  value: String,
+  record: Object,
+  parentDisabled: Boolean
+});
+const wangEditEle = ref()
+const emit = defineEmits(['update:value', 'change']);
+const editorOption = ref({
+  placeholder: (props.record.options.disabled || props.parentDisabled) ? '' : props.record.options.placeholder || '',
+  theme: 'snow',
+});
+let flag = ref(0)
+const handleCreated = (editor) => {
+  console.log('handleCreated')
+  editorRef.value = editor;
+  flag.value = 1
+};
+onMounted(() => {
+  setTimeout(() => {
+    const editor = editorRef.value;
+    if (props.record.options.disabled == true) {
+      editor.disable()
+    }
+  }, 2000)
+})
+
+watch(() => props.value, (newVal) => {
+  if (flag.value === 1 && newVal) {
+    flag.value = 2
+    const editor = editorRef.value;
+    console.log(editor.getHtml(), editor, 'editor.getHtml()')
+    if (newVal.startsWith('<ul>')) {
+      newVal = newVal.replace(/^<ul>/, "").replace(/<\/ul>$/, "")
+      editor.setHtml(newVal)
+    } else {
+      editor.setHtml(newVal)
+    }
+
+  }
+}, {immediate: true});
+
+
+onBeforeUnmount(() => {
+  const editor = editorRef.value;
+  if (editor) editor.destroy();
+});
+
+const toolbarConfig = {
+  toolbarKeys: [
+    "headerSelect",
+    "bold",
+    "italic",
+    "underline",
+    "indent",
+    "justifyCenter",
+    "justifyJustify",
+    "justifyLeft",
+    "justifyRight",
+    "bulletedList",
+    "numberedList",
+    "color",
+    "fontSize",
+    "lineHeight",
+    "clearStyle",
+    "undo",
+    "redo",
+    "uploadImage",
+    "insertLink",
+    "insertImage",
+    "fullScreen",
+
+  ]
+};
+const editorRef = shallowRef();
+const editorConfig = {
+  placeholder: '请输入内容...',
+  MENU_CONF: {
+    uploadImage: {
+      server: import.meta.env.VITE_APP_API_URL + '/support/file/upload/wangEditor',
+      headers: {
+        'x-access-token': localRead(LocalStorageKeyConst.USER_TOKEN)
+      }
+    },
+
+  },
+
+};
+
+const onEditorChange = (editor) => {
+  emit('update:value', editor.getHtml());
+  emit('change', editor.getHtml(), props.record.model, props.record);
+};
+
+// 暴露给模板的响应式数据和方法
+defineExpose({
+  onEditorChange,
+});
+</script>
+<style scoped lang="less">
+.w-e-full-screen-container {
+  z-index: 99999 !important;
+}
+</style>

Some files were not shown because too many files changed in this diff