After Width: | Height: | Size: 177 KiB |
After Width: | Height: | Size: 126 KiB |
22
README.md
|
@ -120,18 +120,22 @@
|
|||
|
||||
### 工作流程
|
||||
|
||||
| | 功能 | 描述 |
|
||||
|-----|-------|----------------------------------------|
|
||||
| 🚀 | 流程模型 | 配置工作流的流程模型,支持文件导入与在线设计流程图,提供 7 种任务分配规则 |
|
||||
| 🚀 | 流程表单 | 拖动表单元素生成相应的工作流表单,覆盖 Element UI 所有的表单组件 |
|
||||
| 🚀 | 用户分组 | 自定义用户分组,可用于工作流的审批分组 |
|
||||
| 🚀 | 我的流程 | 查看我发起的工作流程,支持新建、取消流程等操作,高亮流程图、审批时间线 |
|
||||
| 🚀 | 待办任务 | 查看自己【未】审批的工作任务,支持通过、不通过、转发、委派、退回等操作 |
|
||||
| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,未来会支持回退操作 |
|
||||
| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 |
|
||||
| | 功能 | 描述 |
|
||||
|----|-------|-----------------------------------------|
|
||||
| 🚀 | 流程模型 | 配置工作流的流程模型,支持 BPMN 和仿钉钉/飞书设计器 |
|
||||
| 🚀 | 流程表单 | 拖动表单元素生成相应的工作流表单,覆盖 Element UI 所有的表单组件 |
|
||||
| 🚀 | 用户分组 | 自定义用户分组,可用于工作流的审批分组 |
|
||||
| 🚀 | 我的流程 | 查看我发起的工作流程,支持新建、取消流程等操作,高亮流程图、审批时间线 |
|
||||
| 🚀 | 待办任务 | 查看自己【未】审批的工作任务,支持通过、不通过、转派、委派、退回、加减签等操作 |
|
||||
| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,支持流程预测,展示未来审批人信息 |
|
||||
| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 |
|
||||
|
||||
![功能图](/.image/common/bpm-feature.png)
|
||||
|
||||
| BPMN 设计器 | 钉钉/飞书设计器 |
|
||||
|------------------------------|--------------------------------|
|
||||
| ![](/.image/工作流设计器-bpmn.jpg) | ![](/.image/工作流设计器-simple.jpg) |
|
||||
|
||||
### 支付系统
|
||||
|
||||
| | 功能 | 描述 |
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"qrcode": "^1.5.3",
|
||||
"qs": "^6.12.0",
|
||||
"sortablejs": "^1.15.3",
|
||||
"steady-xml": "^0.1.0",
|
||||
"url": "^0.11.3",
|
||||
"video.js": "^7.21.5",
|
||||
|
@ -95,7 +96,7 @@
|
|||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"bpmn-js": "8.9.0",
|
||||
"bpmn-js": "8.10.0",
|
||||
"bpmn-js-properties-panel": "0.46.0",
|
||||
"consola": "^3.2.3",
|
||||
"eslint": "^8.57.0",
|
||||
|
|
171
pnpm-lock.yaml
|
@ -13,10 +13,10 @@ importers:
|
|||
version: 2.3.1(vue@3.5.12(typescript@5.3.3))
|
||||
'@form-create/designer':
|
||||
specifier: ^3.2.6
|
||||
version: 3.2.7(vue@3.5.12(typescript@5.3.3))
|
||||
version: 3.2.8(vue@3.5.12(typescript@5.3.3))
|
||||
'@form-create/element-ui':
|
||||
specifier: ^3.2.11
|
||||
version: 3.2.11(vue@3.5.12(typescript@5.3.3))
|
||||
version: 3.2.13(vue@3.5.12(typescript@5.3.3))
|
||||
'@iconify/iconify':
|
||||
specifier: ^3.1.1
|
||||
version: 3.1.1
|
||||
|
@ -125,6 +125,9 @@ importers:
|
|||
qs:
|
||||
specifier: ^6.12.0
|
||||
version: 6.12.1
|
||||
sortablejs:
|
||||
specifier: ^1.15.3
|
||||
version: 1.15.3
|
||||
steady-xml:
|
||||
specifier: ^0.1.0
|
||||
version: 0.1.0
|
||||
|
@ -214,11 +217,11 @@ importers:
|
|||
specifier: ^10.4.17
|
||||
version: 10.4.19(postcss@8.4.38)
|
||||
bpmn-js:
|
||||
specifier: 8.9.0
|
||||
version: 8.9.0
|
||||
specifier: 8.10.0
|
||||
version: 8.10.0
|
||||
bpmn-js-properties-panel:
|
||||
specifier: 0.46.0
|
||||
version: 0.46.0(bpmn-js@8.9.0)
|
||||
version: 0.46.0(bpmn-js@8.10.0)
|
||||
consola:
|
||||
specifier: ^3.2.3
|
||||
version: 3.2.3
|
||||
|
@ -296,7 +299,7 @@ importers:
|
|||
version: 0.8.0(rollup@4.17.1)
|
||||
unplugin-vue-components:
|
||||
specifier: ^0.25.2
|
||||
version: 0.25.2(@babel/parser@7.25.8)(rollup@4.17.1)(vue@3.5.12(typescript@5.3.3))
|
||||
version: 0.25.2(@babel/parser@7.26.2)(rollup@4.17.1)(vue@3.5.12(typescript@5.3.3))
|
||||
vite:
|
||||
specifier: 5.1.4
|
||||
version: 5.1.4(@types/node@20.12.7)(sass@1.75.0)(terser@5.30.4)
|
||||
|
@ -451,16 +454,16 @@ packages:
|
|||
resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-string-parser@7.25.7':
|
||||
resolution: {integrity: sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==, tarball: https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz}
|
||||
'@babel/helper-string-parser@7.25.9':
|
||||
resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==, tarball: https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-identifier@7.22.20':
|
||||
resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-identifier@7.25.7':
|
||||
resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==, tarball: https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz}
|
||||
'@babel/helper-validator-identifier@7.25.9':
|
||||
resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==, tarball: https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-option@7.23.5':
|
||||
|
@ -484,8 +487,8 @@ packages:
|
|||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@babel/parser@7.25.8':
|
||||
resolution: {integrity: sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==, tarball: https://registry.npmmirror.com/@babel/parser/-/parser-7.25.8.tgz}
|
||||
'@babel/parser@7.26.2':
|
||||
resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==, tarball: https://registry.npmmirror.com/@babel/parser/-/parser-7.26.2.tgz}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
|
@ -961,8 +964,8 @@ packages:
|
|||
resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/types@7.25.8':
|
||||
resolution: {integrity: sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==, tarball: https://registry.npmmirror.com/@babel/types/-/types-7.25.8.tgz}
|
||||
'@babel/types@7.26.0':
|
||||
resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==, tarball: https://registry.npmmirror.com/@babel/types/-/types-7.26.0.tgz}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@bpmn-io/diagram-js-ui@0.2.3':
|
||||
|
@ -1070,7 +1073,7 @@ packages:
|
|||
postcss-selector-parser: ^6.0.13
|
||||
|
||||
'@ctrl/tinycolor@3.6.1':
|
||||
resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==, tarball: https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz}
|
||||
resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@dual-bundle/import-meta-resolve@4.0.0':
|
||||
|
@ -1238,13 +1241,13 @@ packages:
|
|||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
|
||||
'@floating-ui/core@1.6.1':
|
||||
resolution: {integrity: sha512-42UH54oPZHPdRHdw6BgoBD6cg/eVTmVrFcgeRDM3jbO7uxSoipVcmcIGFcA5jmOHO5apcyvBhkSKES3fQJnu7A==, tarball: https://registry.npmmirror.com/@floating-ui/core/-/core-1.6.1.tgz}
|
||||
resolution: {integrity: sha512-42UH54oPZHPdRHdw6BgoBD6cg/eVTmVrFcgeRDM3jbO7uxSoipVcmcIGFcA5jmOHO5apcyvBhkSKES3fQJnu7A==}
|
||||
|
||||
'@floating-ui/dom@1.6.4':
|
||||
resolution: {integrity: sha512-0G8R+zOvQsAG1pg2Q99P21jiqxqGBW1iRe/iXHsBRBxnpXKFI8QwbB4x5KmYLggNO5m34IQgOIu9SCRfR/WWiQ==, tarball: https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.6.4.tgz}
|
||||
resolution: {integrity: sha512-0G8R+zOvQsAG1pg2Q99P21jiqxqGBW1iRe/iXHsBRBxnpXKFI8QwbB4x5KmYLggNO5m34IQgOIu9SCRfR/WWiQ==}
|
||||
|
||||
'@floating-ui/utils@0.2.2':
|
||||
resolution: {integrity: sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==, tarball: https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.2.tgz}
|
||||
resolution: {integrity: sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==}
|
||||
|
||||
'@form-create/component-elm-checkbox@3.2.8':
|
||||
resolution: {integrity: sha512-ol/SyzzeDueUTd87MPnYydOe7Sc6cL8S/Uhv5QmWofMY1TuuBet9DPb65JjyS6Lk51/cl3TabvtJj93EAxL6KA==, tarball: https://registry.npmmirror.com/@form-create/component-elm-checkbox/-/component-elm-checkbox-3.2.8.tgz}
|
||||
|
@ -1264,8 +1267,8 @@ packages:
|
|||
'@form-create/component-elm-tree@3.2.9':
|
||||
resolution: {integrity: sha512-5NG4YeFZ5jzN9Aa0JFuFD8OGKXBqSHSN0KRgxxUgdhzRg8hcRq/JODuN7yYMa7YrBP0ecTKyel8Q4ufR5Ct8iw==, tarball: https://registry.npmmirror.com/@form-create/component-elm-tree/-/component-elm-tree-3.2.9.tgz}
|
||||
|
||||
'@form-create/component-elm-upload@3.2.9':
|
||||
resolution: {integrity: sha512-PdYlUCRs7x/zQjkDkTX9q3116ysKUPZ4R4OwzhSc430JPLSVUCx/CqlhenbAnqZFEj5khwnvppbYSzrTTaDa4A==, tarball: https://registry.npmmirror.com/@form-create/component-elm-upload/-/component-elm-upload-3.2.9.tgz}
|
||||
'@form-create/component-elm-upload@3.2.13':
|
||||
resolution: {integrity: sha512-qngh1Hzb/Oo51gbh3LDiMmUnDaa2+k7sXS4GEZujoDuKCctBjG60y3pi214CmOqBq9PiynM8knf6yQKqpjlRqA==, tarball: https://registry.npmmirror.com/@form-create/component-elm-upload/-/component-elm-upload-3.2.13.tgz}
|
||||
|
||||
'@form-create/component-subform@3.1.34':
|
||||
resolution: {integrity: sha512-OJcFH/7MTHx7JLEjDK/weS27qfuFWAI+OK+gXTJ2jIt9aZkGWF/EWkjetiJLt5a0KMw4Z15wOS2XCY9pVK9vlA==, tarball: https://registry.npmmirror.com/@form-create/component-subform/-/component-subform-3.1.34.tgz}
|
||||
|
@ -1273,18 +1276,18 @@ packages:
|
|||
'@form-create/component-wangeditor@3.1.20':
|
||||
resolution: {integrity: sha512-lAjpltmYfr3a2AeXasCehGsZNL/1WB6vWqqV9TIsJ4pleTr0/D/oPwEYQjfv+gG+NoB2Sa25SRGhtlnephjyhg==, tarball: https://registry.npmmirror.com/@form-create/component-wangeditor/-/component-wangeditor-3.1.20.tgz}
|
||||
|
||||
'@form-create/core@3.2.11':
|
||||
resolution: {integrity: sha512-xcaAxFSpAaVRWSpZ3ikrr89OmGidtN1y2YC7mQcQ/Hs7KvdbipH2I27JF5qm98+S7gs/e3Z9jrscngmSwsLw7g==, tarball: https://registry.npmmirror.com/@form-create/core/-/core-3.2.11.tgz}
|
||||
'@form-create/core@3.2.13':
|
||||
resolution: {integrity: sha512-HVLfZ5gf9DRO74OJTw3bt/GwFXhyBWvMmrOG9WkRTEQEMIeGOWudH843iaYp2ljgJN6jrn3RcCfONC9nzAmk8g==, tarball: https://registry.npmmirror.com/@form-create/core/-/core-3.2.13.tgz}
|
||||
peerDependencies:
|
||||
vue: ^3.1.0
|
||||
|
||||
'@form-create/designer@3.2.7':
|
||||
resolution: {integrity: sha512-jLpX51yXt2SOmsGOiDey5wq6K6gQLfd7CcGtW6zH2tDQTJd4ddS/QstVKmei6ddIwA9GWuk3JWnktGLk4ry2sg==, tarball: https://registry.npmmirror.com/@form-create/designer/-/designer-3.2.7.tgz}
|
||||
'@form-create/designer@3.2.8':
|
||||
resolution: {integrity: sha512-SgrGiWOFaQTARAmysepHDtFyRi97rERrlkv1joz+DCOAzZME3RKRTXVqA7ALzJ2jI3psiCosGAK4rPSLh6EvgA==, tarball: https://registry.npmmirror.com/@form-create/designer/-/designer-3.2.8.tgz}
|
||||
peerDependencies:
|
||||
vue: ^3.1.5
|
||||
|
||||
'@form-create/element-ui@3.2.11':
|
||||
resolution: {integrity: sha512-cJpKuu5zGNJK5TlsXTLqfc972aAVYk4q2ljn0ERfxM89oRl+2tkatOVr2vPYqGj/Z4Ufpr1R/ZP+RGGl2jVIHQ==, tarball: https://registry.npmmirror.com/@form-create/element-ui/-/element-ui-3.2.11.tgz}
|
||||
'@form-create/element-ui@3.2.13':
|
||||
resolution: {integrity: sha512-b/ilL9/huwQhXhGM3irzKTqlF7n69ld/CPiLQzuzSEUFC4VqBNFcy8kpCQ9/j4+VvuEPCvro1ax3v4V7Mxol9g==, tarball: https://registry.npmmirror.com/@form-create/element-ui/-/element-ui-3.2.13.tgz}
|
||||
peerDependencies:
|
||||
vue: ^3.1.0
|
||||
|
||||
|
@ -1437,7 +1440,7 @@ packages:
|
|||
resolution: {integrity: sha512-s2t+1oVtGDV6KtqfCXtUOhxfeYvOdDF90IVm+nMs/6bUP0HeGZLslguuL/AibpwtfL4FA/oCsIu/RhwapgAdJw==}
|
||||
|
||||
'@rollup/plugin-virtual@3.0.2':
|
||||
resolution: {integrity: sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==, tarball: https://registry.npmmirror.com/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz}
|
||||
resolution: {integrity: sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
|
||||
|
@ -1615,7 +1618,7 @@ packages:
|
|||
os: [win32]
|
||||
|
||||
'@swc/core@1.7.26':
|
||||
resolution: {integrity: sha512-f5uYFf+TmMQyYIoxkn/evWhNGuUzC730dFwAKGwBVHHVoPyak1/GvJUm6i1SKl+2Hrj9oN0i3WSoWWZ4pgI8lw==, tarball: https://registry.npmmirror.com/@swc/core/-/core-1.7.26.tgz}
|
||||
resolution: {integrity: sha512-f5uYFf+TmMQyYIoxkn/evWhNGuUzC730dFwAKGwBVHHVoPyak1/GvJUm6i1SKl+2Hrj9oN0i3WSoWWZ4pgI8lw==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@swc/helpers': '*'
|
||||
|
@ -1624,10 +1627,10 @@ packages:
|
|||
optional: true
|
||||
|
||||
'@swc/counter@0.1.3':
|
||||
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==, tarball: https://registry.npmmirror.com/@swc/counter/-/counter-0.1.3.tgz}
|
||||
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
||||
|
||||
'@swc/types@0.1.12':
|
||||
resolution: {integrity: sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA==, tarball: https://registry.npmmirror.com/@swc/types/-/types-0.1.12.tgz}
|
||||
resolution: {integrity: sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA==}
|
||||
|
||||
'@sxzz/popperjs-es@2.11.7':
|
||||
resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==, tarball: https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz}
|
||||
|
@ -1781,7 +1784,7 @@ packages:
|
|||
resolution: {integrity: sha512-1CQjuSrgbv1/dhmcfQ83eVyYbvGyqhTvb2Opxr0QCV+iJ4J6/J+XWQ3Om59WiwCd1MN3rDUHasx5XRrpUtewYQ==, tarball: https://registry.npmmirror.com/@types/video.js/-/video.js-7.3.58.tgz}
|
||||
|
||||
'@types/web-bluetooth@0.0.16':
|
||||
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==, tarball: https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz}
|
||||
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
|
||||
|
||||
'@types/web-bluetooth@0.0.20':
|
||||
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
|
||||
|
@ -2127,19 +2130,19 @@ packages:
|
|||
resolution: {integrity: sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==}
|
||||
|
||||
'@vueuse/core@9.13.0':
|
||||
resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==, tarball: https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz}
|
||||
resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
|
||||
|
||||
'@vueuse/metadata@10.9.0':
|
||||
resolution: {integrity: sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==}
|
||||
|
||||
'@vueuse/metadata@9.13.0':
|
||||
resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==, tarball: https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz}
|
||||
resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
|
||||
|
||||
'@vueuse/shared@10.9.0':
|
||||
resolution: {integrity: sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==}
|
||||
|
||||
'@vueuse/shared@9.13.0':
|
||||
resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==, tarball: https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz}
|
||||
resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
|
||||
|
||||
'@wangeditor/basic-modules@1.1.7':
|
||||
resolution: {integrity: sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==}
|
||||
|
@ -2347,7 +2350,7 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
|
||||
async-validator@4.2.5:
|
||||
resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==, tarball: https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz}
|
||||
resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
|
||||
|
||||
async@3.2.5:
|
||||
resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==}
|
||||
|
@ -2432,11 +2435,11 @@ packages:
|
|||
bpmn-js-token-simulation@0.10.0:
|
||||
resolution: {integrity: sha512-QuZQ/KVXKt9Vl+XENyOBoTW2Aw+uKjuBlKdCJL6El7AyM7DkJ5bZkSYURshId1SkBDdYg2mJ1flSmsrhGuSfwg==}
|
||||
|
||||
bpmn-js@8.9.0:
|
||||
resolution: {integrity: sha512-cthSxiJUpEHspiUKiL0YA8/mRCYngNKwALWieLKPtFo42n+vWTFgmxnASNRwhxpPEbSXjYuTah1lZ0lSyLWPpw==}
|
||||
bpmn-js@8.10.0:
|
||||
resolution: {integrity: sha512-NozeOi01qL0ZdVq8+5hWZcikyEvgrP1yzCBqlhSufJdHFsnEMBCwn2bJJ0B/6JgX+IBwy1sk/Uw+Ds8rQ8vfrw==, tarball: https://registry.npmmirror.com/bpmn-js/-/bpmn-js-8.10.0.tgz}
|
||||
|
||||
bpmn-moddle@7.1.3:
|
||||
resolution: {integrity: sha512-ZcBfw0NSOdYTSXFKEn7MOXHItz7VfLZTrFYKO8cK6V8ZzGjCcdiLIOiw7Lctw1PJsihhLiZQS8Htj2xKf+NwCg==}
|
||||
resolution: {integrity: sha512-ZcBfw0NSOdYTSXFKEn7MOXHItz7VfLZTrFYKO8cK6V8ZzGjCcdiLIOiw7Lctw1PJsihhLiZQS8Htj2xKf+NwCg==, tarball: https://registry.npmmirror.com/bpmn-moddle/-/bpmn-moddle-7.1.3.tgz}
|
||||
|
||||
brace-expansion@1.1.11:
|
||||
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
||||
|
@ -2733,7 +2736,7 @@ packages:
|
|||
engines: {node: '>= 6'}
|
||||
|
||||
css.escape@1.5.1:
|
||||
resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
|
||||
resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==, tarball: https://registry.npmmirror.com/css.escape/-/css.escape-1.5.1.tgz}
|
||||
|
||||
cssesc@3.0.0:
|
||||
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
||||
|
@ -2968,7 +2971,7 @@ packages:
|
|||
resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==}
|
||||
|
||||
diagram-js-direct-editing@1.8.0:
|
||||
resolution: {integrity: sha512-B4Xj+PJfgBjbPEzT3uZQEkZI5xHFB0Izc+7BhDFuHidzrEMzQKZrFGdA3PqfWhReHf3dp+iB6Tt11G9eGNjKMw==}
|
||||
resolution: {integrity: sha512-B4Xj+PJfgBjbPEzT3uZQEkZI5xHFB0Izc+7BhDFuHidzrEMzQKZrFGdA3PqfWhReHf3dp+iB6Tt11G9eGNjKMw==, tarball: https://registry.npmmirror.com/diagram-js-direct-editing/-/diagram-js-direct-editing-1.8.0.tgz}
|
||||
peerDependencies:
|
||||
diagram-js: '*'
|
||||
|
||||
|
@ -2976,10 +2979,10 @@ packages:
|
|||
resolution: {integrity: sha512-LF9BiwjbOPpZd0ez5VSlYRbdbEA59YQX43bWvNDp1rLMv0xwZ5yIg4oaYDK82nIQ0kH1tjvoQRpNevMTCgQVyw==}
|
||||
|
||||
diagram-js@7.9.0:
|
||||
resolution: {integrity: sha512-o1yUtX5TXV1pmpevP55gxU/AEG6nCidOXGs/HLuxNXG0zMZ3jQta7kMqRxTK93rNw/XuHmP1eMOwdvdJ2RP5qA==}
|
||||
resolution: {integrity: sha512-o1yUtX5TXV1pmpevP55gxU/AEG6nCidOXGs/HLuxNXG0zMZ3jQta7kMqRxTK93rNw/XuHmP1eMOwdvdJ2RP5qA==, tarball: https://registry.npmmirror.com/diagram-js/-/diagram-js-7.9.0.tgz}
|
||||
|
||||
didi@5.2.1:
|
||||
resolution: {integrity: sha512-IKNnajUlD4lWMy/Q9Emkk7H1qnzREgY4UyE3IhmOi/9IKua0JYtYldk928bOdt1yNxN8EiOy1sqtSozEYsmjCg==}
|
||||
resolution: {integrity: sha512-IKNnajUlD4lWMy/Q9Emkk7H1qnzREgY4UyE3IhmOi/9IKua0JYtYldk928bOdt1yNxN8EiOy1sqtSozEYsmjCg==, tarball: https://registry.npmmirror.com/didi/-/didi-5.2.1.tgz}
|
||||
|
||||
didi@9.0.2:
|
||||
resolution: {integrity: sha512-q2+aj+lnJcUweV7A9pdUrwFr4LHVmRPwTmQLtHPFz4aT7IBoryN6Iy+jmFku+oIzr5ebBkvtBCOb87+dJhb7bg==}
|
||||
|
@ -3075,7 +3078,7 @@ packages:
|
|||
resolution: {integrity: sha512-9ItEpeu15hW5m8jKdriL+BQrgwDTXEL9pn4SkillWFu73ZNNNQ2BKKLS+ZHv2vC9UkNhosAeyfxOf/5OSeTCPA==}
|
||||
|
||||
element-plus@2.8.4:
|
||||
resolution: {integrity: sha512-ZlVAdUOoJliv4kW3ntWnnSHMT+u/Os7mXJjk2xzOlqNeHaI2/ozlF+R58ZCEak8ZnDi6+5A2viWEYRsq64IuiA==, tarball: https://registry.npmmirror.com/element-plus/-/element-plus-2.8.4.tgz}
|
||||
resolution: {integrity: sha512-ZlVAdUOoJliv4kW3ntWnnSHMT+u/Os7mXJjk2xzOlqNeHaI2/ozlF+R58ZCEak8ZnDi6+5A2viWEYRsq64IuiA==}
|
||||
peerDependencies:
|
||||
vue: ^3.2.0
|
||||
|
||||
|
@ -3160,7 +3163,7 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
|
||||
escape-html@1.0.3:
|
||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==, tarball: https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz}
|
||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||
|
||||
escape-string-regexp@1.0.5:
|
||||
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
|
||||
|
@ -4019,7 +4022,7 @@ packages:
|
|||
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
|
||||
|
||||
lodash-unified@1.0.3:
|
||||
resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==, tarball: https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz}
|
||||
resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==}
|
||||
peerDependencies:
|
||||
'@types/lodash-es': '*'
|
||||
lodash: '*'
|
||||
|
@ -4158,7 +4161,7 @@ packages:
|
|||
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
|
||||
|
||||
memoize-one@6.0.0:
|
||||
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==, tarball: https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz}
|
||||
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
|
||||
|
||||
meow@12.1.1:
|
||||
resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==}
|
||||
|
@ -4207,7 +4210,7 @@ packages:
|
|||
engines: {node: '>=12'}
|
||||
|
||||
min-dash@3.8.1:
|
||||
resolution: {integrity: sha512-evumdlmIlg9mbRVPbC4F5FuRhNmcMS5pvuBUbqb1G9v09Ro0ImPEgz5n3khir83lFok1inKqVDjnKEg3GpDxQg==}
|
||||
resolution: {integrity: sha512-evumdlmIlg9mbRVPbC4F5FuRhNmcMS5pvuBUbqb1G9v09Ro0ImPEgz5n3khir83lFok1inKqVDjnKEg3GpDxQg==, tarball: https://registry.npmmirror.com/min-dash/-/min-dash-3.8.1.tgz}
|
||||
|
||||
min-dash@4.2.1:
|
||||
resolution: {integrity: sha512-to+unsToePnm7cUeR9TrMzFlETHd/UXmU+ELTRfWZj5XGT41KF6X3L233o3E/GdEs3sk2Tbw/lOLD1avmWkg8A==}
|
||||
|
@ -4260,10 +4263,10 @@ packages:
|
|||
resolution: {integrity: sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==}
|
||||
|
||||
moddle-xml@9.0.6:
|
||||
resolution: {integrity: sha512-tl0reHpsY/aKlLGhXeFlQWlYAQHFxTkFqC8tq8jXRYpQSnLVw13T6swMaourLd7EXqHdWsc+5ggsB+fEep6xZQ==}
|
||||
resolution: {integrity: sha512-tl0reHpsY/aKlLGhXeFlQWlYAQHFxTkFqC8tq8jXRYpQSnLVw13T6swMaourLd7EXqHdWsc+5ggsB+fEep6xZQ==, tarball: https://registry.npmmirror.com/moddle-xml/-/moddle-xml-9.0.6.tgz}
|
||||
|
||||
moddle@5.0.4:
|
||||
resolution: {integrity: sha512-Kjb+hjuzO+YlojNGxEUXvdhLYTHTtAABDlDcJTtTcn5MbJF9Zkv4I1Fyvp3Ypmfgg1EfHDZ3PsCQTuML9JD6wg==}
|
||||
resolution: {integrity: sha512-Kjb+hjuzO+YlojNGxEUXvdhLYTHTtAABDlDcJTtTcn5MbJF9Zkv4I1Fyvp3Ypmfgg1EfHDZ3PsCQTuML9JD6wg==, tarball: https://registry.npmmirror.com/moddle/-/moddle-5.0.4.tgz}
|
||||
|
||||
mpd-parser@0.22.1:
|
||||
resolution: {integrity: sha512-fwBebvpyPUU8bOzvhX0VQZgSohncbgYwUyJJoTSNpmy7ccD2ryiCvM7oRkn/xQH5cv73/xU7rJSNCLjdGFor0Q==}
|
||||
|
@ -4329,7 +4332,7 @@ packages:
|
|||
engines: {node: '>=0.10.0'}
|
||||
|
||||
normalize-wheel-es@1.2.0:
|
||||
resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==, tarball: https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz}
|
||||
resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==}
|
||||
|
||||
npm-run-path@4.0.1:
|
||||
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
|
||||
|
@ -4364,7 +4367,7 @@ packages:
|
|||
engines: {node: '>= 0.4'}
|
||||
|
||||
object-refs@0.3.0:
|
||||
resolution: {integrity: sha512-eP0ywuoWOaDoiake/6kTJlPJhs+k0qNm4nYRzXLNHj6vh+5M3i9R1epJTdxIPGlhWc4fNRQ7a6XJNCX+/L4FOQ==}
|
||||
resolution: {integrity: sha512-eP0ywuoWOaDoiake/6kTJlPJhs+k0qNm4nYRzXLNHj6vh+5M3i9R1epJTdxIPGlhWc4fNRQ7a6XJNCX+/L4FOQ==, tarball: https://registry.npmmirror.com/object-refs/-/object-refs-0.3.0.tgz}
|
||||
|
||||
object-visit@1.0.1:
|
||||
resolution: {integrity: sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==}
|
||||
|
@ -4491,8 +4494,8 @@ packages:
|
|||
picocolors@1.0.0:
|
||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
||||
|
||||
picocolors@1.1.0:
|
||||
resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==, tarball: https://registry.npmmirror.com/picocolors/-/picocolors-1.1.0.tgz}
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, tarball: https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz}
|
||||
|
||||
picomatch@2.3.1:
|
||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||
|
@ -4852,7 +4855,7 @@ packages:
|
|||
resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
|
||||
|
||||
saxen@8.1.2:
|
||||
resolution: {integrity: sha512-xUOiiFbc3Ow7p8KMxwsGICPx46ZQvy3+qfNVhrkwfz3Vvq45eGt98Ft5IQaA1R/7Tb5B5MKh9fUR9x3c3nDTxw==}
|
||||
resolution: {integrity: sha512-xUOiiFbc3Ow7p8KMxwsGICPx46ZQvy3+qfNVhrkwfz3Vvq45eGt98Ft5IQaA1R/7Tb5B5MKh9fUR9x3c3nDTxw==, tarball: https://registry.npmmirror.com/saxen/-/saxen-8.1.2.tgz}
|
||||
|
||||
scroll-into-view-if-needed@2.2.31:
|
||||
resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==}
|
||||
|
@ -4956,6 +4959,9 @@ packages:
|
|||
sortablejs@1.14.0:
|
||||
resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==}
|
||||
|
||||
sortablejs@1.15.3:
|
||||
resolution: {integrity: sha512-zdK3/kwwAK1cJgy1rwl1YtNTbRmc8qW/+vgXf75A7NHag5of4pyI6uK86ktmQETyWRH7IGaE73uZOOBcGxgqZg==}
|
||||
|
||||
source-map-js@1.2.0:
|
||||
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -4980,7 +4986,7 @@ packages:
|
|||
engines: {node: '>=0.10.0'}
|
||||
|
||||
source-map@0.6.1:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, tarball: https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz}
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
split-string@3.1.0:
|
||||
|
@ -5167,7 +5173,7 @@ packages:
|
|||
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
||||
|
||||
tiny-svg@2.2.4:
|
||||
resolution: {integrity: sha512-NOi39lBknf4UdDEahNkbEAJnzhu1ZcN2j75IS2vLRmIhsfxdZpTChfLKBcN1ShplVmPIXJAIafk6YY5/Aa80lQ==}
|
||||
resolution: {integrity: sha512-NOi39lBknf4UdDEahNkbEAJnzhu1ZcN2j75IS2vLRmIhsfxdZpTChfLKBcN1ShplVmPIXJAIafk6YY5/Aa80lQ==, tarball: https://registry.npmmirror.com/tiny-svg/-/tiny-svg-2.2.4.tgz}
|
||||
|
||||
tiny-svg@3.0.1:
|
||||
resolution: {integrity: sha512-P8T4iwiW1t95vpHVHqrD36Brn7TqFYCPSHIWk9WLJtYK1X4aDd+5cgqcAADIWSjf1/i5idKnpCh9mim8hEdRBg==}
|
||||
|
@ -5376,7 +5382,7 @@ packages:
|
|||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
||||
uuid@10.0.0:
|
||||
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==, tarball: https://registry.npmmirror.com/uuid/-/uuid-10.0.0.tgz}
|
||||
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
|
||||
hasBin: true
|
||||
|
||||
vary@1.1.2:
|
||||
|
@ -5426,7 +5432,7 @@ packages:
|
|||
vite: '>=2.0.0'
|
||||
|
||||
vite-plugin-top-level-await@1.4.4:
|
||||
resolution: {integrity: sha512-QyxQbvcMkgt+kDb12m2P8Ed35Sp6nXP+l8ptGrnHV9zgYDUpraO0CPdlqLSeBqvY2DToR52nutDG7mIHuysdiw==, tarball: https://registry.npmmirror.com/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.4.4.tgz}
|
||||
resolution: {integrity: sha512-QyxQbvcMkgt+kDb12m2P8Ed35Sp6nXP+l8ptGrnHV9zgYDUpraO0CPdlqLSeBqvY2DToR52nutDG7mIHuysdiw==}
|
||||
peerDependencies:
|
||||
vite: '>=2.8'
|
||||
|
||||
|
@ -5812,11 +5818,11 @@ snapshots:
|
|||
|
||||
'@babel/helper-string-parser@7.24.1': {}
|
||||
|
||||
'@babel/helper-string-parser@7.25.7': {}
|
||||
'@babel/helper-string-parser@7.25.9': {}
|
||||
|
||||
'@babel/helper-validator-identifier@7.22.20': {}
|
||||
|
||||
'@babel/helper-validator-identifier@7.25.7': {}
|
||||
'@babel/helper-validator-identifier@7.25.9': {}
|
||||
|
||||
'@babel/helper-validator-option@7.23.5': {}
|
||||
|
||||
|
@ -5845,9 +5851,9 @@ snapshots:
|
|||
dependencies:
|
||||
'@babel/types': 7.24.0
|
||||
|
||||
'@babel/parser@7.25.8':
|
||||
'@babel/parser@7.26.2':
|
||||
dependencies:
|
||||
'@babel/types': 7.25.8
|
||||
'@babel/types': 7.26.0
|
||||
|
||||
'@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.4(@babel/core@7.24.4)':
|
||||
dependencies:
|
||||
|
@ -6418,11 +6424,10 @@ snapshots:
|
|||
'@babel/helper-validator-identifier': 7.22.20
|
||||
to-fast-properties: 2.0.0
|
||||
|
||||
'@babel/types@7.25.8':
|
||||
'@babel/types@7.26.0':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.25.7
|
||||
'@babel/helper-validator-identifier': 7.25.7
|
||||
to-fast-properties: 2.0.0
|
||||
'@babel/helper-string-parser': 7.25.9
|
||||
'@babel/helper-validator-identifier': 7.25.9
|
||||
|
||||
'@bpmn-io/diagram-js-ui@0.2.3':
|
||||
dependencies:
|
||||
|
@ -6702,7 +6707,7 @@ snapshots:
|
|||
dependencies:
|
||||
'@form-create/utils': 3.2.0
|
||||
|
||||
'@form-create/component-elm-upload@3.2.9':
|
||||
'@form-create/component-elm-upload@3.2.13':
|
||||
dependencies:
|
||||
'@form-create/utils': 3.2.0
|
||||
|
||||
|
@ -6712,15 +6717,15 @@ snapshots:
|
|||
dependencies:
|
||||
wangeditor: 4.7.15
|
||||
|
||||
'@form-create/core@3.2.11(vue@3.5.12(typescript@5.3.3))':
|
||||
'@form-create/core@3.2.13(vue@3.5.12(typescript@5.3.3))':
|
||||
dependencies:
|
||||
'@form-create/utils': 3.2.0
|
||||
vue: 3.5.12(typescript@5.3.3)
|
||||
|
||||
'@form-create/designer@3.2.7(vue@3.5.12(typescript@5.3.3))':
|
||||
'@form-create/designer@3.2.8(vue@3.5.12(typescript@5.3.3))':
|
||||
dependencies:
|
||||
'@form-create/component-wangeditor': 3.1.20
|
||||
'@form-create/element-ui': 3.2.11(vue@3.5.12(typescript@5.3.3))
|
||||
'@form-create/element-ui': 3.2.13(vue@3.5.12(typescript@5.3.3))
|
||||
'@form-create/utils': 3.2.0
|
||||
codemirror: 6.65.7
|
||||
element-plus: 2.8.4(vue@3.5.12(typescript@5.3.3))
|
||||
|
@ -6729,7 +6734,7 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
|
||||
'@form-create/element-ui@3.2.11(vue@3.5.12(typescript@5.3.3))':
|
||||
'@form-create/element-ui@3.2.13(vue@3.5.12(typescript@5.3.3))':
|
||||
dependencies:
|
||||
'@form-create/component-elm-checkbox': 3.2.8
|
||||
'@form-create/component-elm-frame': 3.2.0
|
||||
|
@ -6737,9 +6742,9 @@ snapshots:
|
|||
'@form-create/component-elm-radio': 3.2.8
|
||||
'@form-create/component-elm-select': 3.2.0
|
||||
'@form-create/component-elm-tree': 3.2.9
|
||||
'@form-create/component-elm-upload': 3.2.9
|
||||
'@form-create/component-elm-upload': 3.2.13
|
||||
'@form-create/component-subform': 3.1.34
|
||||
'@form-create/core': 3.2.11(vue@3.5.12(typescript@5.3.3))
|
||||
'@form-create/core': 3.2.13(vue@3.5.12(typescript@5.3.3))
|
||||
'@form-create/utils': 3.2.0
|
||||
vue: 3.5.12(typescript@5.3.3)
|
||||
|
||||
|
@ -7674,7 +7679,7 @@ snapshots:
|
|||
|
||||
'@vue/compiler-core@3.5.12':
|
||||
dependencies:
|
||||
'@babel/parser': 7.25.8
|
||||
'@babel/parser': 7.26.2
|
||||
'@vue/shared': 3.5.12
|
||||
entities: 4.5.0
|
||||
estree-walker: 2.0.2
|
||||
|
@ -7704,7 +7709,7 @@ snapshots:
|
|||
|
||||
'@vue/compiler-sfc@3.5.12':
|
||||
dependencies:
|
||||
'@babel/parser': 7.25.8
|
||||
'@babel/parser': 7.26.2
|
||||
'@vue/compiler-core': 3.5.12
|
||||
'@vue/compiler-dom': 3.5.12
|
||||
'@vue/compiler-ssr': 3.5.12
|
||||
|
@ -8109,11 +8114,11 @@ snapshots:
|
|||
|
||||
boolbase@1.0.0: {}
|
||||
|
||||
bpmn-js-properties-panel@0.46.0(bpmn-js@8.9.0):
|
||||
bpmn-js-properties-panel@0.46.0(bpmn-js@8.10.0):
|
||||
dependencies:
|
||||
'@bpmn-io/element-templates-validator': 0.2.0
|
||||
'@bpmn-io/extract-process-variables': 0.4.5
|
||||
bpmn-js: 8.9.0
|
||||
bpmn-js: 8.10.0
|
||||
ids: 1.0.5
|
||||
inherits: 2.0.4
|
||||
lodash: 4.17.21
|
||||
|
@ -8128,7 +8133,7 @@ snapshots:
|
|||
min-dom: 0.2.0
|
||||
svg.js: 2.7.1
|
||||
|
||||
bpmn-js@8.9.0:
|
||||
bpmn-js@8.10.0:
|
||||
dependencies:
|
||||
bpmn-moddle: 7.1.3
|
||||
css.escape: 1.5.1
|
||||
|
@ -10377,7 +10382,7 @@ snapshots:
|
|||
|
||||
picocolors@1.0.0: {}
|
||||
|
||||
picocolors@1.1.0: {}
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@2.3.1: {}
|
||||
|
||||
|
@ -10463,7 +10468,7 @@ snapshots:
|
|||
postcss@8.4.47:
|
||||
dependencies:
|
||||
nanoid: 3.3.7
|
||||
picocolors: 1.1.0
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
posthtml-parser@0.2.1:
|
||||
|
@ -10862,6 +10867,8 @@ snapshots:
|
|||
|
||||
sortablejs@1.14.0: {}
|
||||
|
||||
sortablejs@1.15.3: {}
|
||||
|
||||
source-map-js@1.2.0: {}
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
@ -11328,7 +11335,7 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- rollup
|
||||
|
||||
unplugin-vue-components@0.25.2(@babel/parser@7.25.8)(rollup@4.17.1)(vue@3.5.12(typescript@5.3.3)):
|
||||
unplugin-vue-components@0.25.2(@babel/parser@7.26.2)(rollup@4.17.1)(vue@3.5.12(typescript@5.3.3)):
|
||||
dependencies:
|
||||
'@antfu/utils': 0.7.7
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.17.1)
|
||||
|
@ -11342,7 +11349,7 @@ snapshots:
|
|||
unplugin: 1.10.1
|
||||
vue: 3.5.12(typescript@5.3.3)
|
||||
optionalDependencies:
|
||||
'@babel/parser': 7.25.8
|
||||
'@babel/parser': 7.26.2
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
- supports-color
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
export const getActivityList = async (params) => {
|
||||
return await request.get({
|
||||
url: '/bpm/activity/list',
|
||||
params
|
||||
})
|
||||
}
|
|
@ -36,6 +36,16 @@ export const CategoryApi = {
|
|||
return await request.put({ url: `/bpm/category/update`, data })
|
||||
},
|
||||
|
||||
// 批量修改流程分类的排序
|
||||
updateCategorySortBatch: async (ids: number[]) => {
|
||||
return await request.put({
|
||||
url: `/bpm/category/update-sort-batch`,
|
||||
params: {
|
||||
ids: ids.join(',')
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 删除流程分类
|
||||
deleteCategory: async (id: number) => {
|
||||
return await request.delete({ url: `/bpm/category/delete?id=` + id })
|
||||
|
|
|
@ -26,8 +26,8 @@ export type ModelVO = {
|
|||
bpmnXml: string
|
||||
}
|
||||
|
||||
export const getModelPage = async (params) => {
|
||||
return await request.get({ url: '/bpm/model/page', params })
|
||||
export const getModelList = async (name: string | undefined) => {
|
||||
return await request.get({ url: '/bpm/model/list', params: { name } })
|
||||
}
|
||||
|
||||
export const getModel = async (id: string) => {
|
||||
|
@ -38,6 +38,16 @@ export const updateModel = async (data: ModelVO) => {
|
|||
return await request.put({ url: '/bpm/model/update', data: data })
|
||||
}
|
||||
|
||||
// 批量修改流程分类的排序
|
||||
export const updateModelSortBatch = async (ids: number[]) => {
|
||||
return await request.put({
|
||||
url: `/bpm/model/update-sort-batch`,
|
||||
params: {
|
||||
ids: ids.join(',')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const updateModelBpmn = async (data: ModelVO) => {
|
||||
return await request.put({ url: '/bpm/model/update-bpmn', data: data })
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import request from '@/config/axios'
|
||||
import { ProcessDefinitionVO } from '@/api/bpm/model'
|
||||
import { NodeType } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import { NodeType, CandidateStrategy } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
export type Task = {
|
||||
id: string
|
||||
name: string
|
||||
|
@ -24,30 +24,30 @@ export type ProcessInstanceVO = {
|
|||
|
||||
// 用户信息
|
||||
export type User = {
|
||||
id: number,
|
||||
nickname: string,
|
||||
id: number
|
||||
nickname: string
|
||||
avatar: string
|
||||
}
|
||||
|
||||
// 审批任务信息
|
||||
export type ApprovalTaskInfo = {
|
||||
id: number,
|
||||
ownerUser: User,
|
||||
assigneeUser: User,
|
||||
status: number,
|
||||
id: number
|
||||
ownerUser: User
|
||||
assigneeUser: User
|
||||
status: number
|
||||
reason: string
|
||||
|
||||
}
|
||||
|
||||
// 审批节点信息
|
||||
export type ApprovalNodeInfo = {
|
||||
id : number
|
||||
id: number
|
||||
name: string
|
||||
nodeType: NodeType
|
||||
candidateStrategy?: CandidateStrategy
|
||||
status: number
|
||||
startTime?: Date
|
||||
endTime?: Date
|
||||
candidateUserList?: User[]
|
||||
candidateUsers?: User[]
|
||||
tasks: ApprovalTaskInfo[]
|
||||
}
|
||||
|
||||
|
@ -88,12 +88,16 @@ export const getProcessInstanceCopyPage = async (params: any) => {
|
|||
}
|
||||
|
||||
// 获取审批详情
|
||||
export const getApprovalDetail = async (processInstanceId?:string, processDefinitionId?:string) => {
|
||||
const param = processInstanceId ? '?processInstanceId='+ processInstanceId : '?processDefinitionId='+ processDefinitionId
|
||||
return await request.get({ url: 'bpm/process-instance/get-approval-detail'+ param })
|
||||
export const getApprovalDetail = async (params: any) => {
|
||||
return await request.get({ url: 'bpm/process-instance/get-approval-detail' , params })
|
||||
}
|
||||
|
||||
// 获取表单字段权限
|
||||
export const getFormFieldsPermission = async (params: any) => {
|
||||
return await request.get({ url: '/bpm/process-instance/get-form-fields-permission', params })
|
||||
}
|
||||
|
||||
// 获取流程实例的 BPMN 模型视图
|
||||
export const getProcessInstanceBpmnModelView = async (id: string) => {
|
||||
return await request.get({ url: '/bpm/process-instance/get-bpmn-model-view?id=' + id })
|
||||
}
|
||||
|
|
|
@ -9,10 +9,10 @@ export enum TaskStatusEnum {
|
|||
*/
|
||||
NOT_START = -1,
|
||||
|
||||
/**
|
||||
/**
|
||||
* 待审批
|
||||
*/
|
||||
WAIT = 0,
|
||||
WAIT = 0,
|
||||
/**
|
||||
* 审批中
|
||||
*/
|
||||
|
@ -35,19 +35,10 @@ export enum TaskStatusEnum {
|
|||
* 已退回
|
||||
*/
|
||||
RETURN = 5,
|
||||
/**
|
||||
* 委派中
|
||||
*/
|
||||
DELEGATE = 6,
|
||||
/**
|
||||
* 审批通过中
|
||||
*/
|
||||
APPROVING = 7,
|
||||
|
||||
}
|
||||
|
||||
export type TaskVO = {
|
||||
id: number
|
||||
APPROVING = 7
|
||||
}
|
||||
|
||||
export const getTaskTodoPage = async (params: any) => {
|
||||
|
@ -76,12 +67,12 @@ export const getTaskListByProcessInstanceId = async (processInstanceId: string)
|
|||
})
|
||||
}
|
||||
|
||||
// 获取所有可回退的节点
|
||||
// 获取所有可退回的节点
|
||||
export const getTaskListByReturn = async (id: string) => {
|
||||
return await request.get({ url: '/bpm/task/list-by-return', params: { id } })
|
||||
}
|
||||
|
||||
// 回退
|
||||
// 退回
|
||||
export const returnTask = async (data: any) => {
|
||||
return await request.put({ url: '/bpm/task/return', data })
|
||||
}
|
||||
|
@ -106,6 +97,16 @@ export const signDeleteTask = async (data: any) => {
|
|||
return await request.delete({ url: '/bpm/task/delete-sign', data })
|
||||
}
|
||||
|
||||
// 抄送
|
||||
export const copyTask = async (data: any) => {
|
||||
return await request.put({ url: '/bpm/task/copy', data })
|
||||
}
|
||||
|
||||
// 获取我的待办任务
|
||||
export const myTodoTask = async (processInstanceId: string) => {
|
||||
return await request.get({ url: '/bpm/task/my-todo?processInstanceId=' + processInstanceId })
|
||||
}
|
||||
|
||||
// 获取减签任务列表
|
||||
export const getChildrenTaskList = async (id: string) => {
|
||||
return await request.get({ url: '/bpm/task/list-by-parent-task-id?parentTaskId=' + id })
|
||||
|
|
|
@ -13,10 +13,11 @@ export interface DeliveryPickUpStoreVO {
|
|||
latitude: number
|
||||
longitude: number
|
||||
status: number
|
||||
verifyUserIds: number[] // 绑定用户编号组数
|
||||
}
|
||||
|
||||
// 查询自提门店列表
|
||||
export const getDeliveryPickUpStorePage = async (params) => {
|
||||
export const getDeliveryPickUpStorePage = async (params: any) => {
|
||||
return await request.get({ url: '/trade/delivery/pick-up-store/page', params })
|
||||
}
|
||||
|
||||
|
@ -26,8 +27,8 @@ export const getDeliveryPickUpStore = async (id: number) => {
|
|||
}
|
||||
|
||||
// 查询自提门店精简列表
|
||||
export const getListAllSimple = async (): Promise<DeliveryPickUpStoreVO[]> => {
|
||||
return await request.get({ url: '/trade/delivery/pick-up-store/list-all-simple' })
|
||||
export const getSimpleDeliveryPickUpStoreList = async (): Promise<DeliveryPickUpStoreVO[]> => {
|
||||
return await request.get({ url: '/trade/delivery/pick-up-store/simple-list' })
|
||||
}
|
||||
|
||||
// 新增自提门店
|
||||
|
@ -44,3 +45,8 @@ export const updateDeliveryPickUpStore = async (data: DeliveryPickUpStoreVO) =>
|
|||
export const deleteDeliveryPickUpStore = async (id: number) => {
|
||||
return await request.delete({ url: '/trade/delivery/pick-up-store/delete?id=' + id })
|
||||
}
|
||||
|
||||
// 绑定自提店员
|
||||
export const bindStoreStaffId = async (data: any) => {
|
||||
return await request.post({ url: '/trade/delivery/pick-up-store/bind', data })
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ export interface AppVO {
|
|||
remark: string
|
||||
payNotifyUrl: string
|
||||
refundNotifyUrl: string
|
||||
transferNotifyUrl: string
|
||||
merchantId: number
|
||||
merchantName: string
|
||||
createTime: Date
|
||||
|
@ -19,6 +20,7 @@ export interface AppPageReqVO extends PageParam {
|
|||
remark?: string
|
||||
payNotifyUrl?: string
|
||||
refundNotifyUrl?: string
|
||||
transferNotifyUrl?: string
|
||||
merchantName?: string
|
||||
createTime?: Date[]
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg t="1731390087280" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4297" width="200" height="200"><path d="M639.9 541.7c76.4-44.2 127.9-126.8 127.9-221.5C767.7 179 653.2 64.5 512 64.5S256.3 179 256.3 320.2c0 89.6 46.1 168.4 115.8 214.1C193.5 593 64.5 761.2 64.5 959.5h63.9c0-211.5 172.1-383.6 383.6-383.6 44.9 0 87.8 8.1 127.9 22.4v-56.6zM320.2 320.2c0-105.8 86-191.8 191.8-191.8s191.8 86 191.8 191.8S617.7 512 512 512s-191.8-86-191.8-191.8zM831.6 767.7V639.9h-63.9v127.8H639.9v63.9h127.8v127.9h63.9V831.6h127.9v-63.9z" fill="#5f6266" p-id="4298"></path></svg>
|
After Width: | Height: | Size: 608 B |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
@ -0,0 +1 @@
|
|||
<svg t="1729561718271" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8640" width="200" height="200"><path d="M908.5952 920.4224H164.7616a31.0784 31.0784 0 0 1-30.976-30.976c0-17.0496 13.9264-30.976 30.976-30.976h743.8336c17.0496 0 31.0272 13.9264 31.0272 30.976a31.0784 31.0784 0 0 1-31.0272 30.976z m0-123.9552H164.7616a31.0784 31.0784 0 0 1-30.976-30.976v-154.9824c0-51.1488 41.8304-92.9792 92.9792-92.9792h198.3488c-6.1952-37.1712-24.7808-72.8064-51.1488-103.8336a216.576 216.576 0 0 1-54.2208-144.128c0-58.88 23.2448-114.688 66.6112-156.4672C429.7728 71.168 485.5296 51.0976 545.9968 52.6848c111.5648 4.608 206.08 100.6592 207.616 212.2752 1.536 55.808-20.1216 110.0288-57.344 151.8592-26.3168 27.904-41.8304 61.952-48.0256 100.7104h198.3488c51.2 0 93.0304 41.8304 93.0304 92.9792v154.9824a31.0784 31.0784 0 0 1-31.0272 30.976z m-712.8064-61.952H877.568v-124.0064a31.0784 31.0784 0 0 0-31.0272-30.976h-232.448a31.0784 31.0784 0 0 1-30.976-31.0272c0-65.024 23.2448-127.0784 66.6624-173.568 27.8528-29.3888 41.8304-68.1472 41.8304-108.4416-1.536-80.5888-68.1984-148.7872-148.7872-151.8592a150.528 150.528 0 0 0-113.152 43.3664 153.6 153.6 0 0 0-48.0256 111.616c0 37.1712 13.9776 74.3424 38.7584 102.2464 44.9536 51.1488 69.7344 113.152 69.7344 176.64a31.0784 31.0784 0 0 1-30.976 31.0272h-232.448a31.0784 31.0784 0 0 0-30.976 30.976v123.9552z" fill="#fff" p-id="8641"></path></svg>
|
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 7.4 KiB |
|
@ -0,0 +1 @@
|
|||
<svg t="1729585232424" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1602" width="200" height="200"><path d="M925.5 898.9H804.9c-19 0-34.5-15.4-34.5-34.4V761.3c0-19 15.4-34.4 34.5-34.4h34.5V572.2c0-19-15.4-34.4-34.5-34.4H529.2V727h34.5c19 0 34.5 15.4 34.5 34.4v103.2c0 19-15.4 34.4-34.5 34.4H443.1c-19 0-34.5-15.4-34.5-34.4V761.3c0-19 15.4-34.4 34.5-34.4h34.5V537.8H219.1c-19 0-34.5 15.4-34.5 34.4V727h34.5c19 0 34.5 15.4 34.5 34.4v103.2c0 19-15.4 34.4-34.5 34.4H98.5c-19 0-34.5-15.4-34.5-34.4V761.3c0-19 15.4-34.4 34.5-34.4H133V555c0-38 30.9-68.8 68.9-68.8h275.7V297.1h-34.5c-19 0-34.5-15.4-34.5-34.4V159.5c0-19 15.4-34.4 34.5-34.4h120.6c19 0 34.5 15.4 34.5 34.4v103.2c0 19-15.4 34.4-34.5 34.4h-34.5v189.2h292.9c38.1 0 68.9 30.8 68.9 68.8v172h34.5c19 0 34.5 15.4 34.5 34.4v103.2c0 18.8-15.4 34.2-34.5 34.2z m0 0" p-id="1603" fill="#fff"></path></svg>
|
After Width: | Height: | Size: 897 B |
|
@ -0,0 +1 @@
|
|||
<svg t="1729649333541" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1644" width="200" height="200"><path d="M647.888 893.84L491.904 571.52l393.888-393.888-237.904 716.208zM872.32 123.232L459.872 535.68 134.96 380.88 872.32 123.232z m90.72-68.32a23.968 23.968 0 0 0-24.784-5.568L64.08 354.816a23.984 23.984 0 0 0-2.4 44.32l381.392 181.728 187.36 387.088a24.048 24.048 0 0 0 23.152 13.504 24.032 24.032 0 0 0 21.232-16.4L968.96 79.552c2.88-8.672 0.592-18.24-5.92-24.64z" fill="#fff" p-id="1645"></path></svg>
|
After Width: | Height: | Size: 553 B |
|
@ -0,0 +1 @@
|
|||
<svg t="1730189225011" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2651" id="mx_n_1730189225011" width="200" height="200"><path d="M793.889347 200.380242c27.648573 20.615681 42.196018 32.710677 63.781037 56.119312 25.313864 27.453234 43.242957 48.52047 64.502857 86.507991 44.537416 79.580127 53.527718 136.949077 53.517684 212.063821 0 64.933675-15.452562 130.459388-40.138263 187.311893-22.076044 50.841799-61.545336 104.359483-101.886297 138.933914-45.506755 39.001681-81.214423 60.462941-137.605337 81.826531-55.699867 21.102023-114.070267 28.641326-181.379458 27.791064-68.274516-0.862973-129.364283-11.040029-180.533878-31.80489-46.159002-18.731189-98.338744-46.827973-141.596418-87.541551-43.946046-41.361142-70.369064-75.958317-93.88139-127.198155-26.157437-57.004361-40.094111-129.065922-39.680686-191.781288 0-36.980719 4.033895-70.902234 12.252873-105.241856 8.532726-35.651474 20.069131-69.572989 38.13135-102.35257 18.856956-34.221214 36.754607-62.067803 58.869452-88.973149 23.248751-28.285434 39.2104-46.417894 64.295476-63.475987 18.297696-12.442861 36.879036-9.295353 47.199252-2.306612 4.403836 2.982273 8.919391 6.577992 12.933218 12.933217 9.572307 15.156208-0.334486 29.769212-6.69038 38.465836-7.148625 9.781026-23.130343 26.023643-38.738775 43.218205-38.192895 42.075603-55.133918 65.965228-74.986303 106.965794-30.772668 63.552249-37.495827 115.718611-38.131349 166.573791-0.668971 53.517684 9.995096 99.647251 27.427813 140.483919 33.916163 80.572211 94.807915 144.44289 175.270414 178.615938 41.108271 17.845472 113.812713 37.319888 181.960793 38.13135 56.193568 0.668971 125.919751-11.321666 166.574459-28.096784 45.935566-18.954626 97.223569-56.862539 127.10383-94.324918 23.013273-28.852721 52.179742-70.910931 64.413884-105.694749 14.863868-42.260239 24.806784-87.661297 24.559934-132.458943 0-54.414105-11.53373-108.417461-36.918505-156.856317-20.16747-38.483228-46.480777-74.607665-84.66899-108.048189-13.377414-11.714352-23.822728-20.067124-38.808348-31.619586-10.191774-7.857065-36.059546-25.027545-28.923632-47.326356 4.970455-15.53217 18.303717-25.294464 31.887843-27.205046 19.456354-2.736092 28.565733 2.427027 43.705885 12.041479l6.179955 4.322891zM510.755379 531.65738c-8.696624-0.668971-10.034566-0.446204-20.738102-6.689711-11.031333-6.434832-17.839451-21.183637-16.514219-35.175166V92.220334c0-18.178619 0.386665-22.815926 8.988295-31.685813 5.351768-5.519011 10.963097-11.381873 26.08987-11.539751 16.055305-0.167243 21.407073 3.846584 27.929542 9.700081 9.70677 8.711341 10.703537 17.56049 10.377078 33.525483v397.5715c-0.509756 15.273947 0.326458 22.967114-11.380535 33.502739-3.884046 3.495374-8.027653 7.693167-20.96087 8.362138l-3.791059 0.000669z m4.453341 0.573308" p-id="2652" fill="#ffffff"></path></svg>
|
After Width: | Height: | Size: 2.7 KiB |
|
@ -0,0 +1 @@
|
|||
<svg t="1729585239190" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1755" width="200" height="200"><path d="M901.489435 536.822664v-0.931601l-1.001722-198.240726c-0.100172-19.162936-9.21584-37.474409-25.043042-50.246361-14.024104-11.349507-32.265456-17.60025-51.348255-17.610268l-618.062295-0.18031c-19.142902 0-37.424323 6.280795-51.478478 17.690405-15.827203 12.842072-24.902802 31.2437-24.892785 50.486775v196.798247A114.987635 114.987635 0 1 0 195.295664 536.922836V338.782282c1.15198-1.252152 4.808264-3.596181 10.768509-3.596181l276.725622 0.090155v199.753326a114.987635 114.987635 0 1 0 65.612772 1.412428V335.326342l275.693849 0.080138c6.01033 0 9.626546 2.344029 10.768508 3.596181l1.001722 195.70637a114.987635 114.987635 0 1 0 65.592737 2.113633zM214.979496 645.910158a56.437001 56.437001 0 1 1-56.437001-56.437001 56.507122 56.507122 0 0 1 56.437001 56.437001z m354.689623 0a56.437001 56.437001 0 1 1-56.437001-56.437001 56.507122 56.507122 0 0 1 56.437001 56.437001z m295.507904 56.437001a56.437001 56.437001 0 1 1 56.437001-56.437001 56.507122 56.507122 0 0 1-56.457035 56.437001z" p-id="1756" fill='#fff'></path></svg>
|
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
|
@ -0,0 +1 @@
|
|||
<svg width="22" height="22" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#FAFAFA" d="M0 0h22v22H0z"/><circle fill="#919BAE" cx="1" cy="1" r="1"/></g></svg>
|
After Width: | Height: | Size: 192 B |
|
@ -0,0 +1 @@
|
|||
<svg t="1729561814171" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1359" width="200" height="200"><path d="M674.496 603.456c120.256 0 218.176 90.752 221.44 203.84l0.064 5.888v125.888c0 11.52-9.92 20.928-22.144 20.928h-44.352a21.568 21.568 0 0 1-22.144-20.928v-125.888c0-67.712-56.512-123.264-128-125.76l-4.928-0.064H349.568c-71.488 0-130.176 53.504-132.864 121.152l-0.064 4.672v125.888c0 11.52-9.92 20.928-22.144 20.928h-44.352A21.568 21.568 0 0 1 128 939.072v-125.888c0-113.92 95.872-206.528 215.36-209.664l6.208-0.064h324.928zM497.216 128c122.368 0 221.568 93.888 221.568 209.728s-99.2 209.792-221.568 209.792c-122.304 0-221.44-93.952-221.44-209.728C275.712 221.952 374.848 128 497.152 128z m0 83.904c-73.408 0-132.864 56.32-132.864 125.888 0 69.504 59.52 125.824 132.864 125.824 73.408 0 132.928-56.32 132.928-125.824 0-69.504-59.52-125.888-132.928-125.888z" fill="#fff" p-id="1360"></path></svg>
|
After Width: | Height: | Size: 947 B |
|
@ -185,7 +185,6 @@ export const useApiSelect = (option: ApiSelectProps) => {
|
|||
</el-select>
|
||||
)
|
||||
}
|
||||
debugger
|
||||
return (
|
||||
<el-select
|
||||
class="w-1/1"
|
||||
|
|
|
@ -16,3 +16,46 @@ export const localeProps = (t, prefix, rules) => {
|
|||
return rule
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析表单组件的 field, title 等字段(递归,如果组件包含子组件)
|
||||
*
|
||||
* @param rule 组件的生成规则 https://www.form-create.com/v3/guide/rule
|
||||
* @param fields 解析后表单组件字段
|
||||
* @param parentTitle 如果是子表单,子表单的标题,默认为空
|
||||
*/
|
||||
export const parseFormFields = (
|
||||
rule: Record<string, any>,
|
||||
fields: Array<Record<string, any>> = [],
|
||||
parentTitle: string = ''
|
||||
) => {
|
||||
const { type, field, $required, title: tempTitle, children } = rule
|
||||
if (field && tempTitle) {
|
||||
let title = tempTitle
|
||||
if (parentTitle) {
|
||||
title = `${parentTitle}.${tempTitle}`
|
||||
}
|
||||
let required = false
|
||||
if ($required) {
|
||||
required = true
|
||||
}
|
||||
fields.push({
|
||||
field,
|
||||
title,
|
||||
type,
|
||||
required
|
||||
})
|
||||
// TODO 子表单 需要处理子表单字段
|
||||
// if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
|
||||
// // 解析子表单的字段
|
||||
// rule.props.rule.forEach((item) => {
|
||||
// parseFields(item, fieldsPermission, title)
|
||||
// })
|
||||
// }
|
||||
}
|
||||
if (children && Array.isArray(children)) {
|
||||
children.forEach((rule) => {
|
||||
parseFormFields(rule, fields)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<template>
|
||||
<div class="node-handler-wrapper">
|
||||
<div class="node-handler" v-if="props.showAdd">
|
||||
<div class="node-handler">
|
||||
<el-popover
|
||||
trigger="hover"
|
||||
v-model:visible="popoverShow"
|
||||
placement="right-start"
|
||||
width="auto"
|
||||
v-if="!readonly"
|
||||
>
|
||||
<div class="handler-item-wrapper">
|
||||
<div class="handler-item" @click="addNode(NodeType.USER_TASK_NODE)">
|
||||
|
@ -27,11 +28,17 @@
|
|||
<div class="handler-item-text">条件分支</div>
|
||||
</div>
|
||||
<div class="handler-item" @click="addNode(NodeType.PARALLEL_BRANCH_NODE)">
|
||||
<div class="handler-item-icon condition">
|
||||
<div class="handler-item-icon parallel">
|
||||
<span class="iconfont icon-size icon-parallel"></span>
|
||||
</div>
|
||||
<div class="handler-item-text">并行分支</div>
|
||||
</div>
|
||||
<div class="handler-item" @click="addNode(NodeType.INCLUSIVE_BRANCH_NODE)">
|
||||
<div class="handler-item-icon inclusive">
|
||||
<span class="iconfont icon-size icon-inclusive"></span>
|
||||
</div>
|
||||
<div class="handler-item-text">包容分支</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #reference>
|
||||
<div class="add-icon"><Icon icon="ep:plus" /></div>
|
||||
|
@ -56,23 +63,36 @@ import { generateUUID } from '@/utils'
|
|||
defineOptions({
|
||||
name: 'NodeHandler'
|
||||
})
|
||||
const popoverShow = ref(false)
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const popoverShow = ref(false)
|
||||
const props = defineProps({
|
||||
childNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
default: null
|
||||
},
|
||||
showAdd: {
|
||||
// 是否显示添加节点
|
||||
type: Boolean,
|
||||
default: true
|
||||
currentNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emits = defineEmits(['update:childNode'])
|
||||
|
||||
const readonly = inject<Boolean>('readonly') // 是否只读
|
||||
|
||||
const addNode = (type: number) => {
|
||||
// 校验:条件分支、包容分支后面,不允许直接添加并行分支
|
||||
if (
|
||||
type === NodeType.PARALLEL_BRANCH_NODE &&
|
||||
[NodeType.CONDITION_BRANCH_NODE, NodeType.INCLUSIVE_BRANCH_NODE].includes(
|
||||
props.currentNode?.type
|
||||
)
|
||||
) {
|
||||
message.error('条件分支、包容分支后面,不允许直接添加并行分支')
|
||||
return
|
||||
}
|
||||
|
||||
popoverShow.value = false
|
||||
if (type === NodeType.USER_TASK_NODE) {
|
||||
const id = 'Activity_' + generateUUID()
|
||||
|
@ -122,12 +142,11 @@ const addNode = (type: number) => {
|
|||
childNode: undefined,
|
||||
conditionType: 1,
|
||||
defaultFlow: false
|
||||
|
||||
},
|
||||
{
|
||||
id: 'Flow_' + generateUUID(),
|
||||
name: '其它情况',
|
||||
showText: '其它情况进入此流程',
|
||||
showText: '未满足其它条件时,将进入此分支',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
conditionType: undefined,
|
||||
|
@ -162,6 +181,33 @@ const addNode = (type: number) => {
|
|||
}
|
||||
emits('update:childNode', data)
|
||||
}
|
||||
if (type === NodeType.INCLUSIVE_BRANCH_NODE) {
|
||||
const data: SimpleFlowNode = {
|
||||
name: '包容分支',
|
||||
type: NodeType.INCLUSIVE_BRANCH_NODE,
|
||||
id: 'GateWay_' + generateUUID(),
|
||||
childNode: props.childNode,
|
||||
conditionNodes: [
|
||||
{
|
||||
id: 'Flow_' + generateUUID(),
|
||||
name: '包容条件1',
|
||||
showText: '',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
defaultFlow: false
|
||||
},
|
||||
{
|
||||
id: 'Flow_' + generateUUID(),
|
||||
name: '其它情况',
|
||||
showText: '未满足其它条件时,将进入此分支',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
defaultFlow: true
|
||||
}
|
||||
]
|
||||
}
|
||||
emits('update:childNode', data)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -31,6 +31,13 @@
|
|||
@update:model-value="handleModelValueUpdate"
|
||||
@find:parent-node="findFromParentNode"
|
||||
/>
|
||||
<!-- 包容分支节点 -->
|
||||
<InclusiveNode
|
||||
v-if="currentNode && currentNode.type === NodeType.INCLUSIVE_BRANCH_NODE"
|
||||
:flow-node="currentNode"
|
||||
@update:model-value="handleModelValueUpdate"
|
||||
@find:parent-node="findFromParentNode"
|
||||
/>
|
||||
<!-- 递归显示孩子节点 -->
|
||||
<ProcessNodeTree
|
||||
v-if="currentNode && currentNode.childNode"
|
||||
|
@ -40,7 +47,10 @@
|
|||
/>
|
||||
|
||||
<!-- 结束节点 -->
|
||||
<EndEventNode v-if="currentNode && currentNode.type === NodeType.END_EVENT_NODE" />
|
||||
<EndEventNode
|
||||
v-if="currentNode && currentNode.type === NodeType.END_EVENT_NODE"
|
||||
:flow-node="currentNode"
|
||||
/>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import StartUserNode from './nodes/StartUserNode.vue'
|
||||
|
@ -49,6 +59,7 @@ import UserTaskNode from './nodes/UserTaskNode.vue'
|
|||
import CopyTaskNode from './nodes/CopyTaskNode.vue'
|
||||
import ExclusiveNode from './nodes/ExclusiveNode.vue'
|
||||
import ParallelNode from './nodes/ParallelNode.vue'
|
||||
import InclusiveNode from './nodes/InclusiveNode.vue'
|
||||
import { SimpleFlowNode, NodeType } from './consts'
|
||||
import { useWatchNode } from './node'
|
||||
defineOptions({
|
||||
|
|
|
@ -1,23 +1,11 @@
|
|||
<template>
|
||||
<div class="simple-flow-canvas" v-loading="loading">
|
||||
<div class="simple-flow-container">
|
||||
<div class="top-area-container">
|
||||
<div class="top-actions">
|
||||
<div class="canvas-control">
|
||||
<span class="control-scale-group">
|
||||
<span class="control-scale-button"> <Icon icon="ep:plus" @click="zoomOut()" /></span>
|
||||
<span class="control-scale-label">{{ scaleValue }}%</span>
|
||||
<span class="control-scale-button"><Icon icon="ep:minus" @click="zoomIn()" /></span>
|
||||
</span>
|
||||
</div>
|
||||
<el-button type="primary" @click="saveSimpleFlowModel">保存</el-button>
|
||||
<!-- <el-button type="primary">全局设置</el-button> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="scale-container" :style="`transform: scale(${scaleValue / 100});`">
|
||||
<ProcessNodeTree v-if="processNodeTree" v-model:flow-node="processNodeTree" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-loading="loading" class="overflow-auto">
|
||||
<SimpleProcessModel
|
||||
v-if="processNodeTree"
|
||||
:flow-node="processNodeTree"
|
||||
:readonly="false"
|
||||
@save="saveSimpleFlowModel"
|
||||
/>
|
||||
<Dialog v-model="errorDialogVisible" title="保存失败" width="400" :fullscreen="false">
|
||||
<div class="mb-2">以下节点内容不完善,请修改后保存</div>
|
||||
<div
|
||||
|
@ -35,7 +23,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ProcessNodeTree from './ProcessNodeTree.vue'
|
||||
import SimpleProcessModel from './SimpleProcessModel.vue'
|
||||
import { updateBpmSimpleModel, getBpmSimpleModel } from '@/api/bpm/simple'
|
||||
import { SimpleFlowNode, NodeType, NodeId, NODE_DEFAULT_TEXT } from './consts'
|
||||
import { getModel } from '@/api/bpm/model'
|
||||
|
@ -50,14 +38,16 @@ import * as UserGroupApi from '@/api/bpm/userGroup'
|
|||
defineOptions({
|
||||
name: 'SimpleProcessDesigner'
|
||||
})
|
||||
const router = useRouter() // 路由
|
||||
const emits = defineEmits(['success']) // 保存成功事件
|
||||
|
||||
const props = defineProps({
|
||||
modelId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
const loading = ref(true)
|
||||
|
||||
const loading = ref(false)
|
||||
const formFields = ref<string[]>([])
|
||||
const formType = ref(20)
|
||||
const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
|
||||
|
@ -79,28 +69,26 @@ const message = useMessage() // 国际化
|
|||
const processNodeTree = ref<SimpleFlowNode | undefined>()
|
||||
const errorDialogVisible = ref(false)
|
||||
let errorNodes: SimpleFlowNode[] = []
|
||||
const saveSimpleFlowModel = async () => {
|
||||
if (!props.modelId) {
|
||||
message.error('缺少模型 modelId 编号')
|
||||
const saveSimpleFlowModel = async (simpleModelNode: SimpleFlowNode) => {
|
||||
if (!simpleModelNode) {
|
||||
message.error('模型数据为空')
|
||||
return
|
||||
}
|
||||
errorNodes = []
|
||||
validateNode(processNodeTree.value, errorNodes)
|
||||
if (errorNodes.length > 0) {
|
||||
errorDialogVisible.value = true
|
||||
return
|
||||
}
|
||||
const data = {
|
||||
id: props.modelId,
|
||||
simpleModel: processNodeTree.value
|
||||
}
|
||||
|
||||
const result = await updateBpmSimpleModel(data)
|
||||
if (result) {
|
||||
message.success('修改成功')
|
||||
close()
|
||||
} else {
|
||||
message.alert('修改失败')
|
||||
try {
|
||||
loading.value = true
|
||||
const data = {
|
||||
id: props.modelId,
|
||||
simpleModel: simpleModelNode
|
||||
}
|
||||
const result = await updateBpmSimpleModel(data)
|
||||
if (result) {
|
||||
message.success('修改成功')
|
||||
emits('success')
|
||||
} else {
|
||||
message.alert('修改失败')
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
// 校验节点设置。 暂时以 showText 为空 未节点错误配置
|
||||
|
@ -111,58 +99,37 @@ const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNo
|
|||
return
|
||||
}
|
||||
if (type == NodeType.START_USER_NODE) {
|
||||
// 发起人节点暂时不用校验,直接校验孩子节点
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
|
||||
if (type === NodeType.USER_TASK_NODE) {
|
||||
if (!showText) {
|
||||
errorNodes.push(node)
|
||||
}
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
if (type === NodeType.COPY_TASK_NODE) {
|
||||
if (!showText) {
|
||||
errorNodes.push(node)
|
||||
}
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
if (type === NodeType.CONDITION_NODE) {
|
||||
if (
|
||||
type === NodeType.USER_TASK_NODE ||
|
||||
type === NodeType.COPY_TASK_NODE ||
|
||||
type === NodeType.CONDITION_NODE
|
||||
) {
|
||||
if (!showText) {
|
||||
errorNodes.push(node)
|
||||
}
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
|
||||
if (type == NodeType.CONDITION_BRANCH_NODE) {
|
||||
if (
|
||||
type == NodeType.CONDITION_BRANCH_NODE ||
|
||||
type == NodeType.PARALLEL_BRANCH_NODE ||
|
||||
type == NodeType.INCLUSIVE_BRANCH_NODE
|
||||
) {
|
||||
// 分支节点
|
||||
// 1. 先校验各个分支
|
||||
conditionNodes?.forEach((item) => {
|
||||
validateNode(item, errorNodes)
|
||||
})
|
||||
// 2. 校验孩子节点
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
router.push({ path: '/bpm/manager/model' })
|
||||
}
|
||||
let scaleValue = ref(100)
|
||||
const MAX_SCALE_VALUE = 200
|
||||
const MIN_SCALE_VALUE = 50
|
||||
// 放大
|
||||
const zoomOut = () => {
|
||||
if (scaleValue.value == MAX_SCALE_VALUE) {
|
||||
return
|
||||
}
|
||||
scaleValue.value += 10
|
||||
}
|
||||
// 缩小
|
||||
const zoomIn = () => {
|
||||
if (scaleValue.value == MIN_SCALE_VALUE) {
|
||||
return
|
||||
}
|
||||
scaleValue.value -= 10
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
|
@ -188,7 +155,7 @@ onMounted(async () => {
|
|||
// 获取用户组列表
|
||||
userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
|
||||
|
||||
// 获取 SIMPLE 设计器模型
|
||||
//获取 SIMPLE 设计器模型
|
||||
const result = await getBpmSimpleModel(props.modelId)
|
||||
if (result) {
|
||||
processNodeTree.value = result
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
<template>
|
||||
<div class="simple-process-model-container position-relative">
|
||||
<div class="position-absolute top-0px right-0px bg-#fff">
|
||||
<el-row type="flex" justify="end">
|
||||
<el-button-group key="scale-control" size="default">
|
||||
<el-button size="default" :icon="ScaleToOriginal" @click="processReZoom()" />
|
||||
<el-button size="default" :plain="true" :icon="ZoomOut" @click="zoomOut()" />
|
||||
<el-button size="default" class="w-80px"> {{ scaleValue }}% </el-button>
|
||||
<el-button size="default" :plain="true" :icon="ZoomIn" @click="zoomIn()" />
|
||||
</el-button-group>
|
||||
<el-button
|
||||
v-if="!readonly"
|
||||
size="default"
|
||||
class="ml-4px"
|
||||
type="primary"
|
||||
:icon="Select"
|
||||
@click="saveSimpleFlowModel"
|
||||
>保存模型</el-button
|
||||
>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="simple-process-model" :style="`transform: scale(${scaleValue / 100});`">
|
||||
<ProcessNodeTree v-if="processNodeTree" v-model:flow-node="processNodeTree" />
|
||||
</div>
|
||||
</div>
|
||||
<Dialog v-model="errorDialogVisible" title="保存失败" width="400" :fullscreen="false">
|
||||
<div class="mb-2">以下节点内容不完善,请修改后保存</div>
|
||||
<div
|
||||
class="mb-3 b-rounded-1 bg-gray-100 p-2 line-height-normal"
|
||||
v-for="(item, index) in errorNodes"
|
||||
:key="index"
|
||||
>
|
||||
{{ item.name }} : {{ NODE_DEFAULT_TEXT.get(item.type) }}
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="errorDialogVisible = false">知道了</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ProcessNodeTree from './ProcessNodeTree.vue'
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from './consts'
|
||||
import { useWatchNode } from './node'
|
||||
import { Select, ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue'
|
||||
defineOptions({
|
||||
name: 'SimpleProcessModel'
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
required: true
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
const emits = defineEmits<{
|
||||
'save': [node: SimpleFlowNode | undefined]
|
||||
}>()
|
||||
|
||||
const processNodeTree = useWatchNode(props)
|
||||
|
||||
provide('readonly', props.readonly)
|
||||
let scaleValue = ref(100)
|
||||
const MAX_SCALE_VALUE = 200
|
||||
const MIN_SCALE_VALUE = 50
|
||||
// 放大
|
||||
const zoomIn = () => {
|
||||
if (scaleValue.value == MAX_SCALE_VALUE) {
|
||||
return
|
||||
}
|
||||
scaleValue.value += 10
|
||||
}
|
||||
// 缩小
|
||||
const zoomOut = () => {
|
||||
if (scaleValue.value == MIN_SCALE_VALUE) {
|
||||
return
|
||||
}
|
||||
scaleValue.value -= 10
|
||||
}
|
||||
const processReZoom = () => {
|
||||
scaleValue.value = 100
|
||||
}
|
||||
|
||||
const errorDialogVisible = ref(false)
|
||||
let errorNodes: SimpleFlowNode[] = []
|
||||
const saveSimpleFlowModel = async () => {
|
||||
errorNodes = []
|
||||
validateNode(processNodeTree.value, errorNodes)
|
||||
if (errorNodes.length > 0) {
|
||||
errorDialogVisible.value = true
|
||||
return
|
||||
}
|
||||
emits('save', processNodeTree.value)
|
||||
}
|
||||
// 校验节点设置。 暂时以 showText 为空 未节点错误配置
|
||||
const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => {
|
||||
if (node) {
|
||||
const { type, showText, conditionNodes } = node
|
||||
if (type == NodeType.END_EVENT_NODE) {
|
||||
return
|
||||
}
|
||||
if (type == NodeType.START_USER_NODE) {
|
||||
// 发起人节点暂时不用校验,直接校验孩子节点
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
|
||||
if (
|
||||
type === NodeType.USER_TASK_NODE ||
|
||||
type === NodeType.COPY_TASK_NODE ||
|
||||
type === NodeType.CONDITION_NODE
|
||||
) {
|
||||
if (!showText) {
|
||||
errorNodes.push(node)
|
||||
}
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
|
||||
if (
|
||||
type == NodeType.CONDITION_BRANCH_NODE ||
|
||||
type == NodeType.PARALLEL_BRANCH_NODE ||
|
||||
type == NodeType.INCLUSIVE_BRANCH_NODE
|
||||
) {
|
||||
// 分支节点
|
||||
// 1. 先校验各个分支
|
||||
conditionNodes?.forEach((item) => {
|
||||
validateNode(item, errorNodes)
|
||||
})
|
||||
// 2. 校验孩子节点
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<SimpleProcessModel :flow-node="simpleModel" :readonly="true" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useWatchNode } from './node'
|
||||
import { SimpleFlowNode } from './consts'
|
||||
|
||||
defineOptions({
|
||||
name: 'SimpleProcessViewer'
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
required: true
|
||||
},
|
||||
// 流程任务
|
||||
tasks: {
|
||||
type: Array,
|
||||
default: () => [] as any[]
|
||||
},
|
||||
// 流程实例
|
||||
processInstance: {
|
||||
type: Object,
|
||||
default: () => undefined
|
||||
}
|
||||
})
|
||||
const approveTasks = ref<any[]>(props.tasks)
|
||||
const currentProcessInstance = ref(props.processInstance)
|
||||
const simpleModel = useWatchNode(props)
|
||||
watch(
|
||||
() => props.tasks,
|
||||
(newValue) => {
|
||||
approveTasks.value = newValue
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => props.processInstance,
|
||||
(newValue) => {
|
||||
currentProcessInstance.value = newValue
|
||||
}
|
||||
)
|
||||
|
||||
provide('tasks', approveTasks)
|
||||
provide('processInstance', currentProcessInstance)
|
||||
</script>
|
||||
p
|
|
@ -1,6 +1,6 @@
|
|||
// @ts-ignore
|
||||
import { DictDataVO } from '@/api/system/dict/types'
|
||||
|
||||
import { TaskStatusEnum } from '@/api/bpm/task'
|
||||
/**
|
||||
* 节点类型
|
||||
*/
|
||||
|
@ -79,7 +79,7 @@ export interface SimpleFlowNode {
|
|||
// 审批按钮设置
|
||||
buttonsSetting?: any[]
|
||||
// 表单权限
|
||||
fieldsPermission?: Array<Record<string, string>>
|
||||
fieldsPermission?: Array<Record<string, any>>
|
||||
// 审批任务超时处理
|
||||
timeoutHandler?: TimeoutHandler
|
||||
// 审批任务拒绝处理
|
||||
|
@ -96,7 +96,8 @@ export interface SimpleFlowNode {
|
|||
conditionGroups?: ConditionGroup
|
||||
// 是否默认的条件
|
||||
defaultFlow?: boolean
|
||||
|
||||
// 活动的状态,用于前端节点状态展示
|
||||
activityStatus?: TaskStatusEnum
|
||||
}
|
||||
// 候选人策略枚举 ( 用于审批节点。抄送节点 )
|
||||
export enum CandidateStrategy {
|
||||
|
@ -144,6 +145,14 @@ export enum CandidateStrategy {
|
|||
* 指定用户组
|
||||
*/
|
||||
USER_GROUP = 40,
|
||||
/**
|
||||
* 表单内用户字段
|
||||
*/
|
||||
FORM_USER = 50,
|
||||
/**
|
||||
* 表单内部门负责人
|
||||
*/
|
||||
FORM_DEPT_LEADER = 51,
|
||||
/**
|
||||
* 流程表达式
|
||||
*/
|
||||
|
@ -178,7 +187,7 @@ export enum ApproveMethodType {
|
|||
export type RejectHandler = {
|
||||
// 审批拒绝类型
|
||||
type: RejectHandlerType
|
||||
// 回退节点 Id
|
||||
// 退回节点 Id
|
||||
returnNodeId?: string
|
||||
}
|
||||
|
||||
|
@ -360,9 +369,13 @@ export enum OperationButtonType {
|
|||
*/
|
||||
ADD_SIGN = 5,
|
||||
/**
|
||||
* 回退
|
||||
* 退回
|
||||
*/
|
||||
RETURN = 6
|
||||
RETURN = 6,
|
||||
/**
|
||||
* 抄送
|
||||
*/
|
||||
COPY = 7
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -419,6 +432,8 @@ export const CANDIDATE_STRATEGY: DictDataVO[] = [
|
|||
{ label: '发起人部门负责人', value: CandidateStrategy.START_USER_DEPT_LEADER },
|
||||
{ label: '发起人连续部门负责人', value: CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER },
|
||||
{ label: '用户组', value: CandidateStrategy.USER_GROUP },
|
||||
{ label: '表单内用户字段', value: CandidateStrategy.FORM_USER },
|
||||
{ label: '表单内部门负责人', value: CandidateStrategy.FORM_DEPT_LEADER },
|
||||
{ label: '流程表达式', value: CandidateStrategy.EXPRESSION }
|
||||
]
|
||||
// 审批节点 的审批类型
|
||||
|
@ -503,16 +518,17 @@ OPERATION_BUTTON_NAME.set(OperationButtonType.REJECT, '拒绝')
|
|||
OPERATION_BUTTON_NAME.set(OperationButtonType.TRANSFER, '转办')
|
||||
OPERATION_BUTTON_NAME.set(OperationButtonType.DELEGATE, '委派')
|
||||
OPERATION_BUTTON_NAME.set(OperationButtonType.ADD_SIGN, '加签')
|
||||
OPERATION_BUTTON_NAME.set(OperationButtonType.RETURN, '回退')
|
||||
OPERATION_BUTTON_NAME.set(OperationButtonType.RETURN, '退回')
|
||||
OPERATION_BUTTON_NAME.set(OperationButtonType.COPY, '抄送')
|
||||
|
||||
// 默认的按钮权限设置
|
||||
export const DEFAULT_BUTTON_SETTING: ButtonSetting[] = [
|
||||
{ id: OperationButtonType.APPROVE, displayName: '通过', enable: true },
|
||||
{ id: OperationButtonType.REJECT, displayName: '拒绝', enable: true },
|
||||
{ id: OperationButtonType.TRANSFER, displayName: '转办', enable: false },
|
||||
{ id: OperationButtonType.DELEGATE, displayName: '委派', enable: false },
|
||||
{ id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: false },
|
||||
{ id: OperationButtonType.RETURN, displayName: '回退', enable: false }
|
||||
{ id: OperationButtonType.TRANSFER, displayName: '转办', enable: true },
|
||||
{ id: OperationButtonType.DELEGATE, displayName: '委派', enable: true },
|
||||
{ id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: true },
|
||||
{ id: OperationButtonType.RETURN, displayName: '退回', enable: true }
|
||||
]
|
||||
|
||||
// 发起人的按钮权限。暂时定死,不可以编辑
|
||||
|
@ -522,7 +538,7 @@ export const START_USER_BUTTON_SETTING: ButtonSetting[] = [
|
|||
{ id: OperationButtonType.TRANSFER, displayName: '转办', enable: false },
|
||||
{ id: OperationButtonType.DELEGATE, displayName: '委派', enable: false },
|
||||
{ id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: false },
|
||||
{ id: OperationButtonType.RETURN, displayName: '回退', enable: false }
|
||||
{ id: OperationButtonType.RETURN, displayName: '退回', enable: false }
|
||||
]
|
||||
|
||||
export const MULTI_LEVEL_DEPT: DictDataVO = [
|
||||
|
@ -542,3 +558,13 @@ export const MULTI_LEVEL_DEPT: DictDataVO = [
|
|||
{ label: '第 14 级部门', value: 14 },
|
||||
{ label: '第 15 级部门', value: 15 }
|
||||
]
|
||||
|
||||
/**
|
||||
* 流程实例的变量枚举
|
||||
*/
|
||||
export enum ProcessVariableEnum {
|
||||
/**
|
||||
* 发起用户 ID
|
||||
*/
|
||||
START_USER_ID = 'PROCESS_START_USER_ID'
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import SimpleProcessDesigner from './SimpleProcessDesigner.vue'
|
||||
import SimpleProcessViewer from './SimpleProcessViewer.vue'
|
||||
import '../theme/simple-process-designer.scss'
|
||||
|
||||
export { SimpleProcessDesigner }
|
||||
export { SimpleProcessDesigner, SimpleProcessViewer}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { cloneDeep } from 'lodash-es'
|
||||
import { TaskStatusEnum } from '@/api/bpm/task'
|
||||
import * as RoleApi from '@/api/system/role'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import * as PostApi from '@/api/system/post'
|
||||
|
@ -13,8 +14,10 @@ import {
|
|||
NODE_DEFAULT_NAME,
|
||||
AssignStartUserHandlerType,
|
||||
AssignEmptyHandlerType,
|
||||
FieldPermissionType
|
||||
FieldPermissionType,
|
||||
ProcessVariableEnum
|
||||
} from './consts'
|
||||
import { parseFormFields } from '@/components/FormCreate/src/utils/index'
|
||||
export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> {
|
||||
const node = ref<SimpleFlowNode>(props.flowNode)
|
||||
watch(
|
||||
|
@ -26,12 +29,30 @@ export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlo
|
|||
return node
|
||||
}
|
||||
|
||||
// 解析 formCreate 所有表单字段, 并返回
|
||||
const parseFormCreateFields = (formFields?: string[]) => {
|
||||
const result: Array<Record<string, any>> = []
|
||||
if (formFields) {
|
||||
formFields.forEach((fieldStr: string) => {
|
||||
parseFormFields(JSON.parse(fieldStr), result)
|
||||
})
|
||||
}
|
||||
// 固定添加发起人 ID 字段
|
||||
result.unshift({
|
||||
field: ProcessVariableEnum.START_USER_ID,
|
||||
title: '发起人',
|
||||
type: 'UserSelect',
|
||||
required: true
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 表单数据权限配置,用于发起人节点 、审批节点、抄送节点
|
||||
*/
|
||||
export function useFormFieldsPermission(defaultPermission: FieldPermissionType) {
|
||||
// 字段权限配置. 需要有 field, title, permissioin 属性
|
||||
const fieldsPermissionConfig = ref<Array<Record<string, string>>>([])
|
||||
const fieldsPermissionConfig = ref<Array<Record<string, any>>>([])
|
||||
|
||||
const formType = inject<Ref<number>>('formType') // 表单类型
|
||||
|
||||
|
@ -44,49 +65,26 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType)
|
|||
}
|
||||
// 默认的表单权限: 获取表单的所有字段,设置字段默认权限为只读
|
||||
const getDefaultFieldsPermission = (formFields?: string[]) => {
|
||||
const defaultFieldsPermission: Array<Record<string, string>> = []
|
||||
let defaultFieldsPermission: Array<Record<string, any>> = []
|
||||
if (formFields) {
|
||||
formFields.forEach((fieldStr: string) => {
|
||||
parseFieldsSetDefaultPermission(JSON.parse(fieldStr), defaultFieldsPermission)
|
||||
defaultFieldsPermission = parseFormCreateFields(formFields).map((item) => {
|
||||
return {
|
||||
field: item.field,
|
||||
title: item.title,
|
||||
permission: defaultPermission
|
||||
}
|
||||
})
|
||||
}
|
||||
return defaultFieldsPermission
|
||||
}
|
||||
// 解析字段。赋给默认权限
|
||||
const parseFieldsSetDefaultPermission = (
|
||||
rule: Record<string, any>,
|
||||
fieldsPermission: Array<Record<string, string>>,
|
||||
parentTitle: string = ''
|
||||
) => {
|
||||
const { /**type,*/ field, title: tempTitle, children } = rule
|
||||
if (field && tempTitle) {
|
||||
let title = tempTitle
|
||||
if (parentTitle) {
|
||||
title = `${parentTitle}.${tempTitle}`
|
||||
}
|
||||
fieldsPermission.push({
|
||||
field,
|
||||
title,
|
||||
permission: defaultPermission
|
||||
})
|
||||
// TODO 子表单 需要处理子表单字段
|
||||
// if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
|
||||
// // 解析子表单的字段
|
||||
// rule.props.rule.forEach((item) => {
|
||||
// parseFieldsSetDefaultPermission(item, fieldsPermission, title)
|
||||
// })
|
||||
// }
|
||||
}
|
||||
if (children && Array.isArray(children)) {
|
||||
children.forEach((rule) => {
|
||||
parseFieldsSetDefaultPermission(rule, fieldsPermission)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 获取表单的所有字段,作为下拉框选项
|
||||
const formFieldOptions = parseFormCreateFields(unref(formFields))
|
||||
|
||||
return {
|
||||
formType,
|
||||
fieldsPermissionConfig,
|
||||
formFieldOptions,
|
||||
getNodeConfigFormFields
|
||||
}
|
||||
}
|
||||
|
@ -94,50 +92,8 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType)
|
|||
* @description 获取表单的字段
|
||||
*/
|
||||
export function useFormFields() {
|
||||
// 解析后的表单字段
|
||||
const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
|
||||
const parseFormFields = () => {
|
||||
const parsedFormFields: Array<Record<string, string>> = []
|
||||
if (formFields) {
|
||||
formFields.value.forEach((fieldStr: string) => {
|
||||
parseField(JSON.parse(fieldStr), parsedFormFields)
|
||||
})
|
||||
}
|
||||
return parsedFormFields
|
||||
}
|
||||
// 解析字段。
|
||||
const parseField = (
|
||||
rule: Record<string, any>,
|
||||
parsedFormFields: Array<Record<string, string>>,
|
||||
parentTitle: string = ''
|
||||
) => {
|
||||
const { field, title: tempTitle, children, type } = rule
|
||||
if (field && tempTitle) {
|
||||
let title = tempTitle
|
||||
if (parentTitle) {
|
||||
title = `${parentTitle}.${tempTitle}`
|
||||
}
|
||||
parsedFormFields.push({
|
||||
field,
|
||||
title,
|
||||
type
|
||||
})
|
||||
// TODO 子表单 需要处理子表单字段
|
||||
// if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
|
||||
// // 解析子表单的字段
|
||||
// rule.props.rule.forEach((item) => {
|
||||
// parseFieldsSetDefaultPermission(item, fieldsPermission, title)
|
||||
// })
|
||||
// }
|
||||
}
|
||||
if (children && Array.isArray(children)) {
|
||||
children.forEach((rule) => {
|
||||
parseField(rule, parsedFormFields)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return parseFormFields()
|
||||
return parseFormCreateFields(unref(formFields))
|
||||
}
|
||||
|
||||
export type UserTaskFormType = {
|
||||
|
@ -151,6 +107,8 @@ export type UserTaskFormType = {
|
|||
userGroups?: number[] // 用户组
|
||||
postIds?: number[] // 岗位
|
||||
expression?: string // 流程表达式
|
||||
formUser?: string // 表单内用户字段
|
||||
formDept?: string // 表单内部门字段
|
||||
approveRatio?: number
|
||||
rejectHandlerType?: RejectHandlerType
|
||||
returnNodeId?: string
|
||||
|
@ -173,6 +131,8 @@ export type CopyTaskFormType = {
|
|||
userIds?: number[] // 用户
|
||||
userGroups?: number[] // 用户组
|
||||
postIds?: number[] // 岗位
|
||||
formUser?: string // 表单内用户字段
|
||||
formDept?: string // 表单内部门字段
|
||||
expression?: string // 流程表达式
|
||||
}
|
||||
|
||||
|
@ -186,6 +146,7 @@ export function useNodeForm(nodeType: NodeType) {
|
|||
const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList') // 部门列表
|
||||
const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList') // 用户组列表
|
||||
const deptTreeOptions = inject('deptTree') // 部门树
|
||||
const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
|
||||
const configForm = ref<UserTaskFormType | CopyTaskFormType>()
|
||||
if (nodeType === NodeType.USER_TASK_NODE) {
|
||||
configForm.value = {
|
||||
|
@ -281,6 +242,18 @@ export function useNodeForm(nodeType: NodeType) {
|
|||
}
|
||||
}
|
||||
|
||||
// 表单内用户字段
|
||||
if (configForm.value?.candidateStrategy === CandidateStrategy.FORM_USER) {
|
||||
const formFieldOptions = parseFormCreateFields(unref(formFields))
|
||||
const item = formFieldOptions.find((item) => item.field === configForm.value?.formUser)
|
||||
showText = `表单用户:${item?.title}`
|
||||
}
|
||||
|
||||
// 表单内部门负责人
|
||||
if (configForm.value?.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER) {
|
||||
showText = `表单内部门负责人`
|
||||
}
|
||||
|
||||
// 发起人自选
|
||||
if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_SELECT) {
|
||||
showText = `发起人自选`
|
||||
|
@ -327,6 +300,9 @@ export function useNodeForm(nodeType: NodeType) {
|
|||
case CandidateStrategy.USER_GROUP:
|
||||
candidateParam = configForm.value.userGroups!.join(',')
|
||||
break
|
||||
case CandidateStrategy.FORM_USER:
|
||||
candidateParam = configForm.value.formUser!
|
||||
break
|
||||
case CandidateStrategy.EXPRESSION:
|
||||
candidateParam = configForm.value.expression!
|
||||
break
|
||||
|
@ -346,6 +322,13 @@ export function useNodeForm(nodeType: NodeType) {
|
|||
candidateParam = deptIds.concat('|' + configForm.value.deptLevel + '')
|
||||
break
|
||||
}
|
||||
// 表单内部门的负责人
|
||||
case CandidateStrategy.FORM_DEPT_LEADER: {
|
||||
// 候选人参数格式: | 分隔 。左边为表单内部门字段。 右边为部门层级
|
||||
const deptFieldOnForm = configForm.value.formDept!
|
||||
candidateParam = deptFieldOnForm.concat('|' + configForm.value.deptLevel + '')
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -375,6 +358,9 @@ export function useNodeForm(nodeType: NodeType) {
|
|||
case CandidateStrategy.USER_GROUP:
|
||||
configForm.value.userGroups = candidateParam.split(',').map((item) => +item)
|
||||
break
|
||||
case CandidateStrategy.FORM_USER:
|
||||
configForm.value.formUser = candidateParam
|
||||
break
|
||||
case CandidateStrategy.EXPRESSION:
|
||||
configForm.value.expression = candidateParam
|
||||
break
|
||||
|
@ -395,6 +381,14 @@ export function useNodeForm(nodeType: NodeType) {
|
|||
configForm.value.deptLevel = +paramArray[1]
|
||||
break
|
||||
}
|
||||
// 表单内的部门负责人
|
||||
case CandidateStrategy.FORM_DEPT_LEADER: {
|
||||
// 候选人参数格式: | 分隔 。左边为表单内的部门字段。 右边为部门层级
|
||||
const paramArray = candidateParam.split('|')
|
||||
configForm.value.formDept = paramArray[0]
|
||||
configForm.value.deptLevel = +paramArray[1]
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -476,3 +470,26 @@ export function useNodeName2(node: Ref<SimpleFlowNode>, nodeType: NodeType) {
|
|||
blurEvent
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 根据节点任务状态,获取节点任务状态样式
|
||||
*/
|
||||
export function useTaskStatusClass(taskStatus: TaskStatusEnum | undefined): string {
|
||||
if (!taskStatus) {
|
||||
return ''
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.APPROVE) {
|
||||
return 'status-pass'
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.RUNNING) {
|
||||
return 'status-running'
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.REJECT) {
|
||||
return 'status-reject'
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.CANCEL) {
|
||||
return 'status-cancel'
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<div class="mb-3 font-size-16px" v-if="currentNode.defaultFlow">其它条件不满足进入此分支(该分支不可编辑和删除)</div>
|
||||
<div class="mb-3 font-size-16px" v-if="currentNode.defaultFlow">未满足其它条件时,将进入此分支(该分支不可编辑和删除)</div>
|
||||
<div v-else>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
|
|
|
@ -60,7 +60,8 @@
|
|||
<el-form-item
|
||||
v-if="
|
||||
configForm.candidateStrategy == CandidateStrategy.DEPT_MEMBER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.DEPT_LEADER
|
||||
configForm.candidateStrategy == CandidateStrategy.DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
|
||||
"
|
||||
label="指定部门"
|
||||
prop="deptIds"
|
||||
|
@ -122,7 +123,57 @@
|
|||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy === CandidateStrategy.FORM_USER"
|
||||
label="表单内用户字段"
|
||||
prop="formUser"
|
||||
>
|
||||
<el-select v-model="configForm.formUser" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="(item, idx) in userFieldOnFormOptions"
|
||||
:key="idx"
|
||||
:label="item.title"
|
||||
:value="item.field"
|
||||
:disabled ="!item.required"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER"
|
||||
label="表单内部门字段"
|
||||
prop="formDept"
|
||||
>
|
||||
<el-select v-model="configForm.formDept" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="(item, idx) in deptFieldOnFormOptions"
|
||||
:key="idx"
|
||||
:label="item.title"
|
||||
:value="item.field"
|
||||
:disabled ="!item.required"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="
|
||||
configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
|
||||
configForm.candidateStrategy ==
|
||||
CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
|
||||
"
|
||||
:label="deptLevelLabel!"
|
||||
prop="deptLevel"
|
||||
span="24"
|
||||
>
|
||||
<el-select v-model="configForm.deptLevel" clearable>
|
||||
<el-option
|
||||
v-for="(item, index) in MULTI_LEVEL_DEPT"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy === CandidateStrategy.EXPRESSION"
|
||||
label="流程表达式"
|
||||
|
@ -201,7 +252,8 @@ import {
|
|||
CandidateStrategy,
|
||||
NodeType,
|
||||
CANDIDATE_STRATEGY,
|
||||
FieldPermissionType
|
||||
FieldPermissionType,
|
||||
MULTI_LEVEL_DEPT
|
||||
} from '../consts'
|
||||
import {
|
||||
useWatchNode,
|
||||
|
@ -221,6 +273,15 @@ const props = defineProps({
|
|||
required: true
|
||||
}
|
||||
})
|
||||
const deptLevelLabel = computed(() => {
|
||||
let label = '部门负责人来源'
|
||||
if (configForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) {
|
||||
label = label + '(指定部门向上)'
|
||||
} else {
|
||||
label = label + '(发起人部门向上)'
|
||||
}
|
||||
return label
|
||||
})
|
||||
// 抽屉配置
|
||||
const { settingVisible, closeDrawer, openDrawer } = useDrawer()
|
||||
// 当前节点
|
||||
|
@ -230,9 +291,16 @@ const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.COPY_
|
|||
// 激活的 Tab 标签页
|
||||
const activeTabName = ref('user')
|
||||
// 表单字段权限配置
|
||||
const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission(
|
||||
FieldPermissionType.READ
|
||||
)
|
||||
const { formType, fieldsPermissionConfig, formFieldOptions, getNodeConfigFormFields } =
|
||||
useFormFieldsPermission(FieldPermissionType.READ)
|
||||
// 表单内用户字段选项, 必须是必填和用户选择器
|
||||
const userFieldOnFormOptions = computed(() => {
|
||||
return formFieldOptions.filter((item) => item.type === 'UserSelect')
|
||||
})
|
||||
// 表单内部门字段选项, 必须是必填和部门选择器
|
||||
const deptFieldOnFormOptions = computed(() => {
|
||||
return formFieldOptions.filter((item) => item.type === 'DeptSelect')
|
||||
})
|
||||
// 抄送人表单配置
|
||||
const formRef = ref() // 表单 Ref
|
||||
// 表单校验规则
|
||||
|
@ -243,6 +311,8 @@ const formRules = reactive({
|
|||
deptIds: [{ required: true, message: '部门不能为空', trigger: 'change' }],
|
||||
userGroups: [{ required: true, message: '用户组不能为空', trigger: 'change' }],
|
||||
postIds: [{ required: true, message: '岗位不能为空', trigger: 'change' }],
|
||||
formUser: [{ required: true, message: '表单内用户字段不能为空', trigger: 'change' }],
|
||||
formDept: [{ required: true, message: '表单内部门字段不能为空', trigger: 'change' }],
|
||||
expression: [{ required: true, message: '流程表达式不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
|
@ -260,11 +330,7 @@ const {
|
|||
const configForm = tempConfigForm as Ref<CopyTaskFormType>
|
||||
// 抄送人策略, 去掉发起人自选 和 发起人自己
|
||||
const copyUserStrategies = computed(() => {
|
||||
return CANDIDATE_STRATEGY.filter(
|
||||
(item) =>
|
||||
item.value !== CandidateStrategy.START_USER_SELECT &&
|
||||
item.value !== CandidateStrategy.START_USER
|
||||
)
|
||||
return CANDIDATE_STRATEGY.filter((item) => item.value !== CandidateStrategy.START_USER)
|
||||
})
|
||||
// 改变抄送人设置策略
|
||||
const changeCandidateStrategy = () => {
|
||||
|
@ -274,6 +340,7 @@ const changeCandidateStrategy = () => {
|
|||
configForm.value.postIds = []
|
||||
configForm.value.userGroups = []
|
||||
configForm.value.deptLevel = 1
|
||||
configForm.value.formUser = ''
|
||||
}
|
||||
// 保存配置
|
||||
const saveConfig = async () => {
|
||||
|
|
|
@ -119,7 +119,6 @@ const saveConfig = async () => {
|
|||
currentNode.value.fieldsPermission = fieldsPermissionConfig.value
|
||||
// 设置发起人的按钮权限
|
||||
currentNode.value.buttonsSetting = START_USER_BUTTON_SETTING
|
||||
console.log('currentNode.value.buttonsSetting==>', currentNode.value.buttonsSetting)
|
||||
settingVisible.value = false
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -56,7 +56,6 @@
|
|||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy == CandidateStrategy.ROLE"
|
||||
label="指定角色"
|
||||
|
@ -94,25 +93,6 @@
|
|||
show-checkbox
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="
|
||||
configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
|
||||
"
|
||||
:label="deptLevelLabel!"
|
||||
prop="deptLevel"
|
||||
span="24"
|
||||
>
|
||||
<el-select v-model="configForm.deptLevel" clearable>
|
||||
<el-option
|
||||
v-for="(item, index) in MULTI_LEVEL_DEPT"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy == CandidateStrategy.POST"
|
||||
label="指定岗位"
|
||||
|
@ -134,13 +114,7 @@
|
|||
prop="userIds"
|
||||
span="24"
|
||||
>
|
||||
<el-select
|
||||
v-model="configForm.userIds"
|
||||
clearable
|
||||
multiple
|
||||
style="width: 100%"
|
||||
@change="changedCandidateUsers"
|
||||
>
|
||||
<el-select v-model="configForm.userIds" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
|
@ -163,6 +137,57 @@
|
|||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy === CandidateStrategy.FORM_USER"
|
||||
label="表单内用户字段"
|
||||
prop="formUser"
|
||||
>
|
||||
<el-select v-model="configForm.formUser" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="(item, idx) in userFieldOnFormOptions"
|
||||
:key="idx"
|
||||
:label="item.title"
|
||||
:value="item.field"
|
||||
:disabled ="!item.required"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER"
|
||||
label="表单内部门字段"
|
||||
prop="formDept"
|
||||
>
|
||||
<el-select v-model="configForm.formDept" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="(item, idx) in deptFieldOnFormOptions"
|
||||
:key="idx"
|
||||
:label="item.title"
|
||||
:value="item.field"
|
||||
:disabled ="!item.required"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="
|
||||
configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
|
||||
configForm.candidateStrategy ==
|
||||
CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
|
||||
"
|
||||
:label="deptLevelLabel!"
|
||||
prop="deptLevel"
|
||||
span="24"
|
||||
>
|
||||
<el-select v-model="configForm.deptLevel" clearable>
|
||||
<el-option
|
||||
v-for="(item, index) in MULTI_LEVEL_DEPT"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- TODO @jason:后续要支持选择已经存好的表达式 -->
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy === CandidateStrategy.EXPRESSION"
|
||||
|
@ -184,14 +209,7 @@
|
|||
:key="index"
|
||||
class="flex items-center"
|
||||
>
|
||||
<el-radio
|
||||
:value="item.value"
|
||||
:label="item.value"
|
||||
:disabled="
|
||||
item.value !== ApproveMethodType.RANDOM_SELECT_ONE_APPROVE &&
|
||||
notAllowedMultiApprovers
|
||||
"
|
||||
>
|
||||
<el-radio :value="item.value" :label="item.value">
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
<el-form-item prop="approveRatio">
|
||||
|
@ -481,6 +499,8 @@ const deptLevelLabel = computed(() => {
|
|||
let label = '部门负责人来源'
|
||||
if (configForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) {
|
||||
label = label + '(指定部门向上)'
|
||||
} else if (configForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER) {
|
||||
label = label + '(表单内部门向上)'
|
||||
} else {
|
||||
label = label + '(发起人部门向上)'
|
||||
}
|
||||
|
@ -495,9 +515,16 @@ const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.USER_
|
|||
// 激活的 Tab 标签页
|
||||
const activeTabName = ref('user')
|
||||
// 表单字段权限设置
|
||||
const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission(
|
||||
FieldPermissionType.READ
|
||||
)
|
||||
const { formType, fieldsPermissionConfig, formFieldOptions, getNodeConfigFormFields } =
|
||||
useFormFieldsPermission(FieldPermissionType.READ)
|
||||
// 表单内用户字段选项, 必须是必填和用户选择器
|
||||
const userFieldOnFormOptions = computed(() => {
|
||||
return formFieldOptions.filter((item) => item.type === 'UserSelect')
|
||||
})
|
||||
// 表单内部门字段选项, 必须是必填和部门选择器
|
||||
const deptFieldOnFormOptions = computed(() => {
|
||||
return formFieldOptions.filter((item) => item.type === 'DeptSelect')
|
||||
})
|
||||
// 操作按钮设置
|
||||
const { buttonsSetting, btnDisplayNameEdit, changeBtnDisplayName, btnDisplayNameBlurEvent } =
|
||||
useButtonsSetting()
|
||||
|
@ -511,6 +538,8 @@ const formRules = reactive({
|
|||
roleIds: [{ required: true, message: '角色不能为空', trigger: 'change' }],
|
||||
deptIds: [{ required: true, message: '部门不能为空', trigger: 'change' }],
|
||||
userGroups: [{ required: true, message: '用户组不能为空', trigger: 'change' }],
|
||||
formUser: [{ required: true, message: '表单内用户字段不能为空', trigger: 'change' }],
|
||||
formDept: [{ required: true, message: '表单内部门字段不能为空', trigger: 'change' }],
|
||||
postIds: [{ required: true, message: '岗位不能为空', trigger: 'change' }],
|
||||
expression: [{ required: true, message: '流程表达式不能为空', trigger: 'blur' }],
|
||||
approveMethod: [{ required: true, message: '多人审批方式不能为空', trigger: 'change' }],
|
||||
|
@ -537,8 +566,7 @@ const {
|
|||
getShowText
|
||||
} = useNodeForm(NodeType.USER_TASK_NODE)
|
||||
const configForm = tempConfigForm as Ref<UserTaskFormType>
|
||||
// 不允许多人审批
|
||||
const notAllowedMultiApprovers = ref(false)
|
||||
|
||||
// 改变审批人设置策略
|
||||
const changeCandidateStrategy = () => {
|
||||
configForm.value.userIds = []
|
||||
|
@ -547,30 +575,11 @@ const changeCandidateStrategy = () => {
|
|||
configForm.value.postIds = []
|
||||
configForm.value.userGroups = []
|
||||
configForm.value.deptLevel = 1
|
||||
configForm.value.formUser = ''
|
||||
configForm.value.formDept = ''
|
||||
configForm.value.approveMethod = ApproveMethodType.SEQUENTIAL_APPROVE
|
||||
if (
|
||||
configForm.value.candidateStrategy === CandidateStrategy.START_USER ||
|
||||
configForm.value.candidateStrategy === CandidateStrategy.USER
|
||||
) {
|
||||
notAllowedMultiApprovers.value = true
|
||||
} else {
|
||||
notAllowedMultiApprovers.value = false
|
||||
}
|
||||
}
|
||||
// 改变审批候选人
|
||||
const changedCandidateUsers = () => {
|
||||
if (
|
||||
configForm.value.userIds &&
|
||||
configForm.value.userIds?.length <= 1 &&
|
||||
configForm.value.candidateStrategy === CandidateStrategy.USER
|
||||
) {
|
||||
configForm.value.approveMethod = ApproveMethodType.RANDOM_SELECT_ONE_APPROVE
|
||||
configForm.value.rejectHandlerType = RejectHandlerType.FINISH_PROCESS
|
||||
notAllowedMultiApprovers.value = true
|
||||
} else {
|
||||
notAllowedMultiApprovers.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 审批方式改变
|
||||
const approveMethodChanged = () => {
|
||||
configForm.value.rejectHandlerType = RejectHandlerType.FINISH_PROCESS
|
||||
|
@ -579,7 +588,7 @@ const approveMethodChanged = () => {
|
|||
}
|
||||
formRef.value.clearValidate('approveRatio')
|
||||
}
|
||||
// 审批拒绝 可回退的节点
|
||||
// 审批拒绝 可退回的节点
|
||||
const returnTaskList = ref<SimpleFlowNode[]>([])
|
||||
// 审批人超时未处理设置
|
||||
const {
|
||||
|
@ -666,11 +675,6 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
|
|||
configForm.value.candidateStrategy = node.candidateStrategy!
|
||||
// 解析候选人参数
|
||||
parseCandidateParam(node.candidateStrategy!, node?.candidateParam)
|
||||
if (configForm.value.userIds && configForm.value.userIds.length > 1) {
|
||||
notAllowedMultiApprovers.value = true
|
||||
} else {
|
||||
notAllowedMultiApprovers.value = false
|
||||
}
|
||||
// 2.2 设置审批方式
|
||||
configForm.value.approveMethod = node.approveMethod!
|
||||
if (node.approveMethod == ApproveMethodType.APPROVE_BY_RATIO) {
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
<template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div class="node-box" :class="{ 'node-config-error': !currentNode.showText }">
|
||||
<div
|
||||
class="node-box"
|
||||
:class="[
|
||||
{ 'node-config-error': !currentNode.showText },
|
||||
`${useTaskStatusClass(currentNode?.activityStatus)}`
|
||||
]"
|
||||
>
|
||||
<div class="node-title-container">
|
||||
<div class="node-title-icon copy-task"><span class="iconfont icon-copy"></span></div>
|
||||
<input
|
||||
v-if="showInput"
|
||||
v-if="!readonly && showInput"
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent()"
|
||||
|
@ -24,9 +30,9 @@
|
|||
<div class="node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.COPY_TASK_NODE) }}
|
||||
</div>
|
||||
<Icon icon="ep:arrow-right-bold" />
|
||||
<Icon v-if="!readonly" icon="ep:arrow-right-bold" />
|
||||
</div>
|
||||
<div class="node-toolbar">
|
||||
<div v-if="!readonly" class="node-toolbar">
|
||||
<div class="toolbar-icon"
|
||||
><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
|
||||
/></div>
|
||||
|
@ -34,15 +40,23 @@
|
|||
</div>
|
||||
|
||||
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
|
||||
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||
<NodeHandler
|
||||
v-if="currentNode"
|
||||
v-model:child-node="currentNode.childNode"
|
||||
:current-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
<CopyTaskNodeConfig v-if="currentNode" ref="nodeSetting" :flow-node="currentNode" />
|
||||
<CopyTaskNodeConfig
|
||||
v-if="!readonly && currentNode"
|
||||
ref="nodeSetting"
|
||||
:flow-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||
import NodeHandler from '../NodeHandler.vue'
|
||||
import { useNodeName2, useWatchNode } from '../node'
|
||||
import { useNodeName2, useWatchNode, useTaskStatusClass } from '../node'
|
||||
import CopyTaskNodeConfig from '../nodes-config/CopyTaskNodeConfig.vue'
|
||||
defineOptions({
|
||||
name: 'CopyTaskNode'
|
||||
|
@ -57,7 +71,8 @@ const props = defineProps({
|
|||
const emits = defineEmits<{
|
||||
'update:flowNode': [node: SimpleFlowNode | undefined]
|
||||
}>()
|
||||
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
// 监控节点的变化
|
||||
const currentNode = useWatchNode(props)
|
||||
// 节点名称编辑
|
||||
|
@ -66,6 +81,9 @@ const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.
|
|||
const nodeSetting = ref()
|
||||
// 打开节点配置
|
||||
const openNodeConfig = () => {
|
||||
if (readonly) {
|
||||
return
|
||||
}
|
||||
nodeSetting.value.showCopyTaskNodeConfig(currentNode.value)
|
||||
nodeSetting.value.openDrawer()
|
||||
}
|
||||
|
|
|
@ -1,13 +1,102 @@
|
|||
<template>
|
||||
<div class="end-node-wrapper">
|
||||
<div class="end-node-box">
|
||||
<div class="end-node-box cursor-pointer" :class="`${useTaskStatusClass(currentNode?.activityStatus)}`" @click="nodeClick">
|
||||
<span class="node-fixed-name" title="结束">结束</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog title="审批信息" v-model="dialogVisible" width="1000px" append-to-body>
|
||||
<el-row>
|
||||
<el-table
|
||||
:data="processInstanceInfos"
|
||||
size="small"
|
||||
border
|
||||
header-cell-class-name="table-header-gray"
|
||||
>
|
||||
<el-table-column
|
||||
label="序号"
|
||||
header-align="center"
|
||||
align="center"
|
||||
type="index"
|
||||
width="50"
|
||||
/>
|
||||
<el-table-column
|
||||
label="发起人"
|
||||
prop="assigneeUser.nickname"
|
||||
min-width="100"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column label="部门" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.deptName || scope.row.ownerUser?.deptName }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="开始时间"
|
||||
prop="createTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="结束时间"
|
||||
prop="endTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column align="center" label="审批状态" prop="status" min-width="90">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="100">
|
||||
<template #default="scope">
|
||||
{{ formatPast2(scope.row.durationInMillis) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { SimpleFlowNode } from '../consts'
|
||||
import { useWatchNode, useTaskStatusClass } from '../node'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
defineOptions({
|
||||
name: 'EndEventNode'
|
||||
})
|
||||
const props = defineProps({
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
// 监控节点变化
|
||||
const currentNode = useWatchNode(props)
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
const processInstance = inject<Ref<any>>('processInstance')
|
||||
// 审批信息的弹窗显示,用于只读模式
|
||||
const dialogVisible = ref(false) // 弹窗可见性
|
||||
const processInstanceInfos = ref<any[]>([]) // 流程的审批信息
|
||||
|
||||
const nodeClick = () => {
|
||||
if (readonly) {
|
||||
if(processInstance && processInstance.value){
|
||||
processInstanceInfos.value = [
|
||||
{
|
||||
assigneeUser: processInstance.value.startUser,
|
||||
createTime: processInstance.value.startTime,
|
||||
endTime: processInstance.value.endTime,
|
||||
status: processInstance.value.status,
|
||||
durationInMillis: processInstance.value.durationInMillis
|
||||
}
|
||||
]
|
||||
dialogVisible.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
@ -1,7 +1,17 @@
|
|||
<template>
|
||||
<div class="branch-node-wrapper">
|
||||
<div class="branch-node-container">
|
||||
<div class="branch-node-add" @click="addCondition">添加条件</div>
|
||||
<div
|
||||
v-if="readonly"
|
||||
class="branch-node-readonly"
|
||||
:class="`${useTaskStatusClass(currentNode?.activityStatus)}`"
|
||||
>
|
||||
<span class="iconfont icon-exclusive icon-size condition"></span>
|
||||
</div>
|
||||
<el-button v-else class="branch-node-add" color="#67c23a" @click="addCondition" plain
|
||||
>添加条件</el-button
|
||||
>
|
||||
|
||||
<div
|
||||
class="branch-node-item"
|
||||
v-for="(item, index) in currentNode.conditionNodes"
|
||||
|
@ -17,9 +27,15 @@
|
|||
</template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div class="node-box" :class="{ 'node-config-error': !item.showText }">
|
||||
<div
|
||||
class="node-box"
|
||||
:class="[
|
||||
{ 'node-config-error': !item.showText },
|
||||
`${useTaskStatusClass(item.activityStatus)}`
|
||||
]"
|
||||
>
|
||||
<div class="branch-node-title-container">
|
||||
<div v-if="showInputs[index]">
|
||||
<div v-if="!readonly && showInputs[index]">
|
||||
<input
|
||||
type="text"
|
||||
class="input-max-width editable-title-input"
|
||||
|
@ -39,7 +55,10 @@
|
|||
{{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-toolbar" v-if="index + 1 !== currentNode.conditionNodes?.length">
|
||||
<div
|
||||
class="node-toolbar"
|
||||
v-if="!readonly && index + 1 !== currentNode.conditionNodes?.length"
|
||||
>
|
||||
<div class="toolbar-icon">
|
||||
<Icon
|
||||
color="#0089ff"
|
||||
|
@ -65,7 +84,7 @@
|
|||
<Icon icon="ep:arrow-right" />
|
||||
</div>
|
||||
</div>
|
||||
<NodeHandler v-model:child-node="item.childNode" />
|
||||
<NodeHandler v-model:child-node="item.childNode" :current-node="item" />
|
||||
</div>
|
||||
</div>
|
||||
<ConditionNodeConfig :node-index="index" :condition-node="item" :ref="item.id" />
|
||||
|
@ -78,7 +97,11 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||
<NodeHandler
|
||||
v-if="currentNode"
|
||||
v-model:child-node="currentNode.childNode"
|
||||
:current-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -87,6 +110,7 @@ import NodeHandler from '../NodeHandler.vue'
|
|||
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||
import { getDefaultConditionNodeName } from '../utils'
|
||||
import { useTaskStatusClass } from '../node'
|
||||
import { generateUUID } from '@/utils'
|
||||
import ConditionNodeConfig from '../nodes-config/ConditionNodeConfig.vue'
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
|
@ -94,10 +118,6 @@ defineOptions({
|
|||
name: 'ExclusiveNode'
|
||||
})
|
||||
const props = defineProps({
|
||||
// parentNode : {
|
||||
// type: Object as () => SimpleFlowNode,
|
||||
// required: true
|
||||
// },
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
required: true
|
||||
|
@ -113,10 +133,9 @@ const emits = defineEmits<{
|
|||
nodeType: number
|
||||
]
|
||||
}>()
|
||||
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
const currentNode = ref<SimpleFlowNode>(props.flowNode)
|
||||
// const conditionNodes = computed(() => currentNode.value.conditionNodes);
|
||||
|
||||
watch(
|
||||
() => props.flowNode,
|
||||
(newValue) => {
|
||||
|
@ -139,6 +158,9 @@ const clickEvent = (index: number) => {
|
|||
}
|
||||
|
||||
const conditionNodeConfig = (nodeId: string) => {
|
||||
if (readonly) {
|
||||
return
|
||||
}
|
||||
const conditionNode = proxy.$refs[nodeId][0]
|
||||
conditionNode.open()
|
||||
}
|
||||
|
@ -193,7 +215,7 @@ const recursiveFindParentNode = (
|
|||
node: SimpleFlowNode,
|
||||
nodeType: number
|
||||
) => {
|
||||
if (!node || node.type === NodeType.START_EVENT_NODE) {
|
||||
if (!node || node.type === NodeType.START_USER_NODE) {
|
||||
return
|
||||
}
|
||||
if (node.type === nodeType) {
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
<template>
|
||||
<div class="branch-node-wrapper">
|
||||
<div class="branch-node-container">
|
||||
<div
|
||||
v-if="readonly"
|
||||
class="branch-node-readonly"
|
||||
:class="`${useTaskStatusClass(currentNode?.activityStatus)}`"
|
||||
>
|
||||
<span class="iconfont icon-inclusive icon-size inclusive"></span>
|
||||
</div>
|
||||
<el-button v-else class="branch-node-add" color="#345da2" @click="addCondition" plain
|
||||
>添加条件</el-button
|
||||
>
|
||||
<div
|
||||
class="branch-node-item"
|
||||
v-for="(item, index) in currentNode.conditionNodes"
|
||||
:key="index"
|
||||
>
|
||||
<template v-if="index == 0">
|
||||
<div class="branch-line-first-top"> </div>
|
||||
<div class="branch-line-first-bottom"></div>
|
||||
</template>
|
||||
<template v-if="index + 1 == currentNode.conditionNodes?.length">
|
||||
<div class="branch-line-last-top"></div>
|
||||
<div class="branch-line-last-bottom"></div>
|
||||
</template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div
|
||||
class="node-box"
|
||||
:class="[
|
||||
{ 'node-config-error': !item.showText },
|
||||
`${useTaskStatusClass(item.activityStatus)}`
|
||||
]"
|
||||
>
|
||||
<div class="branch-node-title-container">
|
||||
<div v-if="showInputs[index]">
|
||||
<input
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent(index)"
|
||||
v-mountedFocus
|
||||
v-model="item.name"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="branch-title" @click="clickEvent(index)"> {{ item.name }} </div>
|
||||
</div>
|
||||
<div class="branch-node-content" @click="conditionNodeConfig(item.id)">
|
||||
<div class="branch-node-text" :title="item.showText" v-if="item.showText">
|
||||
{{ item.showText }}
|
||||
</div>
|
||||
<div class="branch-node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="node-toolbar"
|
||||
v-if="!readonly && index + 1 !== currentNode.conditionNodes?.length"
|
||||
>
|
||||
<div class="toolbar-icon">
|
||||
<Icon
|
||||
color="#0089ff"
|
||||
icon="ep:circle-close-filled"
|
||||
:size="18"
|
||||
@click="deleteCondition(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="branch-node-move move-node-left"
|
||||
v-if="!readonly && index != 0 && index + 1 !== currentNode.conditionNodes?.length"
|
||||
@click="moveNode(index, -1)"
|
||||
>
|
||||
<Icon icon="ep:arrow-left" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="branch-node-move move-node-right"
|
||||
v-if="
|
||||
!readonly &&
|
||||
currentNode.conditionNodes &&
|
||||
index < currentNode.conditionNodes.length - 2
|
||||
"
|
||||
@click="moveNode(index, 1)"
|
||||
>
|
||||
<Icon icon="ep:arrow-right" />
|
||||
</div>
|
||||
</div>
|
||||
<NodeHandler v-model:child-node="item.childNode" :current-node="item" />
|
||||
</div>
|
||||
</div>
|
||||
<ConditionNodeConfig :node-index="index" :condition-node="item" :ref="item.id" />
|
||||
<!-- 递归显示子节点 -->
|
||||
<ProcessNodeTree
|
||||
v-if="item && item.childNode"
|
||||
:parent-node="item"
|
||||
v-model:flow-node="item.childNode"
|
||||
@find:recursive-find-parent-node="recursiveFindParentNode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<NodeHandler
|
||||
v-if="currentNode"
|
||||
v-model:child-node="currentNode.childNode"
|
||||
:current-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import NodeHandler from '../NodeHandler.vue'
|
||||
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||
import { useTaskStatusClass } from '../node'
|
||||
import { getDefaultInclusiveConditionNodeName } from '../utils'
|
||||
import { generateUUID } from '@/utils'
|
||||
import ConditionNodeConfig from '../nodes-config/ConditionNodeConfig.vue'
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
defineOptions({
|
||||
name: 'InclusiveNode'
|
||||
})
|
||||
const props = defineProps({
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
// 定义事件,更新父组件
|
||||
const emits = defineEmits<{
|
||||
'update:modelValue': [node: SimpleFlowNode | undefined]
|
||||
'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: number]
|
||||
'find:recursiveFindParentNode': [
|
||||
nodeList: SimpleFlowNode[],
|
||||
curentNode: SimpleFlowNode,
|
||||
nodeType: number
|
||||
]
|
||||
}>()
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
|
||||
const currentNode = ref<SimpleFlowNode>(props.flowNode)
|
||||
|
||||
watch(
|
||||
() => props.flowNode,
|
||||
(newValue) => {
|
||||
currentNode.value = newValue
|
||||
}
|
||||
)
|
||||
|
||||
const showInputs = ref<boolean[]>([])
|
||||
// 失去焦点
|
||||
const blurEvent = (index: number) => {
|
||||
showInputs.value[index] = false
|
||||
const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
|
||||
conditionNode.name =
|
||||
conditionNode.name || getDefaultInclusiveConditionNodeName(index, conditionNode.defaultFlow)
|
||||
}
|
||||
|
||||
// 点击条件名称
|
||||
const clickEvent = (index: number) => {
|
||||
showInputs.value[index] = true
|
||||
}
|
||||
|
||||
const conditionNodeConfig = (nodeId: string) => {
|
||||
if (readonly) {
|
||||
return
|
||||
}
|
||||
const conditionNode = proxy.$refs[nodeId][0]
|
||||
conditionNode.open()
|
||||
}
|
||||
|
||||
// 新增条件
|
||||
const addCondition = () => {
|
||||
const conditionNodes = currentNode.value.conditionNodes
|
||||
if (conditionNodes) {
|
||||
const len = conditionNodes.length
|
||||
let lastIndex = len - 1
|
||||
const conditionData: SimpleFlowNode = {
|
||||
id: 'Flow_' + generateUUID(),
|
||||
name: '包容条件' + len,
|
||||
showText: '',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
conditionNodes: [],
|
||||
conditionType: 1,
|
||||
defaultFlow: false
|
||||
}
|
||||
conditionNodes.splice(lastIndex, 0, conditionData)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除条件
|
||||
const deleteCondition = (index: number) => {
|
||||
const conditionNodes = currentNode.value.conditionNodes
|
||||
if (conditionNodes) {
|
||||
conditionNodes.splice(index, 1)
|
||||
if (conditionNodes.length == 1) {
|
||||
const childNode = currentNode.value.childNode
|
||||
// 更新此节点为后续孩子节点
|
||||
emits('update:modelValue', childNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移动节点
|
||||
const moveNode = (index: number, to: number) => {
|
||||
// -1 :向左 1: 向右
|
||||
if (currentNode.value.conditionNodes) {
|
||||
currentNode.value.conditionNodes[index] = currentNode.value.conditionNodes.splice(
|
||||
index + to,
|
||||
1,
|
||||
currentNode.value.conditionNodes[index]
|
||||
)[0]
|
||||
}
|
||||
}
|
||||
// 递归从父节点中查询匹配的节点
|
||||
const recursiveFindParentNode = (
|
||||
nodeList: SimpleFlowNode[],
|
||||
node: SimpleFlowNode,
|
||||
nodeType: number
|
||||
) => {
|
||||
if (!node || node.type === NodeType.START_USER_NODE) {
|
||||
return
|
||||
}
|
||||
if (node.type === nodeType) {
|
||||
nodeList.push(node)
|
||||
}
|
||||
// 条件节点 (NodeType.CONDITION_NODE) 比较特殊。需要调用其父节点条件分支节点(NodeType.INCLUSIVE_BRANCH_NODE) 继续查找
|
||||
emits('find:parentNode', nodeList, nodeType)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
|
@ -1,7 +1,16 @@
|
|||
<template>
|
||||
<div class="branch-node-wrapper">
|
||||
<div class="branch-node-container">
|
||||
<div class="branch-node-add" @click="addCondition">添加分支</div>
|
||||
<div
|
||||
v-if="readonly"
|
||||
class="branch-node-readonly"
|
||||
:class="`${useTaskStatusClass(currentNode?.activityStatus)}`"
|
||||
>
|
||||
<span class="iconfont icon-parallel icon-size parallel"></span>
|
||||
</div>
|
||||
<el-button v-else class="branch-node-add" color="#626aef" @click="addCondition" plain
|
||||
>添加分支</el-button
|
||||
>
|
||||
<div
|
||||
class="branch-node-item"
|
||||
v-for="(item, index) in currentNode.conditionNodes"
|
||||
|
@ -17,7 +26,7 @@
|
|||
</template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div class="node-box">
|
||||
<div class="node-box" :class="`${useTaskStatusClass(item.activityStatus)}`">
|
||||
<div class="branch-node-title-container">
|
||||
<div v-if="showInputs[index]">
|
||||
<input
|
||||
|
@ -39,7 +48,7 @@
|
|||
{{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-toolbar">
|
||||
<div v-if="!readonly" class="node-toolbar">
|
||||
<div class="toolbar-icon">
|
||||
<Icon
|
||||
color="#0089ff"
|
||||
|
@ -49,20 +58,8 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div
|
||||
class="branch-node-move move-node-left"
|
||||
v-if="index != 0 && index + 1 !== currentNode.conditionNodes?.length" @click="moveNode(index, -1)">
|
||||
<Icon icon="ep:arrow-left" />
|
||||
</div> -->
|
||||
|
||||
<!-- <div
|
||||
class="branch-node-move move-node-right"
|
||||
v-if="currentNode.conditionNodes && index < currentNode.conditionNodes.length - 2"
|
||||
@click="moveNode(index, 1)">
|
||||
<Icon icon="ep:arrow-right" />
|
||||
</div> -->
|
||||
</div>
|
||||
<NodeHandler v-model:child-node="item.childNode" />
|
||||
<NodeHandler v-model:child-node="item.childNode" :current-node="item" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 递归显示子节点 -->
|
||||
|
@ -74,7 +71,11 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||
<NodeHandler
|
||||
v-if="currentNode"
|
||||
v-model:child-node="currentNode.childNode"
|
||||
:current-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -82,8 +83,8 @@
|
|||
import NodeHandler from '../NodeHandler.vue'
|
||||
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||
import { useTaskStatusClass } from '../node'
|
||||
import { generateUUID } from '@/utils'
|
||||
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
defineOptions({
|
||||
name: 'ParallelNode'
|
||||
|
@ -106,6 +107,8 @@ const emits = defineEmits<{
|
|||
}>()
|
||||
|
||||
const currentNode = ref<SimpleFlowNode>(props.flowNode)
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
|
||||
watch(
|
||||
() => props.flowNode,
|
||||
|
@ -169,7 +172,7 @@ const recursiveFindParentNode = (
|
|||
node: SimpleFlowNode,
|
||||
nodeType: number
|
||||
) => {
|
||||
if (!node || node.type === NodeType.START_EVENT_NODE) {
|
||||
if (!node || node.type === NodeType.START_USER_NODE) {
|
||||
return
|
||||
}
|
||||
if (node.type === nodeType) {
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
<template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div class="node-box" :class="{ 'node-config-error': !currentNode.showText }">
|
||||
<div
|
||||
class="node-box"
|
||||
:class="[
|
||||
{ 'node-config-error': !currentNode.showText },
|
||||
`${useTaskStatusClass(currentNode?.activityStatus)}`
|
||||
]"
|
||||
>
|
||||
<div class="node-title-container">
|
||||
<div class="node-title-icon start-user"
|
||||
><span class="iconfont icon-start-user"></span
|
||||
|
@ -19,27 +25,88 @@
|
|||
{{ currentNode.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-content" @click="openNodeConfig">
|
||||
<div class="node-content" @click="nodeClick">
|
||||
<div class="node-text" :title="currentNode.showText" v-if="currentNode.showText">
|
||||
{{ currentNode.showText }}
|
||||
</div>
|
||||
<div class="node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.START_USER_NODE) }}
|
||||
</div>
|
||||
<Icon icon="ep:arrow-right-bold" />
|
||||
<Icon icon="ep:arrow-right-bold" v-if="!readonly" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
|
||||
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||
<NodeHandler
|
||||
v-if="currentNode"
|
||||
v-model:child-node="currentNode.childNode"
|
||||
:current-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<StartUserNodeConfig v-if="currentNode" ref="nodeSetting" :flow-node="currentNode" />
|
||||
<StartUserNodeConfig v-if="!readonly && currentNode" ref="nodeSetting" :flow-node="currentNode" />
|
||||
<!-- 审批记录 -->
|
||||
<el-dialog
|
||||
:title="dialogTitle || '审批记录'"
|
||||
v-model="dialogVisible"
|
||||
width="1000px"
|
||||
append-to-body
|
||||
>
|
||||
<el-row>
|
||||
<el-table :data="selectTasks" size="small" border header-cell-class-name="table-header-gray">
|
||||
<el-table-column
|
||||
label="序号"
|
||||
header-align="center"
|
||||
align="center"
|
||||
type="index"
|
||||
width="50"
|
||||
/>
|
||||
<el-table-column label="审批人" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.nickname || scope.row.ownerUser?.nickname }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="部门" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.deptName || scope.row.ownerUser?.deptName }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="开始时间"
|
||||
prop="createTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="结束时间"
|
||||
prop="endTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column align="center" label="审批状态" prop="status" min-width="90">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="审批建议" prop="reason" min-width="120" />
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="100">
|
||||
<template #default="scope">
|
||||
{{ formatPast2(scope.row.durationInMillis) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import NodeHandler from '../NodeHandler.vue'
|
||||
import { useWatchNode, useNodeName2 } from '../node'
|
||||
import { useWatchNode, useNodeName2, useTaskStatusClass } from '../node'
|
||||
import { SimpleFlowNode, NODE_DEFAULT_TEXT, NodeType } from '../consts'
|
||||
import StartUserNodeConfig from '../nodes-config/StartUserNodeConfig.vue'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
defineOptions({
|
||||
name: 'StartEventNode'
|
||||
})
|
||||
|
@ -49,6 +116,8 @@ const props = defineProps({
|
|||
default: () => null
|
||||
}
|
||||
})
|
||||
const readonly = inject<Boolean>('readonly') // 是否只读
|
||||
const tasks = inject<Ref<any[]>>('tasks')
|
||||
// 定义事件,更新父组件。
|
||||
const emits = defineEmits<{
|
||||
'update:modelValue': [node: SimpleFlowNode | undefined]
|
||||
|
@ -59,11 +128,27 @@ const currentNode = useWatchNode(props)
|
|||
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.START_USER_NODE)
|
||||
|
||||
const nodeSetting = ref()
|
||||
// 打开节点配置
|
||||
const openNodeConfig = () => {
|
||||
// 把当前节点传递给配置组件
|
||||
nodeSetting.value.showStartUserNodeConfig(currentNode.value)
|
||||
nodeSetting.value.openDrawer()
|
||||
//
|
||||
const nodeClick = () => {
|
||||
if (readonly) {
|
||||
// 只读模式,弹窗显示任务信息
|
||||
if (tasks && tasks.value) {
|
||||
dialogTitle.value = currentNode.value.name
|
||||
selectTasks.value = tasks.value.filter(
|
||||
(item: any) => item?.taskDefinitionKey === currentNode.value.id
|
||||
)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
} else {
|
||||
// 编辑模式,打开节点配置、把当前节点传递给配置组件
|
||||
nodeSetting.value.showStartUserNodeConfig(currentNode.value)
|
||||
nodeSetting.value.openDrawer()
|
||||
}
|
||||
}
|
||||
|
||||
// 任务的弹窗显示,用于只读模式
|
||||
const dialogVisible = ref(false) // 弹窗可见性
|
||||
const dialogTitle = ref<string | undefined>(undefined) // 弹窗标题
|
||||
const selectTasks = ref<any[] | undefined>([]) // 选中的任务数组
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
<template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div class="node-box" :class="{ 'node-config-error': !currentNode.showText }">
|
||||
<div
|
||||
class="node-box"
|
||||
:class="[
|
||||
{ 'node-config-error': !currentNode.showText },
|
||||
`${useTaskStatusClass(currentNode?.activityStatus)}`
|
||||
]"
|
||||
>
|
||||
<div class="node-title-container">
|
||||
<div class="node-title-icon user-task"><span class="iconfont icon-approve"></span></div>
|
||||
<input
|
||||
v-if="showInput"
|
||||
v-if="!readonly && showInput"
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent()"
|
||||
|
@ -17,23 +23,27 @@
|
|||
{{ currentNode.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-content" @click="openNodeConfig">
|
||||
<div class="node-content" @click="nodeClick">
|
||||
<div class="node-text" :title="currentNode.showText" v-if="currentNode.showText">
|
||||
{{ currentNode.showText }}
|
||||
</div>
|
||||
<div class="node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.USER_TASK_NODE) }}
|
||||
</div>
|
||||
<Icon icon="ep:arrow-right-bold" />
|
||||
<Icon icon="ep:arrow-right-bold" v-if="!readonly" />
|
||||
</div>
|
||||
<div class="node-toolbar">
|
||||
<div v-if="!readonly" class="node-toolbar">
|
||||
<div class="toolbar-icon"
|
||||
><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
|
||||
/></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
|
||||
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||
<NodeHandler
|
||||
v-if="currentNode"
|
||||
v-model:child-node="currentNode.childNode"
|
||||
:current-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<UserTaskNodeConfig
|
||||
|
@ -42,12 +52,69 @@
|
|||
:flow-node="currentNode"
|
||||
@find:return-task-nodes="findReturnTaskNodes"
|
||||
/>
|
||||
<!-- 审批记录 -->
|
||||
<el-dialog
|
||||
:title="dialogTitle || '审批记录'"
|
||||
v-model="dialogVisible"
|
||||
width="1000px"
|
||||
append-to-body
|
||||
>
|
||||
<el-row>
|
||||
<el-table :data="selectTasks" size="small" border header-cell-class-name="table-header-gray">
|
||||
<el-table-column
|
||||
label="序号"
|
||||
header-align="center"
|
||||
align="center"
|
||||
type="index"
|
||||
width="50"
|
||||
/>
|
||||
<el-table-column label="审批人" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.nickname || scope.row.ownerUser?.nickname }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="部门" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.deptName || scope.row.ownerUser?.deptName }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="开始时间"
|
||||
prop="createTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="结束时间"
|
||||
prop="endTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column align="center" label="审批状态" prop="status" min-width="90">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="审批建议" prop="reason" min-width="120" />
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="100">
|
||||
<template #default="scope">
|
||||
{{ formatPast2(scope.row.durationInMillis) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||
import { useWatchNode, useNodeName2 } from '../node'
|
||||
import { useWatchNode, useNodeName2, useTaskStatusClass } from '../node'
|
||||
import NodeHandler from '../NodeHandler.vue'
|
||||
import UserTaskNodeConfig from '../nodes-config/UserTaskNodeConfig.vue'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
defineOptions({
|
||||
name: 'UserTaskNode'
|
||||
})
|
||||
|
@ -61,22 +128,36 @@ const emits = defineEmits<{
|
|||
'update:flowNode': [node: SimpleFlowNode | undefined]
|
||||
'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: NodeType]
|
||||
}>()
|
||||
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
const tasks = inject<Ref<any[]>>('tasks')
|
||||
// 监控节点变化
|
||||
const currentNode = useWatchNode(props)
|
||||
// 节点名称编辑
|
||||
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.START_USER_NODE)
|
||||
const nodeSetting = ref()
|
||||
// 打开节点配置
|
||||
const openNodeConfig = () => {
|
||||
// 把当前节点传递给配置组件
|
||||
nodeSetting.value.showUserTaskNodeConfig(currentNode.value)
|
||||
nodeSetting.value.openDrawer()
|
||||
|
||||
const nodeClick = () => {
|
||||
if (readonly) {
|
||||
if (tasks && tasks.value) {
|
||||
dialogTitle.value = currentNode.value.name
|
||||
// 只读模式,弹窗显示任务信息
|
||||
selectTasks.value = tasks.value.filter(
|
||||
(item: any) => item?.taskDefinitionKey === currentNode.value.id
|
||||
)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
} else {
|
||||
// 编辑模式,打开节点配置、把当前节点传递给配置组件
|
||||
nodeSetting.value.showUserTaskNodeConfig(currentNode.value)
|
||||
nodeSetting.value.openDrawer()
|
||||
}
|
||||
}
|
||||
|
||||
const deleteNode = () => {
|
||||
emits('update:flowNode', currentNode.value.childNode)
|
||||
}
|
||||
|
||||
// 查找可以驳回用户节点
|
||||
const findReturnTaskNodes = (
|
||||
matchNodeList: SimpleFlowNode[] // 匹配的节点
|
||||
|
@ -84,5 +165,10 @@ const findReturnTaskNodes = (
|
|||
// 从父节点查找
|
||||
emits('find:parentNode', matchNodeList, NodeType.USER_TASK_NODE)
|
||||
}
|
||||
|
||||
// 任务的弹窗显示,用于只读模式
|
||||
const dialogVisible = ref(false) // 弹窗可见性
|
||||
const dialogTitle = ref<string | undefined>(undefined) // 弹窗标题
|
||||
const selectTasks = ref<any[] | undefined>([]) // 选中的任务数组
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
@ -8,6 +8,14 @@ export const getDefaultConditionNodeName = (index: number, defaultFlow: boolean
|
|||
return '条件' + (index + 1)
|
||||
}
|
||||
|
||||
// 获取包容分支条件节点默认的名称
|
||||
export const getDefaultInclusiveConditionNodeName = (index: number, defaultFlow: boolean | undefined): string => {
|
||||
if (defaultFlow) {
|
||||
return '其它情况'
|
||||
}
|
||||
return '包容条件' + (index + 1)
|
||||
}
|
||||
|
||||
export const convertTimeUnit = (strTimeUnit: string) => {
|
||||
if (strTimeUnit === 'M') {
|
||||
return TimeUnitType.MINUTE
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" title="人员选择" width="800">
|
||||
<el-row class="gap2" v-loading="formLoading">
|
||||
<el-col :span="6">
|
||||
<ContentWrap class="h-1/1">
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
:data="deptTree"
|
||||
:expand-on-click-node="false"
|
||||
:props="defaultProps"
|
||||
default-expand-all
|
||||
highlight-current
|
||||
node-key="id"
|
||||
@node-click="handleNodeClick"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</el-col>
|
||||
<el-col :span="17">
|
||||
<el-transfer
|
||||
v-model="selectedUserIdList"
|
||||
:titles="['未选', '已选']"
|
||||
filterable
|
||||
filter-placeholder="搜索成员"
|
||||
:data="transferUserList"
|
||||
:props="{ label: 'nickname', key: 'id' }"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template #footer>
|
||||
<el-button
|
||||
:disabled="formLoading || !selectedUserIdList?.length"
|
||||
type="primary"
|
||||
@click="submitForm"
|
||||
>
|
||||
确 定
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { defaultProps, findTreeNode, handleTree } from '@/utils/tree'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'UserSelectForm' })
|
||||
const emit = defineEmits<{
|
||||
confirm: [id: any, userList: any[]]
|
||||
}>()
|
||||
const { t } = useI18n() // 国际
|
||||
const message = useMessage() // 消息弹窗
|
||||
const deptTree = ref<Tree[]>([]) // 部门树形结构化
|
||||
const userList = ref<UserApi.UserVO[]>([]) // 所有用户列表
|
||||
const filteredUserList = ref<UserApi.UserVO[]>([]) // 当前部门过滤后的用户列表
|
||||
const selectedUserIdList: any = ref([]) // 选中的用户列表
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const activityId = ref()
|
||||
|
||||
/** 计算属性:合并已选择的用户和当前部门过滤后的用户 */
|
||||
const transferUserList = computed(() => {
|
||||
// 1.1 获取所有已选择的用户
|
||||
const selectedUsers = userList.value.filter((user: any) =>
|
||||
selectedUserIdList.value.includes(user.id)
|
||||
)
|
||||
|
||||
// 1.2 获取当前部门过滤后的未选择用户
|
||||
const filteredUnselectedUsers = filteredUserList.value.filter(
|
||||
(user: any) => !selectedUserIdList.value.includes(user.id)
|
||||
)
|
||||
|
||||
// 2. 合并并去重
|
||||
return [...selectedUsers, ...filteredUnselectedUsers]
|
||||
})
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: number, selectedList?: any[]) => {
|
||||
activityId.value = id
|
||||
resetForm()
|
||||
|
||||
// 加载部门、用户列表
|
||||
deptTree.value = handleTree(await DeptApi.getSimpleDeptList())
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
|
||||
// 初始状态下,过滤列表等于所有用户列表
|
||||
filteredUserList.value = [...userList.value]
|
||||
selectedUserIdList.value = selectedList?.map((item: any) => item.id) || []
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
/** 获取部门过滤后的用户列表 */
|
||||
const getUserList = async (deptId?: number) => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
// @ts-ignore
|
||||
// TODO @芋艿:替换到 simple List 暂不支持 deptId 过滤
|
||||
// TODO @Zqqq:这个,可以使用前端过滤么?通过 deptList 获取到 deptId 子节点,然后去 userList
|
||||
const data = await UserApi.getUserPage({ pageSize: 100, pageNo: 1, deptId })
|
||||
// 更新过滤后的用户列表
|
||||
filteredUserList.value = data.list
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交选择 */
|
||||
const submitForm = async () => {
|
||||
try {
|
||||
message.success(t('common.updateSuccess'))
|
||||
dialogVisible.value = false
|
||||
// 从所有用户列表中筛选出已选择的用户
|
||||
const emitUserList = userList.value.filter((user: any) =>
|
||||
selectedUserIdList.value.includes(user.id)
|
||||
)
|
||||
// 发送操作成功的事件
|
||||
emit('confirm', activityId.value, emitUserList)
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
deptTree.value = []
|
||||
userList.value = []
|
||||
filteredUserList.value = []
|
||||
selectedUserIdList.value = []
|
||||
}
|
||||
|
||||
/** 处理部门被点击 */
|
||||
const handleNodeClick = (row: { [key: string]: any }) => {
|
||||
getUserList(row.id)
|
||||
}
|
||||
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep() {
|
||||
.el-transfer {
|
||||
display: flex;
|
||||
}
|
||||
.el-transfer__buttons {
|
||||
display: flex !important;
|
||||
flex-direction: column-reverse;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
.el-transfer__button:nth-child(2) {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1211,6 +1211,76 @@
|
|||
"isAttr": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AssignStartUserHandlerType",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:StartEvent", "bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "Integer",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "RejectHandlerType",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "Integer",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "RejectReturnTaskId",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "String",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AssignEmptyHandlerType",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "Integer",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AssignEmptyUserIds",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "String",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"emumerations": []
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="process-panel__container" :style="{ width: `${width}px` }">
|
||||
<div class="process-panel__container" :style="{ width: `${width}px`,maxHeight: '700px' }">
|
||||
<el-collapse v-model="activeTab">
|
||||
<el-collapse-item name="base">
|
||||
<!-- class="panel-tab__title" -->
|
||||
|
@ -54,6 +54,10 @@
|
|||
<template #title><Icon icon="ep:promotion" />其他</template>
|
||||
<element-other-config :id="elementId" />
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="customConfig" v-if="elementType.indexOf('Task') !== -1" key="customConfig">
|
||||
<template #title><Icon icon="ep:circle-plus-filled" />自定义配置</template>
|
||||
<element-custom-config :id="elementId" :type="elementType" />
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,283 @@
|
|||
<!-- UserTask 自定义配置:
|
||||
1. 审批人与提交人为同一人时
|
||||
2. 审批人拒绝时
|
||||
3. 审批人为空时
|
||||
-->
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<el-divider content-position="left">审批人拒绝时</el-divider>
|
||||
<el-form-item prop="rejectHandlerType">
|
||||
<el-radio-group
|
||||
v-model="rejectHandlerType"
|
||||
:disabled="returnTaskList.length === 0"
|
||||
@change="updateRejectHandlerType"
|
||||
>
|
||||
<div class="flex-col">
|
||||
<div v-for="(item, index) in REJECT_HANDLER_TYPES" :key="index">
|
||||
<el-radio :key="item.value" :value="item.value" :label="item.label" />
|
||||
</div>
|
||||
</div>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="rejectHandlerType == RejectHandlerType.RETURN_USER_TASK"
|
||||
label="驳回节点"
|
||||
prop="returnNodeId"
|
||||
>
|
||||
<el-select v-model="returnNodeId" clearable style="width: 100%" @change="updateReturnNodeId">
|
||||
<el-option
|
||||
v-for="item in returnTaskList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">审批人为空时</el-divider>
|
||||
<el-form-item prop="assignEmptyHandlerType">
|
||||
<el-radio-group v-model="assignEmptyHandlerType" @change="updateAssignEmptyHandlerType">
|
||||
<div class="flex-col">
|
||||
<div v-for="(item, index) in ASSIGN_EMPTY_HANDLER_TYPES" :key="index">
|
||||
<el-radio :key="item.value" :value="item.value" :label="item.label" />
|
||||
</div>
|
||||
</div>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="assignEmptyHandlerType == AssignEmptyHandlerType.ASSIGN_USER"
|
||||
label="指定用户"
|
||||
prop="assignEmptyHandlerUserIds"
|
||||
span="24"
|
||||
>
|
||||
<el-select
|
||||
v-model="assignEmptyUserIds"
|
||||
clearable
|
||||
multiple
|
||||
style="width: 100%"
|
||||
@change="updateAssignEmptyUserIds"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">审批人与提交人为同一人时</el-divider>
|
||||
<el-radio-group v-model="assignStartUserHandlerType" @change="updateAssignStartUserHandlerType">
|
||||
<div class="flex-col">
|
||||
<div v-for="(item, index) in ASSIGN_START_USER_HANDLER_TYPES" :key="index">
|
||||
<el-radio :key="item.value" :value="item.value" :label="item.label" />
|
||||
</div>
|
||||
</div>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
ASSIGN_START_USER_HANDLER_TYPES,
|
||||
RejectHandlerType,
|
||||
REJECT_HANDLER_TYPES,
|
||||
ASSIGN_EMPTY_HANDLER_TYPES,
|
||||
AssignEmptyHandlerType
|
||||
} from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'ElementCustomConfig' })
|
||||
const props = defineProps({
|
||||
id: String,
|
||||
type: String
|
||||
})
|
||||
const prefix = inject('prefix')
|
||||
|
||||
// 审批人与提交人为同一人时
|
||||
const assignStartUserHandlerTypeEl = ref()
|
||||
const assignStartUserHandlerType = ref()
|
||||
|
||||
// 审批人拒绝时
|
||||
const rejectHandlerTypeEl = ref()
|
||||
const rejectHandlerType = ref()
|
||||
const returnNodeIdEl = ref()
|
||||
const returnNodeId = ref()
|
||||
const returnTaskList = ref([])
|
||||
|
||||
// 审批人为空时
|
||||
const assignEmptyHandlerTypeEl = ref()
|
||||
const assignEmptyHandlerType = ref()
|
||||
const assignEmptyUserIdsEl = ref()
|
||||
const assignEmptyUserIds = ref()
|
||||
|
||||
const elExtensionElements = ref()
|
||||
const otherExtensions = ref()
|
||||
const bpmnElement = ref()
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances
|
||||
|
||||
const resetCustomConfigList = () => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement
|
||||
|
||||
// 获取可回退的列表
|
||||
returnTaskList.value = findAllPredecessorsExcludingStart(
|
||||
bpmnElement.value.id,
|
||||
bpmnInstances().modeler
|
||||
)
|
||||
|
||||
// 获取元素扩展属性 或者 创建扩展属性
|
||||
elExtensionElements.value =
|
||||
bpmnElement.value.businessObject?.extensionElements ??
|
||||
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
|
||||
|
||||
// 审批人与提交人为同一人时
|
||||
assignStartUserHandlerTypeEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:AssignStartUserHandlerType`
|
||||
)?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignStartUserHandlerType`, { value: 1 })
|
||||
assignStartUserHandlerType.value = assignStartUserHandlerTypeEl.value.value
|
||||
|
||||
// 审批人拒绝时
|
||||
rejectHandlerTypeEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:RejectHandlerType`
|
||||
)?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectHandlerType`, { value: 1 })
|
||||
rejectHandlerType.value = rejectHandlerTypeEl.value.value
|
||||
returnNodeIdEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:RejectReturnTaskId`
|
||||
)?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectReturnTaskId`, { value: '' })
|
||||
returnNodeId.value = returnNodeIdEl.value.value
|
||||
|
||||
// 审批人为空时
|
||||
assignEmptyHandlerTypeEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:AssignEmptyHandlerType`
|
||||
)?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyHandlerType`, { value: 1 })
|
||||
assignEmptyHandlerType.value = assignEmptyHandlerTypeEl.value.value
|
||||
assignEmptyUserIdsEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:AssignEmptyUserIds`
|
||||
)?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyUserIds`, { value: '' })
|
||||
assignEmptyUserIds.value = assignEmptyUserIdsEl.value.value.split(',').map((item) => {
|
||||
// 如果数字超出了最大安全整数范围,则将其作为字符串处理
|
||||
let num = Number(item)
|
||||
return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER ? item : num
|
||||
})
|
||||
|
||||
// 保留剩余扩展元素,便于后面更新该元素对应属性
|
||||
otherExtensions.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex) =>
|
||||
ex.$type !== `${prefix}:AssignStartUserHandlerType` &&
|
||||
ex.$type !== `${prefix}:RejectHandlerType` &&
|
||||
ex.$type !== `${prefix}:RejectReturnTaskId` &&
|
||||
ex.$type !== `${prefix}:AssignEmptyHandlerType` &&
|
||||
ex.$type !== `${prefix}:AssignEmptyUserIds`
|
||||
) ?? []
|
||||
|
||||
// 更新元素扩展属性,避免后续报错
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
||||
const updateAssignStartUserHandlerType = () => {
|
||||
assignStartUserHandlerTypeEl.value.value = assignStartUserHandlerType.value
|
||||
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
||||
const updateRejectHandlerType = () => {
|
||||
rejectHandlerTypeEl.value.value = rejectHandlerType.value
|
||||
|
||||
returnNodeId.value = returnTaskList.value[0].id
|
||||
returnNodeIdEl.value.value = returnNodeId.value
|
||||
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
||||
const updateReturnNodeId = () => {
|
||||
returnNodeIdEl.value.value = returnNodeId.value
|
||||
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
||||
const updateAssignEmptyHandlerType = () => {
|
||||
assignEmptyHandlerTypeEl.value.value = assignEmptyHandlerType.value
|
||||
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
||||
const updateAssignEmptyUserIds = () => {
|
||||
assignEmptyUserIdsEl.value.value = assignEmptyUserIds.value.toString()
|
||||
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
||||
const updateElementExtensions = () => {
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: [
|
||||
...otherExtensions.value,
|
||||
assignStartUserHandlerTypeEl.value,
|
||||
rejectHandlerTypeEl.value,
|
||||
returnNodeIdEl.value,
|
||||
assignEmptyHandlerTypeEl.value,
|
||||
assignEmptyUserIdsEl.value
|
||||
]
|
||||
})
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
extensionElements: extensions
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val) => {
|
||||
val &&
|
||||
val.length &&
|
||||
nextTick(() => {
|
||||
resetCustomConfigList()
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
function findAllPredecessorsExcludingStart(elementId, modeler) {
|
||||
const elementRegistry = modeler.get('elementRegistry')
|
||||
const allConnections = elementRegistry.filter((element) => element.type === 'bpmn:SequenceFlow')
|
||||
const predecessors = new Set() // 使用 Set 来避免重复节点
|
||||
|
||||
// 检查是否是开始事件节点
|
||||
function isStartEvent(element) {
|
||||
return element.type === 'bpmn:StartEvent'
|
||||
}
|
||||
|
||||
function findPredecessorsRecursively(element) {
|
||||
// 获取与当前节点相连的所有连接
|
||||
const incomingConnections = allConnections.filter((connection) => connection.target === element)
|
||||
|
||||
incomingConnections.forEach((connection) => {
|
||||
const source = connection.source // 获取前置节点
|
||||
|
||||
// 只添加不是开始事件的前置节点
|
||||
if (!isStartEvent(source)) {
|
||||
predecessors.add(source.businessObject)
|
||||
// 递归查找前置节点
|
||||
findPredecessorsRecursively(source)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const targetElement = elementRegistry.get(elementId)
|
||||
if (targetElement) {
|
||||
findPredecessorsRecursively(targetElement)
|
||||
}
|
||||
|
||||
return Array.from(predecessors) // 返回前置节点数组
|
||||
}
|
||||
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
onMounted(async () => {
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
</script>
|
|
@ -268,9 +268,9 @@ const bpmnInstances = () => (window as any)?.bpmnInstances
|
|||
const resetFormList = () => {
|
||||
bpmnELement.value = bpmnInstances().bpmnElement
|
||||
formKey.value = bpmnELement.value.businessObject.formKey
|
||||
if (formKey.value?.length > 0) {
|
||||
formKey.value = parseInt(formKey.value)
|
||||
}
|
||||
// if (formKey.value?.length > 0) {
|
||||
// formKey.value = parseInt(formKey.value)
|
||||
// }
|
||||
// 获取元素扩展属性 或者 创建扩展属性
|
||||
elExtensionElements.value =
|
||||
bpmnELement.value.businessObject.get('extensionElements') ||
|
||||
|
|
|
@ -80,7 +80,7 @@ const resetAttributesList = () => {
|
|||
otherExtensionList.value = [] // 其他扩展配置
|
||||
bpmnElementProperties.value =
|
||||
// bpmnElement.value.businessObject?.extensionElements?.filter((ex) => {
|
||||
bpmnElement.value.businessObject?.extensionElements?.values.filter((ex) => {
|
||||
bpmnElement.value.businessObject?.extensionElements?.values?.filter((ex) => {
|
||||
if (ex.$type !== `${prefix}:Properties`) {
|
||||
otherExtensionList.value.push(ex)
|
||||
}
|
||||
|
|
|
@ -1,2 +1,117 @@
|
|||
@use './process-designer.scss';
|
||||
@use './process-panel.scss';
|
||||
|
||||
$success-color: #4eb819;
|
||||
$primary-color: #409EFF;
|
||||
$danger-color: #F56C6C;
|
||||
$cancel-color: #909399;
|
||||
|
||||
.process-viewer {
|
||||
position: relative;
|
||||
border: 1px solid #EFEFEF;
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+') repeat!important;
|
||||
|
||||
.success-arrow {
|
||||
fill: $success-color;
|
||||
stroke: $success-color;
|
||||
}
|
||||
|
||||
.success-conditional {
|
||||
fill: white;
|
||||
stroke: $success-color;
|
||||
}
|
||||
|
||||
.success.djs-connection {
|
||||
.djs-visual path {
|
||||
stroke: $success-color!important;
|
||||
//marker-end: url(#sequenceflow-end-white-success)!important;
|
||||
}
|
||||
}
|
||||
|
||||
.success.djs-connection.condition-expression {
|
||||
.djs-visual path {
|
||||
//marker-start: url(#conditional-flow-marker-white-success)!important;
|
||||
}
|
||||
}
|
||||
|
||||
.success.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $success-color!important;
|
||||
fill: $success-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $success-color!important;
|
||||
}
|
||||
|
||||
.djs-visual path:nth-child(2) {
|
||||
stroke: $success-color!important;
|
||||
fill: $success-color!important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $success-color!important;
|
||||
fill: $success-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
}
|
||||
|
||||
.primary.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $primary-color!important;
|
||||
fill: $primary-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $primary-color!important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $primary-color!important;
|
||||
fill: $primary-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
}
|
||||
|
||||
.danger.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $danger-color!important;
|
||||
fill: $danger-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $danger-color!important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $danger-color!important;
|
||||
fill: $danger-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $cancel-color!important;
|
||||
fill: $cancel-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $cancel-color!important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $cancel-color!important;
|
||||
fill: $cancel-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.process-viewer .djs-tooltip-container, .process-viewer .djs-overlay-container, .process-viewer .djs-palette {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -267,9 +267,9 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||
}
|
||||
},
|
||||
{
|
||||
path: 'manager/simple/workflow/model/edit',
|
||||
component: () => import('@/views/bpm/simpleWorkflow/index.vue'),
|
||||
name: 'SimpleWorkflowDesignEditor',
|
||||
path: 'manager/simple/model',
|
||||
component: () => import('@/views/bpm/simple/SimpleModelDesign.vue'),
|
||||
name: 'SimpleModelDesign',
|
||||
meta: {
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
|
@ -292,7 +292,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||
},
|
||||
{
|
||||
path: 'process-instance/detail',
|
||||
// component: () => import('@/views/bpm/processInstance/detail/index_new.vue'), // TODO 芋艿:新审批界面,已适配 simple 模式,未来会适配 bpmn 模式
|
||||
component: () => import('@/views/bpm/processInstance/detail/index.vue'),
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
meta: {
|
||||
|
|
|
@ -449,3 +449,11 @@ export const BpmModelFormType = {
|
|||
NORMAL: 10, // 流程表单
|
||||
CUSTOM: 20 // 业务表单
|
||||
}
|
||||
|
||||
export const BpmProcessInstanceStatus = {
|
||||
NOT_START: -1, // 未开始
|
||||
RUNNING: 1, // 审批中
|
||||
APPROVE: 2, // 审批通过
|
||||
REJECT: 3, // 审批不通过
|
||||
CANCEL: 4 // 已取消
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ export const setConfAndFields2 = (
|
|||
value?: object
|
||||
) => {
|
||||
if (isRef(detailPreview)) {
|
||||
// @ts-ignore
|
||||
detailPreview = detailPreview.value
|
||||
}
|
||||
// @ts-ignore
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
<script setup lang="ts">
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
/** BPM 流程分类 表单 */
|
||||
defineOptions({ name: 'CategoryForm' })
|
||||
|
@ -57,7 +58,7 @@ const formData = ref({
|
|||
id: undefined,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
status: undefined,
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
sort: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
|
@ -116,7 +117,7 @@ const resetForm = () => {
|
|||
id: undefined,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
status: undefined,
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
sort: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
|
|
|
@ -70,13 +70,7 @@
|
|||
|
||||
<!-- 弹窗:流程模型图的预览 -->
|
||||
<Dialog title="流程图" v-model="bpmnDetailVisible" width="800">
|
||||
<MyProcessViewer
|
||||
key="designer"
|
||||
v-model="bpmnXml"
|
||||
:value="bpmnXml as any"
|
||||
v-bind="bpmnControlForm"
|
||||
:prefix="bpmnControlForm.prefix"
|
||||
/>
|
||||
<MyProcessViewer style="height: 700px" key="designer" :xml="bpmnXml" />
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
|
@ -118,7 +112,7 @@ const formDetailPreview = ref({
|
|||
rule: [],
|
||||
option: {}
|
||||
})
|
||||
const handleFormDetail = async (row) => {
|
||||
const handleFormDetail = async (row: any) => {
|
||||
if (row.formType == 10) {
|
||||
// 设置表单
|
||||
setConfAndFields2(formDetailPreview, row.formConf, row.formFields)
|
||||
|
@ -133,13 +127,13 @@ const handleFormDetail = async (row) => {
|
|||
|
||||
/** 流程图的详情按钮操作 */
|
||||
const bpmnDetailVisible = ref(false)
|
||||
const bpmnXml = ref(null)
|
||||
const bpmnControlForm = ref({
|
||||
prefix: 'flowable'
|
||||
})
|
||||
const handleBpmnDetail = async (row) => {
|
||||
bpmnXml.value = (await DefinitionApi.getProcessDefinition(row.id))?.bpmnXml
|
||||
const bpmnXml = ref('')
|
||||
const handleBpmnDetail = async (row: any) => {
|
||||
// 设置可见
|
||||
bpmnXml.value = ''
|
||||
bpmnDetailVisible.value = true
|
||||
// 加载 BPMN XML
|
||||
bpmnXml.value = (await DefinitionApi.getProcessDefinition(row.id))?.bpmnXml
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
|
|
|
@ -64,7 +64,11 @@ const designerConfig = ref({
|
|||
switchType: [], // 是否可以切换组件类型,或者可以相互切换的字段
|
||||
autoActive: true, // 是否自动选中拖入的组件
|
||||
useTemplate: false, // 是否生成vue2语法的模板组件
|
||||
formOptions: {}, // 定义表单配置默认值
|
||||
formOptions: {
|
||||
form: {
|
||||
labelWidth: '100px' // 设置默认的 label 宽度为 100px
|
||||
}
|
||||
}, // 定义表单配置默认值
|
||||
fieldReadonly: false, // 配置field是否可以编辑
|
||||
hiddenDragMenu: false, // 隐藏拖拽操作按钮
|
||||
hiddenDragBtn: false, // 隐藏拖拽按钮
|
||||
|
|
|
@ -143,8 +143,9 @@ const openForm = (id?: number) => {
|
|||
const toRouter: { name: string; query?: { id: number } } = {
|
||||
name: 'BpmFormEditor'
|
||||
}
|
||||
console.log(typeof id)
|
||||
// 表单新建的时候id传的是event需要排除
|
||||
if (typeof id === 'number') {
|
||||
if (typeof id === 'number' || typeof id === 'string') {
|
||||
toRouter.query = {
|
||||
id
|
||||
}
|
||||
|
|
|
@ -0,0 +1,532 @@
|
|||
<template>
|
||||
<div class="flex items-center h-50px">
|
||||
<!-- 头部:分类名 -->
|
||||
<div class="flex items-center">
|
||||
<el-tooltip content="拖动排序" v-if="isCategorySorting">
|
||||
<Icon
|
||||
:size="22"
|
||||
icon="ic:round-drag-indicator"
|
||||
class="ml-10px category-drag-icon cursor-move text-#8a909c"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<h3 class="ml-20px mr-8px text-18px">{{ categoryInfo.name }}</h3>
|
||||
<div class="color-gray-600 text-16px"> ({{ categoryInfo.modelList?.length || 0 }}) </div>
|
||||
</div>
|
||||
<!-- 头部:操作 -->
|
||||
<div class="flex-1 flex" v-if="!isCategorySorting">
|
||||
<div
|
||||
v-if="categoryInfo.modelList.length > 0"
|
||||
class="ml-20px flex items-center"
|
||||
:class="[
|
||||
'transition-transform duration-300 cursor-pointer',
|
||||
isExpand ? 'rotate-180' : 'rotate-0'
|
||||
]"
|
||||
@click="isExpand = !isExpand"
|
||||
>
|
||||
<Icon icon="ep:arrow-down-bold" color="#999" />
|
||||
</div>
|
||||
<div class="ml-auto flex items-center" :class="isModelSorting ? 'mr-15px' : 'mr-45px'">
|
||||
<template v-if="!isModelSorting">
|
||||
<el-button
|
||||
v-if="categoryInfo.modelList.length > 0"
|
||||
link
|
||||
type="info"
|
||||
class="mr-20px"
|
||||
@click.stop="handleModelSort"
|
||||
>
|
||||
<Icon icon="fa:sort-amount-desc" class="mr-5px" />
|
||||
排序
|
||||
</el-button>
|
||||
<el-button v-else link type="info" class="mr-20px" @click.stop="openModelForm('create')">
|
||||
<Icon icon="fa:plus" class="mr-5px" />
|
||||
新建
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
@command="(command) => handleCategoryCommand(command, categoryInfo)"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-button link type="info">
|
||||
<Icon icon="ep:setting" class="mr-5px" />
|
||||
分类
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="handleRename"> 重命名 </el-dropdown-item>
|
||||
<el-dropdown-item command="handleDeleteCategory"> 删除该类 </el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button @click.stop="handleModelSortCancel"> 取 消 </el-button>
|
||||
<el-button type="primary" @click.stop="handleModelSortSubmit"> 保存排序 </el-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 模型列表 -->
|
||||
<el-collapse-transition>
|
||||
<div v-show="isExpand">
|
||||
<el-table
|
||||
:class="categoryInfo.name"
|
||||
ref="tableRef"
|
||||
:header-cell-style="{ backgroundColor: isDark ? '' : '#edeff0', paddingLeft: '10px' }"
|
||||
:cell-style="{ paddingLeft: '10px' }"
|
||||
:row-style="{ height: '68px' }"
|
||||
:data="modelList"
|
||||
row-key="id"
|
||||
>
|
||||
<el-table-column label="流程名" prop="name" min-width="150">
|
||||
<template #default="scope">
|
||||
<div class="flex items-center">
|
||||
<el-tooltip content="拖动排序" v-if="isModelSorting">
|
||||
<Icon
|
||||
icon="ic:round-drag-indicator"
|
||||
class="drag-icon cursor-move text-#8a909c mr-10px"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-image :src="scope.row.icon" class="h-38px w-38px mr-10px rounded" />
|
||||
{{ scope.row.name }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="可见范围" prop="startUserIds" min-width="100">
|
||||
<template #default="scope">
|
||||
<el-text v-if="!scope.row.startUsers || scope.row.startUsers.length === 0">
|
||||
全部可见
|
||||
</el-text>
|
||||
<el-text v-else-if="scope.row.startUsers.length == 1">
|
||||
{{ scope.row.startUsers[0].nickname }}
|
||||
</el-text>
|
||||
<el-text v-else>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
placement="top"
|
||||
:content="scope.row.startUsers.map((user: any) => user.nickname).join('、')"
|
||||
>
|
||||
{{ scope.row.startUsers[0].nickname }}等 {{ scope.row.startUsers.length }} 人可见
|
||||
</el-tooltip>
|
||||
</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="表单信息" prop="formType" min-width="200">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.formType === BpmModelFormType.NORMAL"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleFormDetail(scope.row)"
|
||||
>
|
||||
<span>{{ scope.row.formName }}</span>
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else-if="scope.row.formType === BpmModelFormType.CUSTOM"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleFormDetail(scope.row)"
|
||||
>
|
||||
<span>{{ scope.row.formCustomCreatePath }}</span>
|
||||
</el-button>
|
||||
<label v-else>暂无表单</label>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最后发布" prop="deploymentTime" min-width="250">
|
||||
<template #default="scope">
|
||||
<div class="flex items-center">
|
||||
<span v-if="scope.row.processDefinition" class="w-150px">
|
||||
{{ formatDate(scope.row.processDefinition.deploymentTime) }}
|
||||
</span>
|
||||
<el-tag v-if="scope.row.processDefinition">
|
||||
v{{ scope.row.processDefinition.version }}
|
||||
</el-tag>
|
||||
<el-tag v-else type="warning">未部署</el-tag>
|
||||
<el-tag
|
||||
v-if="scope.row.processDefinition?.suspensionState === 2"
|
||||
type="warning"
|
||||
class="ml-10px"
|
||||
>
|
||||
已停用
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openModelForm('update', scope.row.id)"
|
||||
v-hasPermi="['bpm:model:update']"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
class="!ml-5px"
|
||||
type="primary"
|
||||
@click="handleDesign(scope.row)"
|
||||
v-hasPermi="['bpm:model:update']"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
设计
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
class="!ml-5px"
|
||||
type="primary"
|
||||
@click="handleDeploy(scope.row)"
|
||||
v-hasPermi="['bpm:model:deploy']"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
发布
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
class="!align-middle ml-5px"
|
||||
@command="(command) => handleModelCommand(command, scope.row)"
|
||||
v-hasPermi="['bpm:process-definition:query', 'bpm:model:update', 'bpm:model:delete']"
|
||||
>
|
||||
<el-button type="primary" link>更多</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
command="handleDefinitionList"
|
||||
v-if="checkPermi(['bpm:process-definition:query'])"
|
||||
>
|
||||
历史
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
command="handleChangeState"
|
||||
v-if="checkPermi(['bpm:model:update']) && scope.row.processDefinition"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
{{ scope.row.processDefinition.suspensionState === 1 ? '停用' : '启用' }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
type="danger"
|
||||
command="handleDelete"
|
||||
v-if="checkPermi(['bpm:model:delete'])"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
|
||||
<!-- 弹窗:重命名分类 -->
|
||||
<Dialog :fullscreen="false" class="rename-dialog" v-model="renameCategoryVisible" width="400">
|
||||
<template #title>
|
||||
<div class="pl-10px font-bold text-18px"> 重命名分类 </div>
|
||||
</template>
|
||||
<div class="px-30px">
|
||||
<el-input v-model="renameCategoryForm.name" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="pr-25px pb-25px">
|
||||
<el-button @click="renameCategoryVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="handleRenameConfirm">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<!-- 表单弹窗:添加流程模型 -->
|
||||
<ModelForm :categoryId="categoryInfo.code" ref="modelFormRef" @success="emit('success')" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ModelForm from './ModelForm.vue'
|
||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
||||
import Sortable from 'sortablejs'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
import * as FormApi from '@/api/bpm/form'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import { BpmModelFormType, BpmModelType } from '@/utils/constants'
|
||||
import { checkPermi } from '@/utils/permission'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
defineOptions({ name: 'BpmModel' })
|
||||
|
||||
const props = defineProps({
|
||||
categoryInfo: propTypes.object.def([]), // 分类后的数据
|
||||
isCategorySorting: propTypes.bool.def(false) // 是否分类在排序
|
||||
})
|
||||
const emit = defineEmits(['success'])
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { push } = useRouter() // 路由
|
||||
const userStore = useUserStoreWithOut() // 用户信息缓存
|
||||
const isDark = computed(() => useAppStore().getIsDark) // 是否黑暗模式
|
||||
|
||||
const isModelSorting = ref(false) // 是否正处于排序状态
|
||||
const originalData: any = ref([]) // 原始数据
|
||||
const modelList: any = ref([]) // 模型列表
|
||||
const isExpand = ref(false) // 是否处于展开状态
|
||||
|
||||
/** '更多'操作按钮 */
|
||||
const handleModelCommand = (command: string, row: any) => {
|
||||
switch (command) {
|
||||
case 'handleDefinitionList':
|
||||
handleDefinitionList(row)
|
||||
break
|
||||
case 'handleDelete':
|
||||
handleDelete(row)
|
||||
break
|
||||
case 'handleChangeState':
|
||||
handleChangeState(row)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/** '分类'操作按钮 */
|
||||
const handleCategoryCommand = async (command: string, row: any) => {
|
||||
switch (command) {
|
||||
case 'handleRename':
|
||||
renameCategoryForm.value = await CategoryApi.getCategory(row.id)
|
||||
renameCategoryVisible.value = true
|
||||
break
|
||||
case 'handleDeleteCategory':
|
||||
await handleDeleteCategory()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row: any) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ModelApi.deleteModel(row.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
emit('success')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 更新状态操作 */
|
||||
const handleChangeState = async (row: any) => {
|
||||
const state = row.processDefinition.suspensionState
|
||||
const newState = state === 1 ? 2 : 1
|
||||
try {
|
||||
// 修改状态的二次确认
|
||||
const id = row.id
|
||||
debugger
|
||||
const statusState = state === 1 ? '停用' : '启用'
|
||||
const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
|
||||
await message.confirm(content)
|
||||
// 发起修改状态
|
||||
await ModelApi.updateModelState(id, newState)
|
||||
message.success(statusState + '成功')
|
||||
// 刷新列表
|
||||
emit('success')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 设计流程 */
|
||||
const handleDesign = (row: any) => {
|
||||
if (row.type == BpmModelType.BPMN) {
|
||||
push({
|
||||
name: 'BpmModelEditor',
|
||||
query: {
|
||||
modelId: row.id
|
||||
}
|
||||
})
|
||||
} else {
|
||||
push({
|
||||
name: 'SimpleModelDesign',
|
||||
query: {
|
||||
modelId: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 发布流程 */
|
||||
const handleDeploy = async (row: any) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.confirm('是否部署该流程!!')
|
||||
// 发起部署
|
||||
await ModelApi.deployModel(row.id)
|
||||
message.success(t('部署成功'))
|
||||
// 刷新列表
|
||||
emit('success')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 跳转到指定流程定义列表 */
|
||||
const handleDefinitionList = (row: any) => {
|
||||
push({
|
||||
name: 'BpmProcessDefinition',
|
||||
query: {
|
||||
key: row.key
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 流程表单的详情按钮操作 */
|
||||
const formDetailVisible = ref(false)
|
||||
const formDetailPreview = ref({
|
||||
rule: [],
|
||||
option: {}
|
||||
})
|
||||
const handleFormDetail = async (row: any) => {
|
||||
if (row.formType == 10) {
|
||||
// 设置表单
|
||||
const data = await FormApi.getForm(row.formId)
|
||||
setConfAndFields2(formDetailPreview, data.conf, data.fields)
|
||||
// 弹窗打开
|
||||
formDetailVisible.value = true
|
||||
} else {
|
||||
await push({
|
||||
path: row.formCustomCreatePath
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 判断是否可以操作 */
|
||||
const isManagerUser = (row: any) => {
|
||||
const userId = userStore.getUser.id
|
||||
return row.managerUserIds && row.managerUserIds.includes(userId)
|
||||
}
|
||||
|
||||
/** 处理模型的排序 **/
|
||||
const handleModelSort = () => {
|
||||
// 保存初始数据
|
||||
originalData.value = cloneDeep(props.categoryInfo.modelList)
|
||||
isModelSorting.value = true
|
||||
initSort()
|
||||
}
|
||||
|
||||
/** 处理模型的排序提交 */
|
||||
const handleModelSortSubmit = async () => {
|
||||
// 保存排序
|
||||
const ids = modelList.value.map((item: any) => item.id)
|
||||
await ModelApi.updateModelSortBatch(ids)
|
||||
// 刷新列表
|
||||
isModelSorting.value = false
|
||||
message.success('排序模型成功')
|
||||
emit('success')
|
||||
}
|
||||
|
||||
/** 处理模型的排序取消 */
|
||||
const handleModelSortCancel = () => {
|
||||
// 恢复初始数据
|
||||
modelList.value = cloneDeep(originalData.value)
|
||||
isModelSorting.value = false
|
||||
}
|
||||
|
||||
/** 创建拖拽实例 */
|
||||
const tableRef = ref()
|
||||
const initSort = () => {
|
||||
const table = document.querySelector(`.${props.categoryInfo.name} .el-table__body-wrapper tbody`)
|
||||
Sortable.create(table, {
|
||||
group: 'shared',
|
||||
animation: 150,
|
||||
draggable: '.el-table__row',
|
||||
handle: '.drag-icon',
|
||||
// 结束拖动事件
|
||||
onEnd: ({ newDraggableIndex, oldDraggableIndex }) => {
|
||||
if (oldDraggableIndex !== newDraggableIndex) {
|
||||
modelList.value.splice(
|
||||
newDraggableIndex,
|
||||
0,
|
||||
modelList.value.splice(oldDraggableIndex, 1)[0]
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 更新 modelList 模型列表 */
|
||||
const updateModeList = () => {
|
||||
modelList.value = cloneDeep(props.categoryInfo.modelList)
|
||||
if (props.categoryInfo.modelList.length > 0) {
|
||||
isExpand.value = true
|
||||
}
|
||||
}
|
||||
|
||||
/** 重命名弹窗确定 */
|
||||
const renameCategoryVisible = ref(false)
|
||||
const renameCategoryForm = ref({
|
||||
name: ''
|
||||
})
|
||||
const handleRenameConfirm = async () => {
|
||||
if (renameCategoryForm.value?.name.length === 0) {
|
||||
return message.warning('请输入名称')
|
||||
}
|
||||
// 发起修改
|
||||
await CategoryApi.updateCategory(renameCategoryForm.value as CategoryVO)
|
||||
message.success('重命名成功')
|
||||
// 刷新列表
|
||||
renameCategoryVisible.value = false
|
||||
emit('success')
|
||||
}
|
||||
|
||||
/** 删除分类 */
|
||||
const handleDeleteCategory = async () => {
|
||||
try {
|
||||
if (props.categoryInfo.modelList.length > 0) {
|
||||
return message.warning('该分类下仍有流程定义,不允许删除')
|
||||
}
|
||||
await message.confirm('确认删除分类吗?')
|
||||
// 发起删除
|
||||
await CategoryApi.deleteCategory(props.categoryInfo.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
emit('success')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 添加流程模型弹窗 */
|
||||
const modelFormRef = ref()
|
||||
const openModelForm = (type: string, id?: number) => {
|
||||
modelFormRef.value.open(type, id)
|
||||
}
|
||||
|
||||
watch(() => props.categoryInfo.modelList, updateModeList, { immediate: true })
|
||||
watch(
|
||||
() => props.isCategorySorting,
|
||||
(val) => {
|
||||
if (val) isExpand.value = false
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.rename-dialog.el-dialog {
|
||||
padding: 0 !important;
|
||||
|
||||
.el-dialog__header {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
border-top: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
:deep() {
|
||||
.el-table__cell {
|
||||
overflow: hidden;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -155,6 +155,7 @@
|
|||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
|
@ -170,7 +171,9 @@ defineOptions({ name: 'ModelForm' })
|
|||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
const userStore = useUserStoreWithOut() // 用户信息缓存
|
||||
|
||||
const props = defineProps({
|
||||
categoryId: propTypes.number
|
||||
})
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
|
@ -232,6 +235,9 @@ const open = async (type: string, id?: string) => {
|
|||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
// 查询用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
if (props.categoryId) {
|
||||
formData.value.category = props.categoryId
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
|
|
|
@ -1,216 +1,94 @@
|
|||
<template>
|
||||
<doc-alert title="流程设计器(BPMN)" url="https://doc.iocoder.cn/bpm/model-designer-dingding/" />
|
||||
<doc-alert
|
||||
title="流程设计器(钉钉、飞书)"
|
||||
url="https://doc.iocoder.cn/bpm/model-designer-bpmn/"
|
||||
/>
|
||||
<doc-alert title="选择审批人、发起人自选" url="https://doc.iocoder.cn/bpm/assignee/" />
|
||||
<doc-alert title="会签、或签、依次审批" url="https://doc.iocoder.cn/bpm/multi-instance/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="流程标识" prop="key">
|
||||
<el-input
|
||||
v-model="queryParams.key"
|
||||
placeholder="请输入流程标识"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入流程名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="category">
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择流程分类"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="category in categoryList"
|
||||
:key="category.code"
|
||||
:label="category.name"
|
||||
:value="category.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['bpm:model:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新建
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="流程名称" align="center" prop="name" min-width="200" />
|
||||
<el-table-column label="流程图标" align="center" prop="icon" min-width="100">
|
||||
<template #default="scope">
|
||||
<el-image :src="scope.row.icon" class="h-32px w-32px" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="可见范围" align="center" prop="startUserIds" min-width="100">
|
||||
<template #default="scope">
|
||||
<el-text v-if="!scope.row.startUsers || scope.row.startUsers.length === 0">
|
||||
全部可见
|
||||
</el-text>
|
||||
<el-text v-else-if="scope.row.startUsers.length == 1">
|
||||
{{ scope.row.startUsers[0].nickname }}
|
||||
</el-text>
|
||||
<el-text v-else>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
placement="top"
|
||||
:content="scope.row.startUsers.map((user: any) => user.nickname).join('、')"
|
||||
>
|
||||
{{ scope.row.startUsers[0].nickname }}等 {{ scope.row.startUsers.length }} 人可见
|
||||
</el-tooltip>
|
||||
</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流程分类" align="center" prop="categoryName" min-width="100" />
|
||||
<el-table-column label="表单信息" align="center" prop="formType" min-width="200">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.formType === 10"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleFormDetail(scope.row)"
|
||||
<div class="flex justify-between pl-20px items-center">
|
||||
<h3 class="font-extrabold">流程模型</h3>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
v-if="!isCategorySorting"
|
||||
class="-mb-15px flex mr-10px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item prop="name" class="ml-auto">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="搜索流程"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
>
|
||||
<span>{{ scope.row.formName }}</span>
|
||||
<template #prefix>
|
||||
<Icon icon="ep:search" class="mx-10px" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<!-- 右上角:新建模型、更多操作 -->
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="openForm('create')" v-hasPermi="['bpm:model:create']">
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新建模型
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else-if="scope.row.formType === 20"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleFormDetail(scope.row)"
|
||||
>
|
||||
<span>{{ scope.row.formCustomCreatePath }}</span>
|
||||
</el-button>
|
||||
<label v-else>暂无表单</label>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最后发布" align="center" prop="deploymentTime" min-width="250">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.processDefinition">
|
||||
{{ formatDate(scope.row.processDefinition.deploymentTime) }}
|
||||
</span>
|
||||
<el-tag v-if="scope.row.processDefinition" class="ml-10px">
|
||||
v{{ scope.row.processDefinition.version }}
|
||||
</el-tag>
|
||||
<el-tag v-else type="warning">未部署</el-tag>
|
||||
<el-tag
|
||||
v-if="scope.row.processDefinition?.suspensionState === 2"
|
||||
type="warning"
|
||||
class="ml-10px"
|
||||
>
|
||||
已停用
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['bpm:model:update']"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
class="!ml-5px"
|
||||
type="primary"
|
||||
@click="handleDesign(scope.row)"
|
||||
v-hasPermi="['bpm:model:update']"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
设计
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
class="!ml-5px"
|
||||
type="primary"
|
||||
@click="handleDeploy(scope.row)"
|
||||
v-hasPermi="['bpm:model:deploy']"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
发布
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
class="!align-middle ml-5px"
|
||||
@command="(command) => handleCommand(command, scope.row)"
|
||||
v-hasPermi="['bpm:process-definition:query', 'bpm:model:update', 'bpm:model:delete']"
|
||||
>
|
||||
<el-button type="primary" link>更多</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-dropdown @command="(command) => handleCommand(command)" placement="bottom-end">
|
||||
<el-button class="w-30px" plain>
|
||||
<Icon icon="ep:setting" />
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
command="handleDefinitionList"
|
||||
v-if="checkPermi(['bpm:process-definition:query'])"
|
||||
>
|
||||
历史
|
||||
<el-dropdown-item command="handleCategoryAdd">
|
||||
<Icon icon="ep:circle-plus" :size="13" class="mr-5px" />
|
||||
新建分类
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
command="handleChangeState"
|
||||
v-if="checkPermi(['bpm:model:update']) && scope.row.processDefinition"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
{{ scope.row.processDefinition.suspensionState === 1 ? '停用' : '启用' }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
type="danger"
|
||||
command="handleDelete"
|
||||
v-if="checkPermi(['bpm:model:delete'])"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
删除
|
||||
<el-dropdown-item command="handleCategorySort">
|
||||
<Icon icon="fa:sort-amount-desc" :size="13" class="mr-5px" />
|
||||
分类排序
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="mr-20px" v-else>
|
||||
<el-button @click="handleCategorySortCancel"> 取 消 </el-button>
|
||||
<el-button type="primary" @click="handleCategorySortSubmit"> 保存排序 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<!-- 按照分类,展示其所属的模型列表 -->
|
||||
<div class="px-15px">
|
||||
<draggable
|
||||
:disabled="!isCategorySorting"
|
||||
v-model="categoryGroup"
|
||||
item-key="id"
|
||||
:animation="400"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<ContentWrap
|
||||
class="rounded-lg transition-all duration-300 ease-in-out hover:shadow-xl"
|
||||
v-loading="loading"
|
||||
:body-style="{ padding: 0 }"
|
||||
:key="element.id"
|
||||
>
|
||||
<CategoryDraggableModel
|
||||
:isCategorySorting="isCategorySorting"
|
||||
:categoryInfo="element"
|
||||
@success="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</draggable>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改流程 -->
|
||||
<ModelForm ref="formRef" @success="getList" />
|
||||
|
||||
<!-- 表单弹窗:添加分类 -->
|
||||
<CategoryForm ref="categoryFormRef" @success="getList" />
|
||||
<!-- 弹窗:表单详情 -->
|
||||
<Dialog title="表单详情" v-model="formDetailVisible" width="800">
|
||||
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
|
||||
|
@ -218,187 +96,126 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
import * as FormApi from '@/api/bpm/form'
|
||||
import ModelForm from './ModelForm.vue'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import draggable from 'vuedraggable'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
import { BpmModelType } from '@/utils/constants'
|
||||
import { checkPermi } from '@/utils/permission'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
import ModelForm from './ModelForm.vue'
|
||||
import CategoryForm from '../category/CategoryForm.vue'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import CategoryDraggableModel from './CategoryDraggableModel.vue'
|
||||
|
||||
defineOptions({ name: 'BpmModel' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { push } = useRouter() // 路由
|
||||
const userStore = useUserStoreWithOut() // 用户信息缓存
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const isCategorySorting = ref(false) // 是否 category 正处于排序状态
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
key: undefined,
|
||||
name: undefined,
|
||||
category: undefined
|
||||
name: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const categoryList = ref([]) // 流程分类列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ModelApi.getModelPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
const categoryGroup: any = ref([]) // 按照 category 分组的数据
|
||||
const originalData: any = ref([]) // 原始数据
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** '更多'操作按钮 */
|
||||
const handleCommand = (command: string, row: any) => {
|
||||
switch (command) {
|
||||
case 'handleDefinitionList':
|
||||
handleDefinitionList(row)
|
||||
break
|
||||
case 'handleDelete':
|
||||
handleDelete(row)
|
||||
break
|
||||
case 'handleChangeState':
|
||||
handleChangeState(row)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row: any) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ModelApi.deleteModel(row.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 更新状态操作 */
|
||||
const handleChangeState = async (row: any) => {
|
||||
const state = row.processDefinition.suspensionState
|
||||
const newState = state === 1 ? 2 : 1
|
||||
try {
|
||||
// 修改状态的二次确认
|
||||
const id = row.id
|
||||
debugger
|
||||
const statusState = state === 1 ? '停用' : '启用'
|
||||
const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
|
||||
await message.confirm(content)
|
||||
// 发起修改状态
|
||||
await ModelApi.updateModelState(id, newState)
|
||||
message.success(statusState + '成功')
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 设计流程 */
|
||||
const handleDesign = (row: any) => {
|
||||
if (row.type == BpmModelType.BPMN) {
|
||||
push({
|
||||
name: 'BpmModelEditor',
|
||||
query: {
|
||||
modelId: row.id
|
||||
}
|
||||
})
|
||||
} else {
|
||||
push({
|
||||
name: 'SimpleWorkflowDesignEditor',
|
||||
query: {
|
||||
modelId: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 发布流程 */
|
||||
const handleDeploy = async (row: any) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.confirm('是否部署该流程!!')
|
||||
// 发起部署
|
||||
await ModelApi.deployModel(row.id)
|
||||
message.success(t('部署成功'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 跳转到指定流程定义列表 */
|
||||
const handleDefinitionList = (row) => {
|
||||
push({
|
||||
name: 'BpmProcessDefinition',
|
||||
query: {
|
||||
key: row.key
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 流程表单的详情按钮操作 */
|
||||
const formDetailVisible = ref(false)
|
||||
const formDetailPreview = ref({
|
||||
rule: [],
|
||||
option: {}
|
||||
})
|
||||
const handleFormDetail = async (row: any) => {
|
||||
if (row.formType == 10) {
|
||||
// 设置表单
|
||||
const data = await FormApi.getForm(row.formId)
|
||||
setConfAndFields2(formDetailPreview, data.conf, data.fields)
|
||||
// 弹窗打开
|
||||
formDetailVisible.value = true
|
||||
} else {
|
||||
await push({
|
||||
path: row.formCustomCreatePath
|
||||
})
|
||||
|
||||
/** 右上角设置按钮 */
|
||||
const handleCommand = (command: string) => {
|
||||
switch (command) {
|
||||
case 'handleCategoryAdd':
|
||||
handleCategoryAdd()
|
||||
break
|
||||
case 'handleCategorySort':
|
||||
handleCategorySort()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/** 判断是否可以操作 */
|
||||
const isManagerUser = (row: any) => {
|
||||
const userId = userStore.getUser.id
|
||||
return row.managerUserIds && row.managerUserIds.includes(userId)
|
||||
/** 新建分类 */
|
||||
const categoryFormRef = ref()
|
||||
const handleCategoryAdd = () => {
|
||||
categoryFormRef.value.open('create')
|
||||
}
|
||||
|
||||
/** 分类排序的提交 */
|
||||
const handleCategorySort = () => {
|
||||
// 保存初始数据
|
||||
originalData.value = cloneDeep(categoryGroup.value)
|
||||
isCategorySorting.value = true
|
||||
}
|
||||
|
||||
/** 分类排序的取消 */
|
||||
const handleCategorySortCancel = () => {
|
||||
// 恢复初始数据
|
||||
categoryGroup.value = cloneDeep(originalData.value)
|
||||
isCategorySorting.value = false
|
||||
}
|
||||
|
||||
/** 分类排序的保存 */
|
||||
const handleCategorySortSubmit = async () => {
|
||||
// 保存排序
|
||||
const ids = categoryGroup.value.map((item: any) => item.id)
|
||||
await CategoryApi.updateCategorySortBatch(ids)
|
||||
// 刷新列表
|
||||
isCategorySorting.value = false
|
||||
message.success('排序分类成功')
|
||||
await getList()
|
||||
}
|
||||
|
||||
/** 加载数据 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 查询模型 + 分裂的列表
|
||||
const modelList = await ModelApi.getModelList(queryParams.name)
|
||||
const categoryList = await CategoryApi.getCategorySimpleList()
|
||||
// 按照 category 聚合
|
||||
// 注意:必须一次性赋值给 categoryGroup,否则每次操作后,列表会重新渲染,滚动条的位置会偏离!!!
|
||||
categoryGroup.value = categoryList.map((category: any) => ({
|
||||
...category,
|
||||
modelList: modelList.filter((model: any) => model.categoryName == category.name)
|
||||
}))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
// 查询流程分类列表
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep() {
|
||||
.el-table--fit .el-table__inner-wrapper:before {
|
||||
height: 0;
|
||||
}
|
||||
.el-card {
|
||||
border-radius: 8px;
|
||||
}
|
||||
.el-form--inline .el-form-item {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.el-divider--horizontal {
|
||||
margin-top: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,404 @@
|
|||
<template>
|
||||
<doc-alert title="流程设计器(BPMN)" url="https://doc.iocoder.cn/bpm/model-designer-dingding/" />
|
||||
<doc-alert
|
||||
title="流程设计器(钉钉、飞书)"
|
||||
url="https://doc.iocoder.cn/bpm/model-designer-bpmn/"
|
||||
/>
|
||||
<doc-alert title="选择审批人、发起人自选" url="https://doc.iocoder.cn/bpm/assignee/" />
|
||||
<doc-alert title="会签、或签、依次审批" url="https://doc.iocoder.cn/bpm/multi-instance/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="流程标识" prop="key">
|
||||
<el-input
|
||||
v-model="queryParams.key"
|
||||
placeholder="请输入流程标识"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入流程名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="category">
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择流程分类"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="category in categoryList"
|
||||
:key="category.code"
|
||||
:label="category.name"
|
||||
:value="category.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['bpm:model:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新建
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="流程名称" align="center" prop="name" min-width="200" />
|
||||
<el-table-column label="流程图标" align="center" prop="icon" min-width="100">
|
||||
<template #default="scope">
|
||||
<el-image :src="scope.row.icon" class="h-32px w-32px" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="可见范围" align="center" prop="startUserIds" min-width="100">
|
||||
<template #default="scope">
|
||||
<el-text v-if="!scope.row.startUsers || scope.row.startUsers.length === 0">
|
||||
全部可见
|
||||
</el-text>
|
||||
<el-text v-else-if="scope.row.startUsers.length == 1">
|
||||
{{ scope.row.startUsers[0].nickname }}
|
||||
</el-text>
|
||||
<el-text v-else>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
placement="top"
|
||||
:content="scope.row.startUsers.map((user: any) => user.nickname).join('、')"
|
||||
>
|
||||
{{ scope.row.startUsers[0].nickname }}等 {{ scope.row.startUsers.length }} 人可见
|
||||
</el-tooltip>
|
||||
</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流程分类" align="center" prop="categoryName" min-width="100" />
|
||||
<el-table-column label="表单信息" align="center" prop="formType" min-width="200">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.formType === 10"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleFormDetail(scope.row)"
|
||||
>
|
||||
<span>{{ scope.row.formName }}</span>
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else-if="scope.row.formType === 20"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleFormDetail(scope.row)"
|
||||
>
|
||||
<span>{{ scope.row.formCustomCreatePath }}</span>
|
||||
</el-button>
|
||||
<label v-else>暂无表单</label>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最后发布" align="center" prop="deploymentTime" min-width="250">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.processDefinition">
|
||||
{{ formatDate(scope.row.processDefinition.deploymentTime) }}
|
||||
</span>
|
||||
<el-tag v-if="scope.row.processDefinition" class="ml-10px">
|
||||
v{{ scope.row.processDefinition.version }}
|
||||
</el-tag>
|
||||
<el-tag v-else type="warning">未部署</el-tag>
|
||||
<el-tag
|
||||
v-if="scope.row.processDefinition?.suspensionState === 2"
|
||||
type="warning"
|
||||
class="ml-10px"
|
||||
>
|
||||
已停用
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['bpm:model:update']"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
class="!ml-5px"
|
||||
type="primary"
|
||||
@click="handleDesign(scope.row)"
|
||||
v-hasPermi="['bpm:model:update']"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
设计
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
class="!ml-5px"
|
||||
type="primary"
|
||||
@click="handleDeploy(scope.row)"
|
||||
v-hasPermi="['bpm:model:deploy']"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
发布
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
class="!align-middle ml-5px"
|
||||
@command="(command) => handleCommand(command, scope.row)"
|
||||
v-hasPermi="['bpm:process-definition:query', 'bpm:model:update', 'bpm:model:delete']"
|
||||
>
|
||||
<el-button type="primary" link>更多</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
command="handleDefinitionList"
|
||||
v-if="checkPermi(['bpm:process-definition:query'])"
|
||||
>
|
||||
历史
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
command="handleChangeState"
|
||||
v-if="checkPermi(['bpm:model:update']) && scope.row.processDefinition"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
{{ scope.row.processDefinition.suspensionState === 1 ? '停用' : '启用' }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
type="danger"
|
||||
command="handleDelete"
|
||||
v-if="checkPermi(['bpm:model:delete'])"
|
||||
:disabled="!isManagerUser(scope.row)"
|
||||
>
|
||||
删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改流程 -->
|
||||
<ModelForm ref="formRef" @success="getList" />
|
||||
|
||||
<!-- 弹窗:表单详情 -->
|
||||
<Dialog title="表单详情" v-model="formDetailVisible" width="800">
|
||||
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
import * as FormApi from '@/api/bpm/form'
|
||||
import ModelForm from './ModelForm.vue'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
import { BpmModelType } from '@/utils/constants'
|
||||
import { checkPermi } from '@/utils/permission'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
|
||||
defineOptions({ name: 'BpmModel' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { push } = useRouter() // 路由
|
||||
const userStore = useUserStoreWithOut() // 用户信息缓存
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
key: undefined,
|
||||
name: undefined,
|
||||
category: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const categoryList = ref([]) // 流程分类列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ModelApi.getModelList(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** '更多'操作按钮 */
|
||||
const handleCommand = (command: string, row: any) => {
|
||||
switch (command) {
|
||||
case 'handleDefinitionList':
|
||||
handleDefinitionList(row)
|
||||
break
|
||||
case 'handleDelete':
|
||||
handleDelete(row)
|
||||
break
|
||||
case 'handleChangeState':
|
||||
handleChangeState(row)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row: any) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ModelApi.deleteModel(row.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 更新状态操作 */
|
||||
const handleChangeState = async (row: any) => {
|
||||
const state = row.processDefinition.suspensionState
|
||||
const newState = state === 1 ? 2 : 1
|
||||
try {
|
||||
// 修改状态的二次确认
|
||||
const id = row.id
|
||||
debugger
|
||||
const statusState = state === 1 ? '停用' : '启用'
|
||||
const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
|
||||
await message.confirm(content)
|
||||
// 发起修改状态
|
||||
await ModelApi.updateModelState(id, newState)
|
||||
message.success(statusState + '成功')
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 设计流程 */
|
||||
const handleDesign = (row: any) => {
|
||||
if (row.type == BpmModelType.BPMN) {
|
||||
push({
|
||||
name: 'BpmModelEditor',
|
||||
query: {
|
||||
modelId: row.id
|
||||
}
|
||||
})
|
||||
} else {
|
||||
push({
|
||||
name: 'SimpleModelDesign',
|
||||
query: {
|
||||
modelId: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 发布流程 */
|
||||
const handleDeploy = async (row: any) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.confirm('是否部署该流程!!')
|
||||
// 发起部署
|
||||
await ModelApi.deployModel(row.id)
|
||||
message.success(t('部署成功'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 跳转到指定流程定义列表 */
|
||||
const handleDefinitionList = (row) => {
|
||||
push({
|
||||
name: 'BpmProcessDefinition',
|
||||
query: {
|
||||
key: row.key
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 流程表单的详情按钮操作 */
|
||||
const formDetailVisible = ref(false)
|
||||
const formDetailPreview = ref({
|
||||
rule: [],
|
||||
option: {}
|
||||
})
|
||||
const handleFormDetail = async (row: any) => {
|
||||
if (row.formType == 10) {
|
||||
// 设置表单
|
||||
const data = await FormApi.getForm(row.formId)
|
||||
setConfAndFields2(formDetailPreview, data.conf, data.fields)
|
||||
// 弹窗打开
|
||||
formDetailVisible.value = true
|
||||
} else {
|
||||
await push({
|
||||
path: row.formCustomCreatePath
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 判断是否可以操作 */
|
||||
const isManagerUser = (row: any) => {
|
||||
const userId = userStore.getUser.id
|
||||
return row.managerUserIds && row.managerUserIds.includes(userId)
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
// 查询流程分类列表
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
})
|
||||
</script>
|
|
@ -0,0 +1,259 @@
|
|||
<template>
|
||||
<ContentWrap :bodyStyle="{ padding: '10px 20px 0' }">
|
||||
<div class="processInstance-wrap-main">
|
||||
<el-scrollbar>
|
||||
<div class="text-#878c93 h-15px">流程:{{ selectProcessDefinition.name }}</div>
|
||||
<el-divider class="!my-8px" />
|
||||
|
||||
<!-- 中间主要内容 tab 栏 -->
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 表单信息 -->
|
||||
<el-tab-pane label="表单填写" name="form">
|
||||
<div class="form-scroll-area">
|
||||
<el-scrollbar>
|
||||
<el-row>
|
||||
<el-col :span="17">
|
||||
<form-create
|
||||
:rule="detailForm.rule"
|
||||
v-model:api="fApi"
|
||||
v-model="detailForm.value"
|
||||
:option="detailForm.option"
|
||||
@submit="submitForm"
|
||||
/>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="6" :offset="1">
|
||||
<!-- 流程时间线 -->
|
||||
<ProcessInstanceTimeline
|
||||
ref="timelineRef"
|
||||
:activity-nodes="activityNodes"
|
||||
:show-status-icon="false"
|
||||
@select-user-confirm="selectUserConfirm"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<!-- 流程图 -->
|
||||
<el-tab-pane label="流程图" name="diagram">
|
||||
<div class="form-scroll-area">
|
||||
<!-- BPMN 流程图预览 -->
|
||||
<ProcessInstanceBpmnViewer
|
||||
:bpmn-xml="bpmnXML"
|
||||
v-if="BpmModelType.BPMN === selectProcessDefinition.modelType"
|
||||
/>
|
||||
|
||||
<!-- Simple 流程图预览 -->
|
||||
<ProcessInstanceSimpleViewer
|
||||
:simple-json="simpleJson"
|
||||
v-if="BpmModelType.SIMPLE === selectProcessDefinition.modelType"
|
||||
/>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<div class="b-t-solid border-t-1px border-[var(--el-border-color)]">
|
||||
<!-- 操作栏按钮 -->
|
||||
<div
|
||||
v-if="activeTab === 'form'"
|
||||
class="h-50px bottom-10 text-14px flex items-center color-#32373c dark:color-#fff font-bold btn-container"
|
||||
>
|
||||
<el-button plain type="success" @click="submitForm">
|
||||
<Icon icon="ep:select" /> 发起
|
||||
</el-button>
|
||||
<el-button plain type="danger" @click="handleCancel">
|
||||
<Icon icon="ep:close" /> 取消
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { decodeFields, setConfAndFields2 } from '@/utils/formCreate'
|
||||
import { BpmModelType } from '@/utils/constants'
|
||||
import { CandidateStrategy } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue'
|
||||
import ProcessInstanceSimpleViewer from '../detail/ProcessInstanceSimpleViewer.vue'
|
||||
import ProcessInstanceTimeline from '../detail/ProcessInstanceTimeline.vue'
|
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import { ApprovalNodeInfo } from '@/api/bpm/processInstance'
|
||||
|
||||
defineOptions({ name: 'ProcessDefinitionDetail' })
|
||||
const props = defineProps<{
|
||||
selectProcessDefinition: any
|
||||
}>()
|
||||
const emit = defineEmits(['cancel'])
|
||||
|
||||
const { push, currentRoute } = useRouter() // 路由
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
|
||||
const detailForm: any = ref({
|
||||
rule: [],
|
||||
option: {},
|
||||
value: {}
|
||||
}) // 流程表单详情
|
||||
const fApi = ref<ApiAttrs>()
|
||||
// 指定审批人
|
||||
const startUserSelectTasks: any = ref([]) // 发起人需要选择审批人或抄送人的任务列表
|
||||
const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
|
||||
const bpmnXML: any = ref(null) // BPMN 数据
|
||||
const simpleJson = ref<string | undefined>() // Simple 设计器数据 json 格式
|
||||
|
||||
const activeTab = ref('form') // 当前的 Tab
|
||||
const activityNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) // 审批节点信息
|
||||
|
||||
/** 设置表单信息、获取流程图数据 **/
|
||||
const initProcessInfo = async (row: any, formVariables?: any) => {
|
||||
// 重置指定审批人
|
||||
startUserSelectTasks.value = []
|
||||
startUserSelectAssignees.value = {}
|
||||
|
||||
// 情况一:流程表单
|
||||
if (row.formType == 10) {
|
||||
// 设置表单
|
||||
// 注意:需要从 formVariables 中,移除不在 row.formFields 的值。
|
||||
// 原因是:后端返回的 formVariables 里面,会有一些非表单的信息。例如说,某个流程节点的审批人。
|
||||
// 这样,就可能导致一个流程被审批不通过后,重新发起时,会直接后端报错!!!
|
||||
const allowedFields = decodeFields(row.formFields).map((fieldObj: any) => fieldObj.field)
|
||||
for (const key in formVariables) {
|
||||
if (!allowedFields.includes(key)) {
|
||||
delete formVariables[key]
|
||||
}
|
||||
}
|
||||
setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables)
|
||||
await nextTick()
|
||||
fApi.value?.btn.show(false) // 隐藏提交按钮
|
||||
// 获取流程审批信息
|
||||
await getApprovalDetail(row)
|
||||
|
||||
// 加载流程图
|
||||
const processDefinitionDetail = await DefinitionApi.getProcessDefinition(row.id)
|
||||
if (processDefinitionDetail) {
|
||||
bpmnXML.value = processDefinitionDetail.bpmnXml
|
||||
simpleJson.value = processDefinitionDetail.simpleModel
|
||||
}
|
||||
// 情况二:业务表单
|
||||
} else if (row.formCustomCreatePath) {
|
||||
await push({
|
||||
path: row.formCustomCreatePath
|
||||
})
|
||||
// 这里暂时无需加载流程图,因为跳出到另外个 Tab;
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取审批详情 */
|
||||
const getApprovalDetail = async (row: any) => {
|
||||
try {
|
||||
const data = await ProcessInstanceApi.getApprovalDetail({ processDefinitionId: row.id })
|
||||
if (!data) {
|
||||
message.error('查询不到审批详情信息!')
|
||||
return
|
||||
}
|
||||
|
||||
// 获取发起人自选的任务
|
||||
startUserSelectTasks.value = data.activityNodes?.filter(
|
||||
(node: ApprovalNodeInfo) => CandidateStrategy.START_USER_SELECT === node.candidateStrategy
|
||||
)
|
||||
if (startUserSelectTasks.value?.length > 0) {
|
||||
for (const node of startUserSelectTasks.value) {
|
||||
startUserSelectAssignees.value[node.id] = []
|
||||
}
|
||||
}
|
||||
|
||||
// 获取审批节点,显示 Timeline 的数据
|
||||
activityNodes.value = data.activityNodes
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = async () => {
|
||||
if (!fApi.value || !props.selectProcessDefinition) {
|
||||
return
|
||||
}
|
||||
// 如果有指定审批人,需要校验
|
||||
if (startUserSelectTasks.value?.length > 0) {
|
||||
for (const userTask of startUserSelectTasks.value) {
|
||||
if (
|
||||
Array.isArray(startUserSelectAssignees.value[userTask.id]) &&
|
||||
startUserSelectAssignees.value[userTask.id].length === 0
|
||||
)
|
||||
return message.warning(`请选择${userTask.name}的候选人`)
|
||||
}
|
||||
}
|
||||
|
||||
// 提交请求
|
||||
fApi.value.btn.loading(true)
|
||||
try {
|
||||
await ProcessInstanceApi.createProcessInstance({
|
||||
processDefinitionId: props.selectProcessDefinition.id,
|
||||
variables: detailForm.value.value,
|
||||
startUserSelectAssignees: startUserSelectAssignees.value
|
||||
})
|
||||
// 提示
|
||||
message.success('发起流程成功')
|
||||
// 跳转回去
|
||||
delView(unref(currentRoute))
|
||||
await push({
|
||||
name: 'BpmProcessInstanceMy'
|
||||
})
|
||||
} finally {
|
||||
fApi.value.btn.loading(false)
|
||||
}
|
||||
}
|
||||
|
||||
/** 取消发起审批 */
|
||||
const handleCancel = () => {
|
||||
emit('cancel')
|
||||
}
|
||||
|
||||
/** 选择发起人 */
|
||||
const selectUserConfirm = (id: string, userList: any[]) => {
|
||||
startUserSelectAssignees.value[id] = userList?.map((item: any) => item.id)
|
||||
}
|
||||
|
||||
defineExpose({ initProcessInfo })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$wrap-padding-height: 20px;
|
||||
$wrap-margin-height: 15px;
|
||||
$button-height: 51px;
|
||||
$process-header-height: 105px;
|
||||
|
||||
.processInstance-wrap-main {
|
||||
height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
|
||||
);
|
||||
max-height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
|
||||
);
|
||||
overflow: auto;
|
||||
|
||||
.form-scroll-area {
|
||||
height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
|
||||
$process-header-height - 40px
|
||||
);
|
||||
max-height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
|
||||
$process-header-height - 40px
|
||||
);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.form-box {
|
||||
:deep(.el-card) {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,133 +1,115 @@
|
|||
<template>
|
||||
<doc-alert title="流程发起、取消、重新发起" url="https://doc.iocoder.cn/bpm/process-instance/" />
|
||||
|
||||
<!-- 第一步,通过流程定义的列表,选择对应的流程 -->
|
||||
<ContentWrap v-if="!selectProcessDefinition" v-loading="loading">
|
||||
<el-tabs tab-position="left" v-model="categoryActive">
|
||||
<el-tab-pane
|
||||
:label="category.name"
|
||||
:name="category.code"
|
||||
:key="category.code"
|
||||
v-for="category in categoryList"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col
|
||||
:lg="6"
|
||||
:sm="12"
|
||||
:xs="24"
|
||||
v-for="definition in categoryProcessDefinitionList"
|
||||
:key="definition.id"
|
||||
>
|
||||
<el-card
|
||||
shadow="hover"
|
||||
class="mb-20px cursor-pointer"
|
||||
@click="handleSelect(definition)"
|
||||
<template v-if="!selectProcessDefinition">
|
||||
<el-input
|
||||
v-model="searchName"
|
||||
class="!w-50% mb-15px"
|
||||
placeholder="请输入流程名称"
|
||||
clearable
|
||||
@input="handleQuery"
|
||||
@clear="handleQuery"
|
||||
>
|
||||
<template #prefix>
|
||||
<Icon icon="ep:search" />
|
||||
</template>
|
||||
</el-input>
|
||||
<ContentWrap
|
||||
:class="{ 'process-definition-container': filteredProcessDefinitionList?.length }"
|
||||
class="position-relative pb-20px h-700px"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-row v-if="filteredProcessDefinitionList?.length" :gutter="20" class="!flex-nowrap">
|
||||
<el-col :span="5">
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
v-for="category in availableCategories"
|
||||
:key="category.code"
|
||||
class="flex items-center p-10px cursor-pointer text-14px rounded-md"
|
||||
:class="categoryActive.code === category.code ? 'text-#3e7bff bg-#e8eeff' : ''"
|
||||
@click="handleCategoryClick(category)"
|
||||
>
|
||||
<template #default>
|
||||
<div class="flex">
|
||||
<el-image :src="definition.icon" class="w-32px h-32px" />
|
||||
<el-text class="!ml-10px" size="large">{{ definition.name }}</el-text>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</ContentWrap>
|
||||
{{ category.name }}
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="19">
|
||||
<el-scrollbar ref="scrollWrapper" height="700" @scroll="handleScroll">
|
||||
<div
|
||||
class="mb-20px pl-10px"
|
||||
v-for="(definitions, categoryCode) in processDefinitionGroup"
|
||||
:key="categoryCode"
|
||||
:ref="`category-${categoryCode}`"
|
||||
>
|
||||
<h3 class="text-18px font-bold mb-10px mt-5px">
|
||||
{{ getCategoryName(categoryCode as any) }}
|
||||
</h3>
|
||||
<div class="grid grid-cols-3 gap3">
|
||||
<el-tooltip
|
||||
v-for="definition in definitions"
|
||||
:key="definition.id"
|
||||
:content="definition.description"
|
||||
:disabled="!definition.description || definition.description.trim().length === 0"
|
||||
placement="top"
|
||||
>
|
||||
<el-card
|
||||
shadow="hover"
|
||||
class="cursor-pointer definition-item-card"
|
||||
@click="handleSelect(definition)"
|
||||
>
|
||||
<template #default>
|
||||
<div class="flex">
|
||||
<el-image :src="definition.icon" class="w-32px h-32px" />
|
||||
<el-text class="!ml-10px" size="large">{{ definition.name }}</el-text>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-empty class="!py-200px" :image-size="200" description="没有找到搜索结果" v-else />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<!-- 第二步,填写表单,进行流程的提交 -->
|
||||
<ContentWrap v-else>
|
||||
<el-card class="box-card">
|
||||
<div class="clearfix">
|
||||
<span class="el-icon-document">申请信息【{{ selectProcessDefinition.name }}】</span>
|
||||
<el-button style="float: right" type="primary" @click="selectProcessDefinition = undefined">
|
||||
<Icon icon="ep:delete" /> 选择其它流程
|
||||
</el-button>
|
||||
</div>
|
||||
<el-col :span="16" :offset="6" style="margin-top: 20px">
|
||||
<form-create
|
||||
:rule="detailForm.rule"
|
||||
v-model:api="fApi"
|
||||
v-model="detailForm.value"
|
||||
:option="detailForm.option"
|
||||
@submit="submitForm"
|
||||
>
|
||||
<template #type-startUserSelect>
|
||||
<el-col :span="24">
|
||||
<el-card class="mb-10px">
|
||||
<template #header>指定审批人</template>
|
||||
<el-form
|
||||
:model="startUserSelectAssignees"
|
||||
:rules="startUserSelectAssigneesFormRules"
|
||||
ref="startUserSelectAssigneesFormRef"
|
||||
>
|
||||
<el-form-item
|
||||
v-for="userTask in startUserSelectTasks"
|
||||
:key="userTask.id"
|
||||
:label="`任务【${userTask.name}】`"
|
||||
:prop="userTask.id"
|
||||
>
|
||||
<el-select
|
||||
v-model="startUserSelectAssignees[userTask.id]"
|
||||
multiple
|
||||
placeholder="请选择审批人"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.id"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</template>
|
||||
</form-create>
|
||||
</el-col>
|
||||
</el-card>
|
||||
<!-- 流程图预览 -->
|
||||
<ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML as any" />
|
||||
</ContentWrap>
|
||||
<ProcessDefinitionDetail
|
||||
v-else
|
||||
ref="processDefinitionDetailRef"
|
||||
:selectProcessDefinition="selectProcessDefinition"
|
||||
@cancel="selectProcessDefinition = undefined"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
||||
import ProcessDefinitionDetail from './ProcessDefinitionDetail.vue'
|
||||
import { groupBy } from 'lodash-es'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceCreate' })
|
||||
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
const route = useRoute() // 路由
|
||||
const { push, currentRoute } = useRouter() // 路由
|
||||
const message = useMessage() // 消息
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
|
||||
const processInstanceId = route.query.processInstanceId
|
||||
const searchName = ref('') // 当前搜索关键字
|
||||
const processInstanceId: any = route.query.processInstanceId // 流程实例编号。场景:重新发起时
|
||||
const loading = ref(true) // 加载中
|
||||
const categoryList = ref([]) // 分类的列表
|
||||
const categoryActive = ref('') // 选中的分类
|
||||
const categoryList: any = ref([]) // 分类的列表
|
||||
const categoryActive: any = ref({}) // 选中的分类
|
||||
const processDefinitionList = ref([]) // 流程定义的列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 流程分类
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
if (categoryList.value.length > 0) {
|
||||
categoryActive.value = categoryList.value[0].code
|
||||
}
|
||||
// 流程定义
|
||||
processDefinitionList.value = await DefinitionApi.getProcessDefinitionList({
|
||||
suspensionState: 1
|
||||
})
|
||||
// 所有流程分类数据
|
||||
await getCategoryList()
|
||||
// 所有流程定义数据
|
||||
await getProcessDefinitionList()
|
||||
|
||||
// 如果 processInstanceId 非空,说明是重新发起
|
||||
if (processInstanceId?.length > 0) {
|
||||
|
@ -137,7 +119,7 @@ const getList = async () => {
|
|||
return
|
||||
}
|
||||
const processDefinition = processDefinitionList.value.find(
|
||||
(item) => item.key == processInstance.processDefinition?.key
|
||||
(item: any) => item.key == processInstance.processDefinition?.key
|
||||
)
|
||||
if (!processDefinition) {
|
||||
message.error('重新发起流程失败,原因:流程定义不存在')
|
||||
|
@ -150,108 +132,168 @@ const getList = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
/** 选中分类对应的流程定义列表 */
|
||||
const categoryProcessDefinitionList = computed(() => {
|
||||
return processDefinitionList.value.filter((item) => item.category == categoryActive.value)
|
||||
/** 获取所有流程分类数据 */
|
||||
const getCategoryList = async () => {
|
||||
try {
|
||||
// 流程分类
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取所有流程定义数据 */
|
||||
const getProcessDefinitionList = async () => {
|
||||
try {
|
||||
// 流程定义
|
||||
processDefinitionList.value = await DefinitionApi.getProcessDefinitionList({
|
||||
suspensionState: 1
|
||||
})
|
||||
// 初始化过滤列表为全部流程定义
|
||||
filteredProcessDefinitionList.value = processDefinitionList.value
|
||||
|
||||
// 在获取完所有数据后,设置第一个有效分类为激活状态
|
||||
if (availableCategories.value.length > 0 && !categoryActive.value?.code) {
|
||||
categoryActive.value = availableCategories.value[0]
|
||||
}
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索流程 */
|
||||
const filteredProcessDefinitionList = ref([]) // 用于存储搜索过滤后的流程定义
|
||||
const handleQuery = () => {
|
||||
if (searchName.value.trim()) {
|
||||
// 如果有搜索关键字,进行过滤
|
||||
filteredProcessDefinitionList.value = processDefinitionList.value.filter(
|
||||
(definition: any) => definition.name.toLowerCase().includes(searchName.value.toLowerCase()) // 假设搜索依据是流程定义的名称
|
||||
)
|
||||
} else {
|
||||
// 如果没有搜索关键字,恢复所有数据
|
||||
filteredProcessDefinitionList.value = processDefinitionList.value
|
||||
}
|
||||
}
|
||||
|
||||
/** 流程定义的分组 */
|
||||
const processDefinitionGroup: any = computed(() => {
|
||||
if (!processDefinitionList.value?.length) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const grouped = groupBy(filteredProcessDefinitionList.value, 'category')
|
||||
// 按照 categoryList 的顺序重新组织数据
|
||||
const orderedGroup = {}
|
||||
categoryList.value.forEach((category: any) => {
|
||||
if (grouped[category.code]) {
|
||||
orderedGroup[category.code] = grouped[category.code]
|
||||
}
|
||||
})
|
||||
return orderedGroup
|
||||
})
|
||||
|
||||
// ========== 表单相关 ==========
|
||||
const fApi = ref<ApiAttrs>()
|
||||
const detailForm = ref({
|
||||
rule: [],
|
||||
option: {},
|
||||
value: {}
|
||||
}) // 流程表单详情
|
||||
const selectProcessDefinition = ref() // 选择的流程定义
|
||||
/** 左侧分类切换 */
|
||||
const handleCategoryClick = (category: any) => {
|
||||
categoryActive.value = category
|
||||
const categoryRef = proxy.$refs[`category-${category.code}`] // 获取点击分类对应的 DOM 元素
|
||||
if (categoryRef?.length) {
|
||||
const scrollWrapper = proxy.$refs.scrollWrapper // 获取右侧滚动容器
|
||||
const categoryOffsetTop = categoryRef[0].offsetTop
|
||||
|
||||
// 指定审批人
|
||||
const bpmnXML = ref(null) // BPMN 数据
|
||||
const startUserSelectTasks = ref([]) // 发起人需要选择审批人的用户任务列表
|
||||
const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
|
||||
const startUserSelectAssigneesFormRef = ref() // 发起人选择审批人的表单 Ref
|
||||
const startUserSelectAssigneesFormRules = ref({}) // 发起人选择审批人的表单 Rules
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
// 滚动到对应位置
|
||||
scrollWrapper.scrollTo({ top: categoryOffsetTop, behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
|
||||
/** 通过分类 code 获取对应的名称 */
|
||||
const getCategoryName = (categoryCode: string) => {
|
||||
return categoryList.value?.find((ctg: any) => ctg.code === categoryCode)?.name
|
||||
}
|
||||
|
||||
// ========== 表单相关 ==========
|
||||
const selectProcessDefinition = ref() // 选择的流程定义
|
||||
const processDefinitionDetailRef = ref()
|
||||
|
||||
/** 处理选择流程的按钮操作 **/
|
||||
const handleSelect = async (row, formVariables) => {
|
||||
const handleSelect = async (row, formVariables?) => {
|
||||
// 设置选择的流程
|
||||
selectProcessDefinition.value = row
|
||||
// 初始化流程定义详情
|
||||
await nextTick()
|
||||
processDefinitionDetailRef.value?.initProcessInfo(row, formVariables)
|
||||
}
|
||||
|
||||
// 重置指定审批人
|
||||
startUserSelectTasks.value = []
|
||||
startUserSelectAssignees.value = {}
|
||||
startUserSelectAssigneesFormRules.value = {}
|
||||
/** 处理滚动事件,和左侧分类联动 */
|
||||
const handleScroll = (e: any) => {
|
||||
// 直接使用事件对象获取滚动位置
|
||||
const scrollTop = e.scrollTop
|
||||
|
||||
// 情况一:流程表单
|
||||
if (row.formType == 10) {
|
||||
// 设置表单
|
||||
setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables)
|
||||
// 加载流程图
|
||||
const processDefinitionDetail = await DefinitionApi.getProcessDefinition(row.id)
|
||||
if (processDefinitionDetail) {
|
||||
bpmnXML.value = processDefinitionDetail.bpmnXml
|
||||
startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
|
||||
|
||||
// 设置指定审批人
|
||||
if (startUserSelectTasks.value?.length > 0) {
|
||||
detailForm.value.rule.push({
|
||||
type: 'startUserSelect',
|
||||
props: {
|
||||
title: '指定审批人'
|
||||
}
|
||||
})
|
||||
// 设置校验规则
|
||||
for (const userTask of startUserSelectTasks.value) {
|
||||
startUserSelectAssignees.value[userTask.id] = []
|
||||
startUserSelectAssigneesFormRules.value[userTask.id] = [
|
||||
{ required: true, message: '请选择审批人', trigger: 'blur' }
|
||||
]
|
||||
// 获取所有分类区域的位置信息
|
||||
const categoryPositions = categoryList.value
|
||||
.map((category: CategoryVO) => {
|
||||
const categoryRef = proxy.$refs[`category-${category.code}`]
|
||||
if (categoryRef?.[0]) {
|
||||
return {
|
||||
code: category.code,
|
||||
offsetTop: categoryRef[0].offsetTop,
|
||||
height: categoryRef[0].offsetHeight
|
||||
}
|
||||
// 加载用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
}
|
||||
return null
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
// 查找当前滚动位置对应的分类
|
||||
let currentCategory = categoryPositions[0]
|
||||
for (const position of categoryPositions) {
|
||||
// 为了更好的用户体验,可以添加一个缓冲区域(比如 50px)
|
||||
if (scrollTop >= position.offsetTop - 50) {
|
||||
currentCategory = position
|
||||
} else {
|
||||
break
|
||||
}
|
||||
// 情况二:业务表单
|
||||
} else if (row.formCustomCreatePath) {
|
||||
await push({
|
||||
path: row.formCustomCreatePath
|
||||
})
|
||||
// 这里暂时无需加载流程图,因为跳出到另外个 Tab;
|
||||
}
|
||||
|
||||
// 更新当前 active 的分类
|
||||
if (currentCategory && categoryActive.value.code !== currentCategory.code) {
|
||||
categoryActive.value = categoryList.value.find(
|
||||
(c: CategoryVO) => c.code === currentCategory.code
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = async (formData) => {
|
||||
if (!fApi.value || !selectProcessDefinition.value) {
|
||||
return
|
||||
}
|
||||
// 如果有指定审批人,需要校验
|
||||
if (startUserSelectTasks.value?.length > 0) {
|
||||
await startUserSelectAssigneesFormRef.value.validate()
|
||||
/** 过滤出有流程的分类列表。目的:只展示有流程的分类 */
|
||||
const availableCategories = computed(() => {
|
||||
if (!categoryList.value?.length || !processDefinitionGroup.value) {
|
||||
return []
|
||||
}
|
||||
|
||||
// 提交请求
|
||||
fApi.value.btn.loading(true)
|
||||
try {
|
||||
await ProcessInstanceApi.createProcessInstance({
|
||||
processDefinitionId: selectProcessDefinition.value.id,
|
||||
variables: formData,
|
||||
startUserSelectAssignees: startUserSelectAssignees.value
|
||||
})
|
||||
// 提示
|
||||
message.success('发起流程成功')
|
||||
// 跳转回去
|
||||
delView(unref(currentRoute))
|
||||
await push({
|
||||
name: 'BpmProcessInstanceMy'
|
||||
})
|
||||
} finally {
|
||||
fApi.value.btn.loading(false)
|
||||
}
|
||||
}
|
||||
// 获取所有有流程的分类代码
|
||||
const availableCategoryCodes = Object.keys(processDefinitionGroup.value)
|
||||
|
||||
// 过滤出有流程的分类
|
||||
return categoryList.value.filter((category: CategoryVO) =>
|
||||
availableCategoryCodes.includes(category.code)
|
||||
)
|
||||
})
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.process-definition-container::before {
|
||||
content: '';
|
||||
border-left: 1px solid #e6e6e6;
|
||||
position: absolute;
|
||||
left: 20.8%;
|
||||
height: 100%;
|
||||
}
|
||||
:deep() {
|
||||
.definition-item-card {
|
||||
.el-card__body {
|
||||
padding: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,267 @@
|
|||
<template>
|
||||
<doc-alert title="流程发起、取消、重新发起" url="https://doc.iocoder.cn/bpm/process-instance/" />
|
||||
|
||||
<!-- 第一步,通过流程定义的列表,选择对应的流程 -->
|
||||
<ContentWrap v-if="!selectProcessDefinition" v-loading="loading">
|
||||
<el-tabs tab-position="left" v-model="categoryActive">
|
||||
<el-tab-pane
|
||||
:label="category.name"
|
||||
:name="category.code"
|
||||
:key="category.code"
|
||||
v-for="category in categoryList"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col
|
||||
:lg="6"
|
||||
:sm="12"
|
||||
:xs="24"
|
||||
v-for="definition in categoryProcessDefinitionList"
|
||||
:key="definition.id"
|
||||
>
|
||||
<el-card
|
||||
shadow="hover"
|
||||
class="mb-20px cursor-pointer"
|
||||
@click="handleSelect(definition)"
|
||||
>
|
||||
<template #default>
|
||||
<div class="flex">
|
||||
<el-image :src="definition.icon" class="w-32px h-32px" />
|
||||
<el-text class="!ml-10px" size="large">{{ definition.name }}</el-text>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 第二步,填写表单,进行流程的提交 -->
|
||||
<ContentWrap v-else>
|
||||
<el-card class="box-card">
|
||||
<div class="clearfix">
|
||||
<span class="el-icon-document">申请信息【{{ selectProcessDefinition.name }}】</span>
|
||||
<el-button style="float: right" type="primary" @click="selectProcessDefinition = undefined">
|
||||
<Icon icon="ep:delete" /> 选择其它流程
|
||||
</el-button>
|
||||
</div>
|
||||
<el-col :span="16" :offset="6" style="margin-top: 20px">
|
||||
<form-create
|
||||
:rule="detailForm.rule"
|
||||
v-model:api="fApi"
|
||||
v-model="detailForm.value"
|
||||
:option="detailForm.option"
|
||||
@submit="submitForm"
|
||||
>
|
||||
<template #type-startUserSelect>
|
||||
<el-col :span="24">
|
||||
<el-card class="mb-10px">
|
||||
<template #header>指定审批人</template>
|
||||
<el-form
|
||||
:model="startUserSelectAssignees"
|
||||
:rules="startUserSelectAssigneesFormRules"
|
||||
ref="startUserSelectAssigneesFormRef"
|
||||
>
|
||||
<el-form-item
|
||||
v-for="userTask in startUserSelectTasks"
|
||||
:key="userTask.id"
|
||||
:label="`任务【${userTask.name}】`"
|
||||
:prop="userTask.id"
|
||||
>
|
||||
<el-select
|
||||
v-model="startUserSelectAssignees[userTask.id]"
|
||||
multiple
|
||||
placeholder="请选择审批人"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.id"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</template>
|
||||
</form-create>
|
||||
</el-col>
|
||||
</el-card>
|
||||
<!-- 流程图预览 -->
|
||||
<ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML as any" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import { decodeFields, setConfAndFields2 } from '@/utils/formCreate'
|
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceCreate' })
|
||||
|
||||
const route = useRoute() // 路由
|
||||
const { push, currentRoute } = useRouter() // 路由
|
||||
const message = useMessage() // 消息
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
|
||||
const processInstanceId = route.query.processInstanceId
|
||||
const loading = ref(true) // 加载中
|
||||
const categoryList = ref([]) // 分类的列表
|
||||
const categoryActive = ref('') // 选中的分类
|
||||
const processDefinitionList = ref([]) // 流程定义的列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 流程分类
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
if (categoryList.value.length > 0) {
|
||||
categoryActive.value = categoryList.value[0].code
|
||||
}
|
||||
// 流程定义
|
||||
processDefinitionList.value = await DefinitionApi.getProcessDefinitionList({
|
||||
suspensionState: 1
|
||||
})
|
||||
|
||||
// 如果 processInstanceId 非空,说明是重新发起
|
||||
if (processInstanceId?.length > 0) {
|
||||
const processInstance = await ProcessInstanceApi.getProcessInstance(processInstanceId)
|
||||
if (!processInstance) {
|
||||
message.error('重新发起流程失败,原因:流程实例不存在')
|
||||
return
|
||||
}
|
||||
const processDefinition = processDefinitionList.value.find(
|
||||
(item) => item.key == processInstance.processDefinition?.key
|
||||
)
|
||||
if (!processDefinition) {
|
||||
message.error('重新发起流程失败,原因:流程定义不存在')
|
||||
return
|
||||
}
|
||||
await handleSelect(processDefinition, processInstance.formVariables)
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 选中分类对应的流程定义列表 */
|
||||
const categoryProcessDefinitionList = computed(() => {
|
||||
return processDefinitionList.value.filter((item) => item.category == categoryActive.value)
|
||||
})
|
||||
|
||||
// ========== 表单相关 ==========
|
||||
const fApi = ref<ApiAttrs>()
|
||||
const detailForm = ref({
|
||||
rule: [],
|
||||
option: {},
|
||||
value: {}
|
||||
}) // 流程表单详情
|
||||
const selectProcessDefinition = ref() // 选择的流程定义
|
||||
|
||||
// 指定审批人
|
||||
const bpmnXML = ref(null) // BPMN 数据
|
||||
const startUserSelectTasks = ref([]) // 发起人需要选择审批人的用户任务列表
|
||||
const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
|
||||
const startUserSelectAssigneesFormRef = ref() // 发起人选择审批人的表单 Ref
|
||||
const startUserSelectAssigneesFormRules = ref({}) // 发起人选择审批人的表单 Rules
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
|
||||
/** 处理选择流程的按钮操作 **/
|
||||
const handleSelect = async (row, formVariables) => {
|
||||
// 设置选择的流程
|
||||
selectProcessDefinition.value = row
|
||||
|
||||
// 重置指定审批人
|
||||
startUserSelectTasks.value = []
|
||||
startUserSelectAssignees.value = {}
|
||||
startUserSelectAssigneesFormRules.value = {}
|
||||
|
||||
// 情况一:流程表单
|
||||
if (row.formType == 10) {
|
||||
// 设置表单
|
||||
// 注意:需要从 formVariables 中,移除不在 row.formFields 的值。
|
||||
// 原因是:后端返回的 formVariables 里面,会有一些非表单的信息。例如说,某个流程节点的审批人。
|
||||
// 这样,就可能导致一个流程被审批不通过后,重新发起时,会直接后端报错!!!
|
||||
const allowedFields = decodeFields(row.formFields).map((fieldObj: any) => fieldObj.field)
|
||||
for (const key in formVariables) {
|
||||
if (!allowedFields.includes(key)) {
|
||||
delete formVariables[key]
|
||||
}
|
||||
}
|
||||
setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables)
|
||||
|
||||
// 加载流程图
|
||||
const processDefinitionDetail = await DefinitionApi.getProcessDefinition(row.id)
|
||||
if (processDefinitionDetail) {
|
||||
bpmnXML.value = processDefinitionDetail.bpmnXml
|
||||
startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
|
||||
|
||||
// 设置指定审批人
|
||||
if (startUserSelectTasks.value?.length > 0) {
|
||||
detailForm.value.rule.push({
|
||||
type: 'startUserSelect',
|
||||
props: {
|
||||
title: '指定审批人'
|
||||
}
|
||||
})
|
||||
// 设置校验规则
|
||||
for (const userTask of startUserSelectTasks.value) {
|
||||
startUserSelectAssignees.value[userTask.id] = []
|
||||
startUserSelectAssigneesFormRules.value[userTask.id] = [
|
||||
{ required: true, message: '请选择审批人', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
// 加载用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
}
|
||||
}
|
||||
// 情况二:业务表单
|
||||
} else if (row.formCustomCreatePath) {
|
||||
await push({
|
||||
path: row.formCustomCreatePath
|
||||
})
|
||||
// 这里暂时无需加载流程图,因为跳出到另外个 Tab;
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = async (formData) => {
|
||||
if (!fApi.value || !selectProcessDefinition.value) {
|
||||
return
|
||||
}
|
||||
// 如果有指定审批人,需要校验
|
||||
if (startUserSelectTasks.value?.length > 0) {
|
||||
await startUserSelectAssigneesFormRef.value.validate()
|
||||
}
|
||||
|
||||
// 提交请求
|
||||
fApi.value.btn.loading(true)
|
||||
try {
|
||||
await ProcessInstanceApi.createProcessInstance({
|
||||
processDefinitionId: selectProcessDefinition.value.id,
|
||||
variables: formData,
|
||||
startUserSelectAssignees: startUserSelectAssignees.value
|
||||
})
|
||||
// 提示
|
||||
message.success('发起流程成功')
|
||||
// 跳转回去
|
||||
delView(unref(currentRoute))
|
||||
await push({
|
||||
name: 'BpmProcessInstanceMy'
|
||||
})
|
||||
} finally {
|
||||
fApi.value.btn.loading(false)
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -1,54 +1,61 @@
|
|||
<template>
|
||||
<el-card v-loading="loading" class="box-card">
|
||||
<template #header>
|
||||
<span class="el-icon-picture-outline">流程图</span>
|
||||
</template>
|
||||
<MyProcessViewer
|
||||
key="designer"
|
||||
:activityData="activityList"
|
||||
:prefix="bpmnControlForm.prefix"
|
||||
:processInstanceData="processInstance"
|
||||
:taskData="tasks"
|
||||
:value="bpmnXml"
|
||||
v-bind="bpmnControlForm"
|
||||
/>
|
||||
<MyProcessViewer key="designer" :xml="view.bpmnXml" :view="view" class="process-viewer" />
|
||||
</el-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
|
||||
import * as ActivityApi from '@/api/bpm/activity'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceBpmnViewer' })
|
||||
|
||||
const props = defineProps({
|
||||
loading: propTypes.bool, // 是否加载中
|
||||
id: propTypes.string, // 流程实例的编号
|
||||
processInstance: propTypes.any, // 流程实例的信息
|
||||
tasks: propTypes.array, // 流程任务的数组
|
||||
bpmnXml: propTypes.string // BPMN XML
|
||||
loading: propTypes.bool.def(false), // 是否加载中
|
||||
bpmnXml: propTypes.string, // BPMN XML
|
||||
modelView: propTypes.object
|
||||
})
|
||||
|
||||
const bpmnControlForm = ref({
|
||||
prefix: 'flowable'
|
||||
})
|
||||
const activityList = ref([]) // 任务列表
|
||||
const view = ref({
|
||||
bpmnXml: ''
|
||||
}) // BPMN 流程图数据
|
||||
|
||||
|
||||
/** 只有 loading 完成时,才去加载流程列表 */
|
||||
watch(
|
||||
() => props.loading,
|
||||
async (value) => {
|
||||
if (value && props.id) {
|
||||
activityList.value = await ActivityApi.getActivityList({
|
||||
processInstanceId: props.id
|
||||
})
|
||||
() => props.modelView,
|
||||
async (newModelView) => {
|
||||
// 加载最新
|
||||
if (newModelView) {
|
||||
//@ts-ignore
|
||||
view.value = newModelView
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/** 监听 bpmnXml */
|
||||
watch(
|
||||
() => props.bpmnXml,
|
||||
(value) => {
|
||||
view.value.bpmnXml = value
|
||||
}
|
||||
)
|
||||
</script>
|
||||
<style>
|
||||
<style lang="scss" scoped>
|
||||
.box-card {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 0;
|
||||
|
||||
:deep(.el-card__body) {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.process-viewer) {
|
||||
height: 100% !important;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
<template>
|
||||
<div v-loading="loading" class="process-viewer-container">
|
||||
<SimpleProcessViewer
|
||||
:flow-node="simpleModel"
|
||||
:tasks="tasks"
|
||||
:process-instance="processInstance"
|
||||
class="process-viewer"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { TaskStatusEnum } from '@/api/bpm/task'
|
||||
import { SimpleFlowNode, NodeType } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import { SimpleProcessViewer } from '@/components/SimpleProcessDesignerV2/src/'
|
||||
defineOptions({ name: 'BpmProcessInstanceSimpleViewer' })
|
||||
|
||||
const props = defineProps({
|
||||
loading: propTypes.bool.def(false), // 是否加载中
|
||||
modelView: propTypes.object,
|
||||
simpleJson: propTypes.string // Simple 模型结构数据 (json 格式)
|
||||
})
|
||||
const simpleModel = ref()
|
||||
// 用户任务
|
||||
const tasks = ref([])
|
||||
// 流程实例
|
||||
const processInstance = ref()
|
||||
|
||||
/** 监控模型视图 包括任务列表、进行中的活动节点编号等 */
|
||||
watch(
|
||||
() => props.modelView,
|
||||
async (newModelView) => {
|
||||
if (newModelView) {
|
||||
tasks.value = newModelView.tasks
|
||||
processInstance.value = newModelView.processInstance
|
||||
// 已经拒绝的活动节点编号集合,只包括 UserTask
|
||||
const rejectedTaskActivityIds: string[] = newModelView.rejectedTaskActivityIds
|
||||
// 进行中的活动节点编号集合, 只包括 UserTask
|
||||
const unfinishedTaskActivityIds: string[] = newModelView.unfinishedTaskActivityIds
|
||||
// 已经完成的活动节点编号集合, 包括 UserTask、Gateway 等
|
||||
const finishedActivityIds: string[] = newModelView.finishedTaskActivityIds
|
||||
// 已经完成的连线节点编号集合,只包括 SequenceFlow
|
||||
const finishedSequenceFlowActivityIds: string[] = newModelView.finishedSequenceFlowActivityIds
|
||||
setSimpleModelNodeTaskStatus(
|
||||
newModelView.simpleModel,
|
||||
newModelView.processInstance.status,
|
||||
rejectedTaskActivityIds,
|
||||
unfinishedTaskActivityIds,
|
||||
finishedActivityIds,
|
||||
finishedSequenceFlowActivityIds
|
||||
)
|
||||
simpleModel.value = newModelView.simpleModel
|
||||
}
|
||||
}
|
||||
)
|
||||
/** 监控模型结构数据 */
|
||||
watch(
|
||||
() => props.simpleJson,
|
||||
async (value) => {
|
||||
if (value) {
|
||||
simpleModel.value = JSON.parse(value)
|
||||
}
|
||||
}
|
||||
)
|
||||
const setSimpleModelNodeTaskStatus = (
|
||||
simpleModel: SimpleFlowNode | undefined,
|
||||
processStatus: number,
|
||||
rejectedTaskActivityIds: string[],
|
||||
unfinishedTaskActivityIds: string[],
|
||||
finishedActivityIds: string[],
|
||||
finishedSequenceFlowActivityIds: string[]
|
||||
) => {
|
||||
if (!simpleModel) {
|
||||
return
|
||||
}
|
||||
// 结束节点
|
||||
if (simpleModel.type === NodeType.END_EVENT_NODE) {
|
||||
if (finishedActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = processStatus
|
||||
} else {
|
||||
simpleModel.activityStatus = TaskStatusEnum.NOT_START
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 审批节点
|
||||
if (
|
||||
simpleModel.type === NodeType.START_USER_NODE ||
|
||||
simpleModel.type === NodeType.USER_TASK_NODE
|
||||
) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.NOT_START
|
||||
if (rejectedTaskActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.REJECT
|
||||
} else if (unfinishedTaskActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.RUNNING
|
||||
} else if (finishedActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.APPROVE
|
||||
}
|
||||
// TODO 是不是还缺一个 cancel 的状态
|
||||
}
|
||||
|
||||
// 抄送节点
|
||||
if (simpleModel.type === NodeType.COPY_TASK_NODE) {
|
||||
// 抄送节点 只有通过和未执行状态
|
||||
if (finishedActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.APPROVE
|
||||
} else {
|
||||
simpleModel.activityStatus = TaskStatusEnum.NOT_START
|
||||
}
|
||||
}
|
||||
// 条件节点 对应 SequenceFlow
|
||||
if (simpleModel.type === NodeType.CONDITION_NODE) {
|
||||
// 条件节点。只有通过和未执行状态
|
||||
if (finishedSequenceFlowActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.APPROVE
|
||||
} else {
|
||||
simpleModel.activityStatus = TaskStatusEnum.NOT_START
|
||||
}
|
||||
}
|
||||
|
||||
// 网关节点
|
||||
if (
|
||||
simpleModel.type === NodeType.CONDITION_BRANCH_NODE ||
|
||||
simpleModel.type === NodeType.PARALLEL_BRANCH_NODE ||
|
||||
simpleModel.type === NodeType.INCLUSIVE_BRANCH_NODE
|
||||
) {
|
||||
// 网关节点。只有通过和未执行状态
|
||||
if (finishedActivityIds.includes(simpleModel.id)) {
|
||||
simpleModel.activityStatus = TaskStatusEnum.APPROVE
|
||||
} else {
|
||||
simpleModel.activityStatus = TaskStatusEnum.NOT_START
|
||||
}
|
||||
simpleModel.conditionNodes?.forEach((node) => {
|
||||
setSimpleModelNodeTaskStatus(
|
||||
node,
|
||||
processStatus,
|
||||
rejectedTaskActivityIds,
|
||||
unfinishedTaskActivityIds,
|
||||
finishedActivityIds,
|
||||
finishedSequenceFlowActivityIds
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
setSimpleModelNodeTaskStatus(
|
||||
simpleModel.childNode,
|
||||
processStatus,
|
||||
rejectedTaskActivityIds,
|
||||
unfinishedTaskActivityIds,
|
||||
finishedActivityIds,
|
||||
finishedSequenceFlowActivityIds
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.process-viewer-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
:deep(.process-viewer) {
|
||||
height: 100% !important;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,85 +1,50 @@
|
|||
<template>
|
||||
<el-card v-loading="loading" class="box-card">
|
||||
<template #header>
|
||||
<span class="el-icon-picture-outline">审批记录</span>
|
||||
</template>
|
||||
<el-col :offset="3" :span="17">
|
||||
<div class="block">
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-if="processInstance.endTime"
|
||||
:type="getProcessInstanceTimelineItemType(processInstance)"
|
||||
>
|
||||
<p style="font-weight: 700">
|
||||
结束流程:在 {{ formatDate(processInstance?.endTime) }} 结束
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS"
|
||||
:value="processInstance.status"
|
||||
/>
|
||||
</p>
|
||||
</el-timeline-item>
|
||||
<el-timeline-item
|
||||
v-for="(item, index) in tasks"
|
||||
:key="index"
|
||||
:type="getTaskTimelineItemType(item)"
|
||||
>
|
||||
<p style="font-weight: 700">
|
||||
审批任务:{{ item.name }}
|
||||
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="item.status" />
|
||||
<el-button
|
||||
class="ml-10px"
|
||||
v-if="!isEmpty(item.children)"
|
||||
@click="openChildrenTask(item)"
|
||||
size="small"
|
||||
>
|
||||
<Icon icon="ep:memo" /> 子任务
|
||||
</el-button>
|
||||
<el-button
|
||||
class="ml-10px"
|
||||
size="small"
|
||||
v-if="item.formId > 0"
|
||||
@click="handleFormDetail(item)"
|
||||
>
|
||||
<Icon icon="ep:document" /> 查看表单
|
||||
</el-button>
|
||||
</p>
|
||||
<el-card :body-style="{ padding: '10px' }">
|
||||
<label v-if="item.assigneeUser" style="margin-right: 30px; font-weight: normal">
|
||||
审批人:{{ item.assigneeUser.nickname }}
|
||||
<el-tag size="small" type="info">{{ item.assigneeUser.deptName }}</el-tag>
|
||||
</label>
|
||||
<label v-if="item.createTime" style="font-weight: normal">创建时间:</label>
|
||||
<label style="font-weight: normal; color: #8a909c">
|
||||
{{ formatDate(item?.createTime) }}
|
||||
</label>
|
||||
<label v-if="item.endTime" style="margin-left: 30px; font-weight: normal">
|
||||
审批时间:
|
||||
</label>
|
||||
<label v-if="item.endTime" style="font-weight: normal; color: #8a909c">
|
||||
{{ formatDate(item?.endTime) }}
|
||||
</label>
|
||||
<label v-if="item.durationInMillis" style="margin-left: 30px; font-weight: normal">
|
||||
耗时:
|
||||
</label>
|
||||
<label v-if="item.durationInMillis" style="font-weight: normal; color: #8a909c">
|
||||
{{ formatPast2(item?.durationInMillis) }}
|
||||
</label>
|
||||
<p v-if="item.reason"> 审批建议:{{ item.reason }} </p>
|
||||
</el-card>
|
||||
</el-timeline-item>
|
||||
<el-timeline-item type="success">
|
||||
<p style="font-weight: 700">
|
||||
发起流程:【{{ processInstance.startUser?.nickname }}】在
|
||||
{{ formatDate(processInstance?.startTime) }} 发起【 {{ processInstance.name }} 】流程
|
||||
</p>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-card>
|
||||
<el-table :data="tasks" border header-cell-class-name="table-header-gray">
|
||||
<el-table-column label="审批节点" prop="name" min-width="120" align="center" />
|
||||
<el-table-column label="审批人" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.nickname || scope.row.ownerUser?.nickname }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="开始时间"
|
||||
prop="createTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="结束时间"
|
||||
prop="endTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column align="center" label="审批状态" prop="status" min-width="90">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="审批建议" prop="reason" min-width="200">
|
||||
<template #default="scope">
|
||||
{{ scope.row.reason }}
|
||||
<el-button
|
||||
class="ml-10px"
|
||||
size="small"
|
||||
v-if="scope.row.formId > 0"
|
||||
@click="handleFormDetail(scope.row)"
|
||||
>
|
||||
<Icon icon="ep:document" /> 查看表单
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" min-width="100">
|
||||
<template #default="scope">
|
||||
{{ formatPast2(scope.row.durationInMillis) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 弹窗:子任务 -->
|
||||
<TaskSignList ref="taskSignListRef" @success="refresh" />
|
||||
<!-- 弹窗:表单 -->
|
||||
<Dialog title="表单详情" v-model="taskFormVisible" width="600">
|
||||
<form-create
|
||||
|
@ -91,61 +56,20 @@
|
|||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { formatDate, formatPast2 } from '@/utils/formatTime'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import TaskSignList from './dialog/TaskSignList.vue'
|
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceTaskList' })
|
||||
|
||||
defineProps({
|
||||
loading: propTypes.bool, // 是否加载中
|
||||
processInstance: propTypes.object, // 流程实例
|
||||
tasks: propTypes.arrayOf(propTypes.object) // 流程任务的数组
|
||||
const props = defineProps({
|
||||
loading: propTypes.bool.def(false), // 是否加载中
|
||||
id: propTypes.string // 流程实例的编号
|
||||
})
|
||||
|
||||
/** 获得流程实例对应的颜色 */
|
||||
const getProcessInstanceTimelineItemType = (item: any) => {
|
||||
if (item.status === 2) {
|
||||
return 'success'
|
||||
}
|
||||
if (item.status === 3) {
|
||||
return 'danger'
|
||||
}
|
||||
if (item.status === 4) {
|
||||
return 'warning'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/** 获得任务对应的颜色 */
|
||||
const getTaskTimelineItemType = (item: any) => {
|
||||
if ([0, 1, 6, 7].includes(item.status)) {
|
||||
return 'primary'
|
||||
}
|
||||
if (item.status === 2) {
|
||||
return 'success'
|
||||
}
|
||||
if (item.status === 3) {
|
||||
return 'danger'
|
||||
}
|
||||
if (item.status === 4) {
|
||||
return 'info'
|
||||
}
|
||||
if (item.status === 5) {
|
||||
return 'warning'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/** 子任务 */
|
||||
const taskSignListRef = ref()
|
||||
const openChildrenTask = (item: any) => {
|
||||
taskSignListRef.value.open(item)
|
||||
}
|
||||
const tasks = ref([]) // 流程任务的数组
|
||||
|
||||
/** 查看表单 */
|
||||
const fApi = ref<ApiAttrs>() // form-create 的 API 操作类
|
||||
|
@ -155,7 +79,7 @@ const taskForm = ref({
|
|||
value: {}
|
||||
}) // 流程任务的表单详情
|
||||
const taskFormVisible = ref(false)
|
||||
const handleFormDetail = async (row) => {
|
||||
const handleFormDetail = async (row: any) => {
|
||||
// 设置表单
|
||||
setConfAndFields2(taskForm, row.formConf, row.formFields, row.formVariables)
|
||||
// 弹窗打开
|
||||
|
@ -167,9 +91,13 @@ const handleFormDetail = async (row) => {
|
|||
fApi.value?.fapi?.disabled(true)
|
||||
}
|
||||
|
||||
/** 刷新数据 */
|
||||
const emit = defineEmits(['refresh']) // 定义 success 事件,用于操作成功后的回调
|
||||
const refresh = () => {
|
||||
emit('refresh')
|
||||
}
|
||||
/** 只有 loading 完成时,才去加载流程列表 */
|
||||
watch(
|
||||
() => props.loading,
|
||||
async (value) => {
|
||||
if (value) {
|
||||
tasks.value = await TaskApi.getTaskListByProcessInstanceId(props.id)
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
|
|
@ -3,155 +3,189 @@
|
|||
<el-timeline class="pt-20px">
|
||||
<!-- 遍历每个审批节点 -->
|
||||
<el-timeline-item
|
||||
v-for="(activity, index) in approveNodes"
|
||||
v-for="(activity, index) in activityNodes"
|
||||
:key="index"
|
||||
size="large"
|
||||
:icon="getApprovalNodeIcon(activity.status, activity.nodeType)"
|
||||
:color="getApprovalNodeColor(activity.status)"
|
||||
>
|
||||
<div class="flex flex-col items-start">
|
||||
<div class="font-bold"> {{ activity.name }}</div>
|
||||
<div class="flex items-center mt-1">
|
||||
<template #dot>
|
||||
<div
|
||||
class="position-absolute left--10px top--6px rounded-full border border-solid border-#dedede w-30px h-30px flex justify-center items-center bg-#3f73f7 p-5px"
|
||||
>
|
||||
<img class="w-full h-full" :src="getApprovalNodeImg(activity.nodeType)" alt="" />
|
||||
<div
|
||||
v-if="showStatusIcon"
|
||||
class="position-absolute top-17px left-17px rounded-full flex items-center p-1px border-2 border-white border-solid"
|
||||
:style="{ backgroundColor: getApprovalNodeColor(activity.status) }"
|
||||
>
|
||||
<el-icon :size="11" color="#fff">
|
||||
<component :is="getApprovalNodeIcon(activity.status, activity.nodeType)" />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex flex-col items-start gap2" :id="`activity-task-${activity.id}`">
|
||||
<!-- 第一行:节点名称、时间 -->
|
||||
<div class="flex w-full">
|
||||
<div class="font-bold"> {{ activity.name }}</div>
|
||||
<!-- 信息:时间 -->
|
||||
<div
|
||||
v-if="activity.status !== TaskStatusEnum.NOT_START"
|
||||
class="text-#a5a5a5 text-13px mt-1 ml-auto"
|
||||
>
|
||||
{{ getApprovalNodeTime(activity) }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 需要自定义选择审批人 -->
|
||||
<div
|
||||
class="flex flex-wrap gap2 items-center"
|
||||
v-if="
|
||||
isEmpty(activity.tasks) &&
|
||||
isEmpty(activity.candidateUsers) &&
|
||||
CandidateStrategy.START_USER_SELECT === activity.candidateStrategy
|
||||
"
|
||||
>
|
||||
<!-- && activity.nodeType === NodeType.USER_TASK_NODE -->
|
||||
|
||||
<el-tooltip content="添加用户" placement="left">
|
||||
<el-button
|
||||
class="!px-6px"
|
||||
@click="handleSelectUser(activity.id, customApproveUsers[activity.id])"
|
||||
>
|
||||
<img class="w-18px text-#ccc" src="@/assets/svgs/bpm/add-user.svg" alt="" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<div
|
||||
v-for="(user, idx1) in customApproveUsers[activity.id]"
|
||||
:key="idx1"
|
||||
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
|
||||
>
|
||||
<el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
|
||||
<el-avatar class="!m-5px" :size="28" v-else>
|
||||
{{ user.nickname.substring(0, 1) }}
|
||||
</el-avatar>
|
||||
{{ user.nickname }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex items-center flex-wrap mt-1 gap2">
|
||||
<!-- 情况一:遍历每个审批节点下的【进行中】task 任务 -->
|
||||
<div v-for="(task, idx) in activity.tasks" :key="idx" class="flex items-center">
|
||||
<div class="flex items-center flex-col pr-2">
|
||||
<div class="position-relative" v-if="task.assigneeUser || task.ownerUser">
|
||||
<!-- 信息:头像 -->
|
||||
<el-avatar
|
||||
:size="36"
|
||||
v-if="task.assigneeUser && task.assigneeUser.avatar"
|
||||
:src="task.assigneeUser.avatar"
|
||||
/>
|
||||
<el-avatar v-else-if="task.assigneeUser && task.assigneeUser.nickname">
|
||||
{{ task.assigneeUser.nickname.substring(0, 1) }}
|
||||
</el-avatar>
|
||||
<el-avatar
|
||||
v-else-if="task.ownerUser && task.ownerUser.avatar"
|
||||
:src="task.ownerUser.avatar"
|
||||
/>
|
||||
<el-avatar v-else-if="task.ownerUser && task.ownerUser.nickname">
|
||||
{{ task.ownerUser.nickname.substring(0, 1) }}
|
||||
</el-avatar>
|
||||
<div v-for="(task, idx) in activity.tasks" :key="idx" class="flex flex-col pr-2 gap2">
|
||||
<div
|
||||
class="position-relative flex flex-wrap gap2"
|
||||
v-if="task.assigneeUser || task.ownerUser"
|
||||
>
|
||||
<!-- 信息:头像昵称 -->
|
||||
<div
|
||||
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
|
||||
>
|
||||
<template v-if="task.assigneeUser?.avatar || task.assigneeUser?.nickname">
|
||||
<el-avatar
|
||||
class="!m-5px"
|
||||
:size="28"
|
||||
v-if="task.assigneeUser?.avatar"
|
||||
:src="task.assigneeUser?.avatar"
|
||||
/>
|
||||
<el-avatar class="!m-5px" :size="28" v-else>
|
||||
{{ task.assigneeUser?.nickname.substring(0, 1) }}
|
||||
</el-avatar>
|
||||
{{ task.assigneeUser?.nickname }}
|
||||
</template>
|
||||
<template v-else-if="task.ownerUser?.avatar || task.ownerUser?.nickname">
|
||||
<el-avatar
|
||||
class="!m-5px"
|
||||
:size="28"
|
||||
v-if="task.ownerUser?.avatar"
|
||||
:src="task.ownerUser?.avatar"
|
||||
/>
|
||||
<el-avatar class="!m-5px" :size="28" v-else>
|
||||
{{ task.ownerUser?.nickname.substring(0, 1) }}
|
||||
</el-avatar>
|
||||
{{ task.ownerUser?.nickname }}
|
||||
</template>
|
||||
<!-- 信息:任务 ICON -->
|
||||
<div
|
||||
class="position-absolute top-26px left-26px bg-#fff rounded-full flex items-center p-2px"
|
||||
v-if="showStatusIcon && onlyStatusIconShow.includes(task.status)"
|
||||
class="position-absolute top-19px left-23px rounded-full flex items-center p-1px border-2 border-white border-solid"
|
||||
:style="{ backgroundColor: statusIconMap2[task.status]?.color }"
|
||||
>
|
||||
<Icon
|
||||
:size="12"
|
||||
:icon="statusIconMap2[task.status]?.icon"
|
||||
:color="statusIconMap2[task.status]?.color"
|
||||
/>
|
||||
<Icon :size="11" :icon="statusIconMap2[task.status]?.icon" color="#FFFFFF" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col mt-1">
|
||||
<!-- 信息:昵称 -->
|
||||
<div
|
||||
v-if="task.assigneeUser && task.assigneeUser.nickname"
|
||||
class="text-10px text-align-center"
|
||||
>
|
||||
{{ task.assigneeUser.nickname }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="task.ownerUser && task.ownerUser.nickname"
|
||||
class="text-10px text-align-center"
|
||||
>
|
||||
{{ task.ownerUser.nickname }}
|
||||
</div>
|
||||
<!-- TODO @jason:审批意见,要展示哈。 -->
|
||||
<!-- <div v-if="task.reason" :title="task.reason" class="text-13px text-truncate w-150px mt-1"> 审批意见: {{ task.reason }}</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<teleport defer :to="`#activity-task-${activity.id}`">
|
||||
<div
|
||||
v-if="
|
||||
task.reason &&
|
||||
[NodeType.USER_TASK_NODE, NodeType.END_EVENT_NODE].includes(activity.nodeType)
|
||||
"
|
||||
class="text-#a5a5a5 text-13px mt-1 w-full bg-#f8f8fa p2 rounded-md"
|
||||
>
|
||||
审批意见:{{ task.reason }}
|
||||
</div>
|
||||
</teleport>
|
||||
</div>
|
||||
<!-- 情况二:遍历每个审批节点下的【候选的】task 任务。例如说,1)依次审批,2)未来的审批任务等 -->
|
||||
<div
|
||||
v-for="(user, idx1) in activity.candidateUserList"
|
||||
v-for="(user, idx1) in activity.candidateUsers"
|
||||
:key="idx1"
|
||||
class="flex items-center"
|
||||
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
|
||||
>
|
||||
<div class="flex items-center flex-col pr-2">
|
||||
<div class="position-relative">
|
||||
<!-- 信息:头像 -->
|
||||
<el-avatar :size="36" v-if="user.avatar" :src="user.avatar" />
|
||||
<el-avatar v-else-if="user.nickname && user.nickname">
|
||||
{{ user.nickname.substring(0, 1) }}
|
||||
</el-avatar>
|
||||
<!-- 信息:任务 ICON -->
|
||||
<div
|
||||
class="position-absolute top-26px left-26px bg-#fff rounded-full flex items-center p-2px"
|
||||
>
|
||||
<Icon
|
||||
:size="12"
|
||||
:icon="statusIconMap2['-1']?.icon"
|
||||
:color="statusIconMap2['-1']?.color"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col mt-1">
|
||||
<!-- 信息:昵称 -->
|
||||
<div v-if="user.nickname" class="text-10px text-align-center">
|
||||
{{ user.nickname }}
|
||||
</div>
|
||||
<!-- <div v-if="task.reason" :title="task.reason" class="text-13px text-truncate w-150px mt-1"> 审批意见: {{ task.reason }}</div> -->
|
||||
</div>
|
||||
<el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
|
||||
<el-avatar class="!m-5px" :size="28" v-else>
|
||||
{{ user.nickname.substring(0, 1) }}
|
||||
</el-avatar>
|
||||
{{ user.nickname }}
|
||||
|
||||
<!-- 信息:任务 ICON -->
|
||||
<div
|
||||
v-if="showStatusIcon"
|
||||
class="position-absolute top-20px left-24px rounded-full flex items-center p-1px border-2 border-white border-solid"
|
||||
:style="{ backgroundColor: statusIconMap2['-1']?.color }"
|
||||
>
|
||||
<Icon :size="11" :icon="statusIconMap2['-1']?.icon" color="#FFFFFF" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 信息:时间 -->
|
||||
<div
|
||||
v-if="activity.status !== TaskStatusEnum.NOT_START"
|
||||
class="text-#a5a5a5 text-13px mt-1"
|
||||
>
|
||||
{{ getApprovalNodeTime(activity) }}
|
||||
</div>
|
||||
|
||||
<!-- TODO @jason:审批意见,要展示哈。 -->
|
||||
<!-- <div class="color-#a1a6ae text-12px mb-10px"> {{ activity.assigneeUser.nickname }}</div>
|
||||
<div v-if="activity.opinion" class="text-#a5a5a5 text-12px w-100%">
|
||||
<div class="mb-5px">审批意见:</div>
|
||||
<div
|
||||
class="w-100% border-1px border-#a5a5a5 border-dashed rounded py-5px px-15px text-#2d2d2d"
|
||||
>
|
||||
{{ activity.opinion }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="activity.createTime" class="text-#a5a5a5 text-13px">
|
||||
{{ formatDate(activity.createTime) }}
|
||||
</div> -->
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
|
||||
<!-- 用户选择弹窗 -->
|
||||
<UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import { TaskStatusEnum } from '@/api/bpm/task'
|
||||
import { NodeType } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import { NodeType, CandidateStrategy } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { Check, Close, Loading, Clock, Minus, Delete } from '@element-plus/icons-vue'
|
||||
import starterSvg from '@/assets/svgs/bpm/starter.svg'
|
||||
import auditorSvg from '@/assets/svgs/bpm/auditor.svg'
|
||||
import copySvg from '@/assets/svgs/bpm/copy.svg'
|
||||
import conditionSvg from '@/assets/svgs/bpm/condition.svg'
|
||||
import parallelSvg from '@/assets/svgs/bpm/parallel.svg'
|
||||
import finishSvg from '@/assets/svgs/bpm/finish.svg'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceTimeline' })
|
||||
const props = defineProps({
|
||||
// 流程实例编号
|
||||
processInstanceId: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
// 流程定义编号
|
||||
processDefinitionId: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
activityNodes: ProcessInstanceApi.ApprovalNodeInfo[] // 审批节点信息
|
||||
showStatusIcon?: boolean // 是否显示头像右下角状态图标
|
||||
}>(),
|
||||
{
|
||||
showStatusIcon: true // 默认值为 true
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// 审批节点
|
||||
const approveNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([])
|
||||
|
||||
const statusIconMap2 = {
|
||||
// 未开始
|
||||
'-1': { color: '#e5e7ec', icon: 'ep-clock' },
|
||||
'-1': { color: '#909398', icon: 'ep-clock' },
|
||||
// 待审批
|
||||
'0': { color: '#e5e7ec', icon: 'ep:loading' },
|
||||
'0': { color: '#00b32a', icon: 'ep:loading' },
|
||||
// 审批中
|
||||
'1': { color: '#448ef7', icon: 'ep:loading' },
|
||||
// 审批通过
|
||||
|
@ -160,7 +194,7 @@ const statusIconMap2 = {
|
|||
'3': { color: '#f46b6c', icon: 'fa-solid:times-circle' },
|
||||
// 取消
|
||||
'4': { color: '#cccccc', icon: 'ep:delete-filled' },
|
||||
// 回退
|
||||
// 退回
|
||||
'5': { color: '#f46b6c', icon: 'ep:remove-filled' },
|
||||
// 委派中
|
||||
'6': { color: '#448ef7', icon: 'ep:loading' },
|
||||
|
@ -170,8 +204,8 @@ const statusIconMap2 = {
|
|||
|
||||
const statusIconMap = {
|
||||
// 审批未开始
|
||||
'-1': { color: '#e5e7ec', icon: Clock },
|
||||
'0': { color: '#e5e7ec', icon: Clock },
|
||||
'-1': { color: '#909398', icon: Clock },
|
||||
'0': { color: '#00b32a', icon: Clock },
|
||||
// 审批中
|
||||
'1': { color: '#448ef7', icon: Loading },
|
||||
// 审批通过
|
||||
|
@ -180,7 +214,7 @@ const statusIconMap = {
|
|||
'3': { color: '#f46b6c', icon: Close },
|
||||
// 已取消
|
||||
'4': { color: '#cccccc', icon: Delete },
|
||||
// 回退
|
||||
// 退回
|
||||
'5': { color: '#f46b6c', icon: Minus },
|
||||
// 委派中
|
||||
'6': { color: '#448ef7', icon: Loading },
|
||||
|
@ -188,13 +222,27 @@ const statusIconMap = {
|
|||
'7': { color: '#00b32a', icon: Check }
|
||||
}
|
||||
|
||||
/** 获得审批详情 */
|
||||
const getApprovalDetail = async () => {
|
||||
const data = await ProcessInstanceApi.getApprovalDetail(
|
||||
props.processInstanceId,
|
||||
props.processDefinitionId
|
||||
)
|
||||
approveNodes.value = data.approveNodes
|
||||
const nodeTypeSvgMap = {
|
||||
// 结束节点
|
||||
[NodeType.END_EVENT_NODE]: { color: '#909398', svg: finishSvg },
|
||||
// 发起人节点
|
||||
[NodeType.START_USER_NODE]: { color: '#909398', svg: starterSvg },
|
||||
// 审批人节点
|
||||
[NodeType.USER_TASK_NODE]: { color: '#ff943e', svg: auditorSvg },
|
||||
// 抄送人节点
|
||||
[NodeType.COPY_TASK_NODE]: { color: '#3296fb', svg: copySvg },
|
||||
// 条件分支节点
|
||||
[NodeType.CONDITION_NODE]: { color: '#14bb83', svg: conditionSvg },
|
||||
// 并行分支节点
|
||||
[NodeType.PARALLEL_BRANCH_NODE]: { color: '#14bb83', svg: parallelSvg }
|
||||
}
|
||||
|
||||
// 只有只有状态是 -1、0、1 才展示头像右小角状态小icon
|
||||
const onlyStatusIconShow = [-1, 0, 1]
|
||||
|
||||
// timeline时间线上icon图标
|
||||
const getApprovalNodeImg = (nodeType: NodeType) => {
|
||||
return nodeTypeSvgMap[nodeType]?.svg
|
||||
}
|
||||
|
||||
const getApprovalNodeIcon = (taskStatus: number, nodeType: NodeType) => {
|
||||
|
@ -202,7 +250,11 @@ const getApprovalNodeIcon = (taskStatus: number, nodeType: NodeType) => {
|
|||
return statusIconMap[taskStatus]?.icon
|
||||
}
|
||||
|
||||
if (nodeType === NodeType.START_USER_NODE || nodeType === NodeType.USER_TASK_NODE) {
|
||||
if (
|
||||
nodeType === NodeType.START_USER_NODE ||
|
||||
nodeType === NodeType.USER_TASK_NODE ||
|
||||
nodeType === NodeType.END_EVENT_NODE
|
||||
) {
|
||||
return statusIconMap[taskStatus]?.icon
|
||||
}
|
||||
}
|
||||
|
@ -212,22 +264,29 @@ const getApprovalNodeColor = (taskStatus: number) => {
|
|||
}
|
||||
|
||||
const getApprovalNodeTime = (node: ProcessInstanceApi.ApprovalNodeInfo) => {
|
||||
if (node.nodeType === NodeType.START_USER_NODE && node.startTime) {
|
||||
return `${formatDate(node.startTime)}`
|
||||
}
|
||||
if (node.endTime) {
|
||||
return `结束时间:${formatDate(node.endTime)}`
|
||||
return `${formatDate(node.endTime)}`
|
||||
}
|
||||
if (node.startTime) {
|
||||
return `创建时间:${formatDate(node.startTime)}`
|
||||
return `${formatDate(node.startTime)}`
|
||||
}
|
||||
}
|
||||
|
||||
/** 重新刷新审批详情 */
|
||||
const refresh = () => {
|
||||
getApprovalDetail()
|
||||
// 选择自定义审批人
|
||||
const userSelectFormRef = ref()
|
||||
const handleSelectUser = (activityId, selectedList) => {
|
||||
userSelectFormRef.value.open(activityId, selectedList)
|
||||
}
|
||||
const emit = defineEmits<{
|
||||
selectUserConfirm: [id: any, userList: any[]]
|
||||
}>()
|
||||
const customApproveUsers: any = ref({}) // key:activityId,value:用户列表
|
||||
// 选择完成
|
||||
const handleUserSelectConfirm = (activityId: string, userList: any[]) => {
|
||||
customApproveUsers.value[activityId] = userList || []
|
||||
emit('selectUserConfirm', activityId, userList)
|
||||
}
|
||||
|
||||
defineExpose({ refresh })
|
||||
|
||||
onMounted(async () => {
|
||||
await getApprovalDetail()
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" title="委派任务" width="500">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item label="接收人" prop="delegateUserId">
|
||||
<el-select v-model="formData.delegateUserId" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userList"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="委派理由" prop="reason">
|
||||
<el-input v-model="formData.reason" clearable placeholder="请输入委派理由" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'BpmTaskDelegateForm' })
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: '',
|
||||
delegateUserId: undefined,
|
||||
reason: ''
|
||||
})
|
||||
const formRules = ref({
|
||||
delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '委派理由不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: string) => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
formData.value.id = id
|
||||
// 获得用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
await TaskApi.delegateTask(formData.value)
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: '',
|
||||
delegateUserId: undefined,
|
||||
reason: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,90 +0,0 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" title="回退任务" width="500">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item label="退回节点" prop="targetTaskDefinitionKey">
|
||||
<el-select v-model="formData.targetTaskDefinitionKey" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in returnList"
|
||||
:key="item.taskDefinitionKey"
|
||||
:label="item.name"
|
||||
:value="item.taskDefinitionKey"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="回退理由" prop="reason">
|
||||
<el-input v-model="formData.reason" clearable placeholder="请输入回退理由" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" name="TaskRollbackDialogForm" setup>
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: '',
|
||||
targetTaskDefinitionKey: undefined,
|
||||
reason: ''
|
||||
})
|
||||
const formRules = ref({
|
||||
targetTaskDefinitionKey: [{ required: true, message: '必须选择回退节点', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '回退理由不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const returnList = ref([] as any)
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: string) => {
|
||||
returnList.value = await TaskApi.getTaskListByReturn(id)
|
||||
if (returnList.value.length === 0) {
|
||||
message.warning('当前没有可回退的节点')
|
||||
return false
|
||||
}
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
formData.value.id = id
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
await TaskApi.returnTask(formData.value)
|
||||
message.success('回退成功')
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: '',
|
||||
targetTaskDefinitionKey: undefined,
|
||||
reason: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,99 +0,0 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" title="加签" width="500">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item label="加签处理人" prop="userIds">
|
||||
<el-select v-model="formData.userIds" multiple clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userList"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="加签理由" prop="reason">
|
||||
<el-input v-model="formData.reason" clearable placeholder="请输入加签理由" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm('before')">
|
||||
向前加签
|
||||
</el-button>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm('after')">
|
||||
向后加签
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'TaskSignCreateForm' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: '',
|
||||
userIds: [],
|
||||
type: '',
|
||||
reason: ''
|
||||
})
|
||||
const formRules = ref({
|
||||
userIds: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '加签理由不能为空', trigger: 'change' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: string) => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
formData.value.id = id
|
||||
// 获得用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async (type: string) => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
formData.value.type = type
|
||||
try {
|
||||
await TaskApi.signCreateTask(formData.value)
|
||||
message.success('加签成功')
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: '',
|
||||
userIds: [],
|
||||
type: '',
|
||||
reason: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,89 +0,0 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" title="减签" width="500">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item label="减签任务" prop="id">
|
||||
<el-radio-group v-model="formData.id">
|
||||
<el-radio-button v-for="item in childrenTaskList" :key="item.id" :value="item.id">
|
||||
{{ item.name }}
|
||||
({{ item.assigneeUser?.deptName || item.ownerUser?.deptName }} -
|
||||
{{ item.assigneeUser?.nickname || item.ownerUser?.nickname }})
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="减签理由" prop="reason">
|
||||
<el-input v-model="formData.reason" clearable placeholder="请输入减签理由" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
|
||||
defineOptions({ name: 'TaskSignDeleteForm' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: '',
|
||||
reason: ''
|
||||
})
|
||||
const formRules = ref({
|
||||
id: [{ required: true, message: '必须选择减签任务', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '减签理由不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const childrenTaskList = ref([])
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: string) => {
|
||||
childrenTaskList.value = await TaskApi.getChildrenTaskList(id)
|
||||
if (isEmpty(childrenTaskList.value)) {
|
||||
message.warning('当前没有可减签的任务')
|
||||
return false
|
||||
}
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
await TaskApi.signDeleteTask(formData.value)
|
||||
message.success('减签成功')
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: '',
|
||||
reason: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,106 +0,0 @@
|
|||
<template>
|
||||
<el-drawer v-model="drawerVisible" title="子任务" size="880px">
|
||||
<!-- 当前任务 -->
|
||||
<template #header>
|
||||
<h4>【{{ parentTask.name }} 】审批人:{{ parentTask?.assigneeUser?.nickname }}</h4>
|
||||
<el-button
|
||||
style="margin-left: 5px"
|
||||
v-if="isSignDeleteButtonVisible(parentTask)"
|
||||
type="danger"
|
||||
plain
|
||||
@click="handleSignDelete(parentTask)"
|
||||
>
|
||||
<Icon icon="ep:remove" /> 减签
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 子任务列表 -->
|
||||
<el-table :data="parentTask.children" style="width: 100%" row-key="id" border>
|
||||
<el-table-column prop="assigneeUser.nickname" label="审批人" min-width="100">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.nickname || scope.row.ownerUser?.nickname }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="assigneeUser.deptName" label="所在部门" min-width="100">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.deptName || scope.row.ownerUser?.deptName }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批状态" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="提交时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="结束时间"
|
||||
align="center"
|
||||
prop="endTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" prop="operation" width="90">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="isSignDeleteButtonVisible(scope.row)"
|
||||
type="danger"
|
||||
plain
|
||||
size="small"
|
||||
@click="handleSignDelete(scope.row)"
|
||||
>
|
||||
<Icon icon="ep:remove" /> 减签
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 减签 -->
|
||||
<TaskSignDeleteForm ref="taskSignDeleteFormRef" @success="handleSignDeleteSuccess" />
|
||||
</el-drawer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import TaskSignDeleteForm from './TaskSignDeleteForm.vue'
|
||||
|
||||
defineOptions({ name: 'TaskSignList' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const drawerVisible = ref(false) // 抽屉的是否展示
|
||||
const parentTask = ref({} as any)
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (task: any) => {
|
||||
if (isEmpty(task.children)) {
|
||||
message.warning('该任务没有子任务')
|
||||
return
|
||||
}
|
||||
parentTask.value = task
|
||||
// 展开抽屉
|
||||
drawerVisible.value = true
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 发起减签 */
|
||||
const taskSignDeleteFormRef = ref()
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const handleSignDelete = (item: any) => {
|
||||
taskSignDeleteFormRef.value.open(item.id)
|
||||
}
|
||||
const handleSignDeleteSuccess = () => {
|
||||
emit('success')
|
||||
// 关闭抽屉
|
||||
drawerVisible.value = false
|
||||
}
|
||||
|
||||
/** 是否显示减签按钮 */
|
||||
const isSignDeleteButtonVisible = (task: any) => {
|
||||
return task && task.children && !isEmpty(task.children)
|
||||
}
|
||||
</script>
|
|
@ -1,89 +0,0 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" title="转派任务" width="500">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item label="新审批人" prop="assigneeUserId">
|
||||
<el-select v-model="formData.assigneeUserId" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userList"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="转派理由" prop="reason">
|
||||
<el-input v-model="formData.reason" clearable placeholder="请输入转派理由" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'TaskTransferForm' })
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: '',
|
||||
assigneeUserId: undefined,
|
||||
reason: ''
|
||||
})
|
||||
const formRules = ref({
|
||||
assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '转派理由不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: string) => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
formData.value.id = id
|
||||
// 获得用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
await TaskApi.transferTask(formData.value)
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: '',
|
||||
assigneeUserId: undefined,
|
||||
reason: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,222 +1,167 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 审批信息 -->
|
||||
<el-card
|
||||
v-for="(item, index) in runningTasks"
|
||||
:key="index"
|
||||
v-loading="processInstanceLoading"
|
||||
class="box-card"
|
||||
>
|
||||
<template #header>
|
||||
<span class="el-icon-picture-outline">审批任务【{{ item.name }}】</span>
|
||||
</template>
|
||||
<el-col :offset="6" :span="16">
|
||||
<el-form
|
||||
:ref="'form' + index"
|
||||
:model="auditForms[index]"
|
||||
:rules="auditRule"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item v-if="processInstance && processInstance.name" label="流程名">
|
||||
{{ processInstance.name }}
|
||||
</el-form-item>
|
||||
<el-form-item v-if="processInstance && processInstance.startUser" label="流程发起人">
|
||||
{{ processInstance?.startUser.nickname }}
|
||||
<el-tag size="small" type="info">{{ processInstance?.startUser.deptName }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-card v-if="runningTasks[index].formId > 0" class="mb-15px !-mt-10px">
|
||||
<template #header>
|
||||
<span class="el-icon-picture-outline">
|
||||
填写表单【{{ runningTasks[index]?.formName }}】
|
||||
</span>
|
||||
</template>
|
||||
<form-create
|
||||
v-model="approveForms[index].value"
|
||||
v-model:api="approveFormFApis[index]"
|
||||
:option="approveForms[index].option"
|
||||
:rule="approveForms[index].rule"
|
||||
/>
|
||||
</el-card>
|
||||
<el-form-item label="审批建议" prop="reason">
|
||||
<el-input
|
||||
v-model="auditForms[index].reason"
|
||||
placeholder="请输入审批建议"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="抄送人" prop="copyUserIds">
|
||||
<el-select v-model="auditForms[index].copyUserIds" multiple placeholder="请选择抄送人">
|
||||
<el-option
|
||||
v-for="itemx in userOptions"
|
||||
:key="itemx.id"
|
||||
:label="itemx.nickname"
|
||||
:value="itemx.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div style="margin-bottom: 20px; margin-left: 10%; font-size: 14px">
|
||||
<!-- TODO @jason:建议搞个 if 来判断,替代现有的 !item.buttonsSetting || item.buttonsSetting[OpsButtonType.APPROVE]?.enable -->
|
||||
<el-button
|
||||
type="success"
|
||||
v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.APPROVE]?.enable"
|
||||
@click="handleAudit(item, true)"
|
||||
>
|
||||
<Icon icon="ep:select" />
|
||||
<!-- TODO @jason:这个也是类似哈,搞个方法来生成名字 -->
|
||||
{{
|
||||
item.buttonsSetting?.[OperationButtonType.APPROVE]?.displayName ||
|
||||
OPERATION_BUTTON_NAME.get(OperationButtonType.APPROVE)
|
||||
}}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.REJECT]?.enable"
|
||||
type="danger"
|
||||
@click="handleAudit(item, false)"
|
||||
>
|
||||
<Icon icon="ep:close" />
|
||||
{{
|
||||
item.buttonsSetting?.[OperationButtonType.REJECT].displayName ||
|
||||
OPERATION_BUTTON_NAME.get(OperationButtonType.REJECT)
|
||||
}}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.TRANSFER]?.enable"
|
||||
type="primary"
|
||||
@click="openTaskUpdateAssigneeForm(item.id)"
|
||||
>
|
||||
<Icon icon="ep:edit" />
|
||||
{{
|
||||
item.buttonsSetting?.[OperationButtonType.TRANSFER]?.displayName ||
|
||||
OPERATION_BUTTON_NAME.get(OperationButtonType.TRANSFER)
|
||||
}}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.DELEGATE]?.enable"
|
||||
type="primary"
|
||||
@click="handleDelegate(item)"
|
||||
>
|
||||
<Icon icon="ep:position" />
|
||||
{{
|
||||
item.buttonsSetting?.[OperationButtonType.DELEGATE]?.displayName ||
|
||||
OPERATION_BUTTON_NAME.get(OperationButtonType.DELEGATE)
|
||||
}}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.ADD_SIGN]?.enable"
|
||||
type="primary"
|
||||
@click="handleSign(item)"
|
||||
>
|
||||
<Icon icon="ep:plus" />
|
||||
{{
|
||||
item.buttonsSetting?.[OperationButtonType.ADD_SIGN]?.displayName ||
|
||||
OPERATION_BUTTON_NAME.get(OperationButtonType.ADD_SIGN)
|
||||
}}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.RETURN]?.enable"
|
||||
type="warning"
|
||||
@click="handleBack(item)"
|
||||
>
|
||||
<Icon icon="ep:back" />
|
||||
{{
|
||||
item.buttonsSetting?.[OperationButtonType.RETURN]?.displayName ||
|
||||
OPERATION_BUTTON_NAME.get(OperationButtonType.RETURN)
|
||||
}}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-card>
|
||||
|
||||
<!-- 申请信息 -->
|
||||
<el-card v-loading="processInstanceLoading" class="box-card">
|
||||
<template #header>
|
||||
<span class="el-icon-document">申请信息【{{ processInstance.name }}】</span>
|
||||
</template>
|
||||
<!-- 情况一:流程表单 -->
|
||||
<el-col v-if="processInstance?.processDefinition?.formType === 10" :offset="6" :span="16">
|
||||
<form-create
|
||||
v-model="detailForm.value"
|
||||
v-model:api="fApi"
|
||||
:option="detailForm.option"
|
||||
:rule="detailForm.rule"
|
||||
<ContentWrap :bodyStyle="{ padding: '10px 20px 0' }" class="position-relative">
|
||||
<div class="processInstance-wrap-main">
|
||||
<el-scrollbar>
|
||||
<img
|
||||
class="position-absolute right-20px"
|
||||
width="150"
|
||||
:src="auditIconsMap[processInstance.status]"
|
||||
alt=""
|
||||
/>
|
||||
</el-col>
|
||||
<!-- 情况二:业务表单 -->
|
||||
<div v-if="processInstance?.processDefinition?.formType === 20">
|
||||
<BusinessFormComponent :id="processInstance.businessKey" />
|
||||
</div>
|
||||
</el-card>
|
||||
<div class="text-#878c93 h-15px">编号:{{ id }}</div>
|
||||
<el-divider class="!my-8px" />
|
||||
<div class="flex items-center gap-5 mb-10px h-40px">
|
||||
<div class="text-26px font-bold mb-5px">{{ processInstance.name }}</div>
|
||||
<dict-tag
|
||||
v-if="processInstance.status"
|
||||
:type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS"
|
||||
:value="processInstance.status"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 审批记录 -->
|
||||
<ProcessInstanceTaskList
|
||||
:loading="tasksLoad"
|
||||
:process-instance="processInstance"
|
||||
:tasks="tasks"
|
||||
@refresh="getTaskList"
|
||||
/>
|
||||
<div class="flex items-center gap-5 mb-10px text-13px h-35px">
|
||||
<div
|
||||
class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600"
|
||||
>
|
||||
<el-avatar
|
||||
:size="28"
|
||||
v-if="processInstance?.startUser?.avatar"
|
||||
:src="processInstance?.startUser?.avatar"
|
||||
/>
|
||||
<el-avatar :size="28" v-else-if="processInstance?.startUser?.nickname">
|
||||
{{ processInstance?.startUser?.nickname.substring(0, 1) }}
|
||||
</el-avatar>
|
||||
{{ processInstance?.startUser?.nickname }}
|
||||
</div>
|
||||
<div class="text-#878c93"> {{ formatDate(processInstance.startTime) }} 提交 </div>
|
||||
</div>
|
||||
|
||||
<!-- 高亮流程图 -->
|
||||
<ProcessInstanceBpmnViewer
|
||||
:id="`${id}`"
|
||||
:bpmn-xml="bpmnXml"
|
||||
:loading="processInstanceLoading"
|
||||
:process-instance="processInstance"
|
||||
:tasks="tasks"
|
||||
/>
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 表单信息 -->
|
||||
<el-tab-pane label="审批详情" name="form">
|
||||
<div class="form-scroll-area">
|
||||
<el-scrollbar>
|
||||
<el-row>
|
||||
<el-col :span="17" class="!flex !flex-col formCol">
|
||||
<!-- 表单信息 -->
|
||||
<div
|
||||
v-loading="processInstanceLoading"
|
||||
class="form-box flex flex-col mb-30px flex-1"
|
||||
>
|
||||
<!-- 情况一:流程表单 -->
|
||||
<el-col v-if="processDefinition?.formType === 10">
|
||||
<form-create
|
||||
v-model="detailForm.value"
|
||||
v-model:api="fApi"
|
||||
:option="detailForm.option"
|
||||
:rule="detailForm.rule"
|
||||
/>
|
||||
</el-col>
|
||||
<!-- 情况二:业务表单 -->
|
||||
<div v-if="processDefinition?.formType === 20">
|
||||
<BusinessFormComponent :id="processInstance.businessKey" />
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="7">
|
||||
<!-- 审批记录时间线 -->
|
||||
<ProcessInstanceTimeline :activity-nodes="activityNodes" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 弹窗:转派审批人 -->
|
||||
<TaskTransferForm ref="taskTransferFormRef" @success="getDetail" />
|
||||
<!-- 弹窗:回退节点 -->
|
||||
<TaskReturnForm ref="taskReturnFormRef" @success="getDetail" />
|
||||
<!-- 弹窗:委派,将任务委派给别人处理,处理完成后,会重新回到原审批人手中-->
|
||||
<TaskDelegateForm ref="taskDelegateForm" @success="getDetail" />
|
||||
<!-- 弹窗:加签,当前任务审批人为A,向前加签选了一个C,则需要C先审批,然后再是A审批,向后加签B,A审批完,需要B再审批完,才算完成这个任务节点 -->
|
||||
<TaskSignCreateForm ref="taskSignCreateFormRef" @success="getDetail" />
|
||||
<!-- 流程图 -->
|
||||
<el-tab-pane label="流程图" name="diagram">
|
||||
<div class="form-scroll-area">
|
||||
<ProcessInstanceSimpleViewer
|
||||
v-show="
|
||||
processDefinition.modelType && processDefinition.modelType === BpmModelType.SIMPLE
|
||||
"
|
||||
:loading="processInstanceLoading"
|
||||
:model-view="processModelView"
|
||||
/>
|
||||
<ProcessInstanceBpmnViewer
|
||||
v-show="
|
||||
processDefinition.modelType && processDefinition.modelType === BpmModelType.BPMN
|
||||
"
|
||||
:loading="processInstanceLoading"
|
||||
:model-view="processModelView"
|
||||
/>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 流转记录 -->
|
||||
<el-tab-pane label="流转记录" name="record">
|
||||
<div class="form-scroll-area">
|
||||
<el-scrollbar>
|
||||
<ProcessInstanceTaskList :loading="processInstanceLoading" :id="id" />
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 流转评论 TODO 待开发 -->
|
||||
<el-tab-pane label="流转评论" name="comment" v-if="false">
|
||||
<div class="form-scroll-area">
|
||||
<el-scrollbar> 流转评论 </el-scrollbar>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div class="b-t-solid border-t-1px border-[var(--el-border-color)]">
|
||||
<!-- 操作栏按钮 -->
|
||||
<ProcessInstanceOperationButton
|
||||
ref="operationButtonRef"
|
||||
:process-instance="processInstance"
|
||||
:process-definition="processDefinition"
|
||||
:userOptions="userOptions"
|
||||
@success="refresh"
|
||||
/>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { BpmModelType } from '@/utils/constants'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
|
||||
import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
|
||||
import TaskReturnForm from './dialog/TaskReturnForm.vue'
|
||||
import TaskDelegateForm from './dialog/TaskDelegateForm.vue'
|
||||
import TaskTransferForm from './dialog/TaskTransferForm.vue'
|
||||
import TaskSignCreateForm from './dialog/TaskSignCreateForm.vue'
|
||||
import { registerComponent } from '@/utils/routerHelper'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import {
|
||||
OperationButtonType,
|
||||
OPERATION_BUTTON_NAME
|
||||
} from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
|
||||
import ProcessInstanceSimpleViewer from './ProcessInstanceSimpleViewer.vue'
|
||||
import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
|
||||
import ProcessInstanceOperationButton from './ProcessInstanceOperationButton.vue'
|
||||
import ProcessInstanceTimeline from './ProcessInstanceTimeline.vue'
|
||||
import { FieldPermissionType } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import { TaskStatusEnum } from '@/api/bpm/task'
|
||||
import runningSvg from '@/assets/svgs/bpm/running.svg'
|
||||
import approveSvg from '@/assets/svgs/bpm/approve.svg'
|
||||
import rejectSvg from '@/assets/svgs/bpm/reject.svg'
|
||||
import cancelSvg from '@/assets/svgs/bpm/cancel.svg'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceDetail' })
|
||||
|
||||
const { query } = useRoute() // 查询参数
|
||||
const props = defineProps<{
|
||||
id: string // 流程实例的编号
|
||||
taskId?: string // 任务编号
|
||||
activityId?: string //流程活动编号,用于抄送查看
|
||||
}>()
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
|
||||
const userId = useUserStore().getUser.id // 当前登录的编号
|
||||
const id = query.id as unknown as string // 流程实例的编号
|
||||
const processInstanceLoading = ref(false) // 流程实例的加载中
|
||||
const processInstance = ref<any>({}) // 流程实例
|
||||
const bpmnXml = ref('') // BPMN XML
|
||||
const tasksLoad = ref(true) // 任务的加载中
|
||||
const tasks = ref<any[]>([]) // 任务列表
|
||||
// ========== 审批信息 ==========
|
||||
const runningTasks = ref<any[]>([]) // 运行中的任务
|
||||
const auditForms = ref<any[]>([]) // 审批任务的表单
|
||||
const auditRule = reactive({
|
||||
reason: [{ required: true, message: '审批建议不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const approveForms = ref<any[]>([]) // 审批通过时,额外的补充信息
|
||||
const approveFormFApis = ref<ApiAttrs[]>([]) // approveForms 的 fAPi
|
||||
const processDefinition = ref<any>({}) // 流程定义
|
||||
const processModelView = ref<any>({}) // 流程模型视图
|
||||
const operationButtonRef = ref() // 操作按钮组件 ref
|
||||
const auditIconsMap = {
|
||||
[TaskStatusEnum.RUNNING]: runningSvg,
|
||||
[TaskStatusEnum.APPROVE]: approveSvg,
|
||||
[TaskStatusEnum.REJECT]: rejectSvg,
|
||||
[TaskStatusEnum.CANCEL]: cancelSvg
|
||||
}
|
||||
|
||||
// ========== 申请信息 ==========
|
||||
const fApi = ref<ApiAttrs>() //
|
||||
|
@ -226,134 +171,62 @@ const detailForm = ref({
|
|||
value: {}
|
||||
}) // 流程实例的表单详情
|
||||
|
||||
/** 监听 approveFormFApis,实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */
|
||||
watch(
|
||||
() => approveFormFApis.value,
|
||||
(value) => {
|
||||
value?.forEach((api) => {
|
||||
api.btn.show(false)
|
||||
api.resetBtn.show(false)
|
||||
})
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
/** 处理审批通过和不通过的操作 */
|
||||
const handleAudit = async (task, pass) => {
|
||||
// 1.1 获得对应表单
|
||||
const index = runningTasks.value.indexOf(task)
|
||||
const auditFormRef = proxy.$refs['form' + index][0]
|
||||
// 1.2 校验表单
|
||||
const elForm = unref(auditFormRef)
|
||||
if (!elForm) return
|
||||
let valid = await elForm.validate()
|
||||
if (!valid) return
|
||||
// 校验申请表单(可编辑字段)
|
||||
// TODO @jason:之前这里是 if (!fApi.value) return;针对业务表单的情况下,会导致没办法审核,可能要看下。我这里改了点,看看是不是还有别的地方兼容性
|
||||
if (fApi.value) {
|
||||
valid = await fApi.value.validate()
|
||||
if (!valid) return
|
||||
}
|
||||
|
||||
// 2.1 提交审批
|
||||
const data = {
|
||||
id: task.id,
|
||||
reason: auditForms.value[index].reason,
|
||||
copyUserIds: auditForms.value[index].copyUserIds
|
||||
}
|
||||
if (pass) {
|
||||
// 审批通过,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
|
||||
const formCreateApi = approveFormFApis.value[index]
|
||||
if (formCreateApi) {
|
||||
await formCreateApi.validate()
|
||||
data.variables = approveForms.value[index].value
|
||||
}
|
||||
// 获取表单可编辑字段的值
|
||||
if (fApi.value && task.fieldsPermission !== null) {
|
||||
data.variables = getWritableValueOfForm(task.fieldsPermission)
|
||||
}
|
||||
|
||||
await TaskApi.approveTask(data)
|
||||
message.success('审批通过成功')
|
||||
} else {
|
||||
await TaskApi.rejectTask(data)
|
||||
message.success('审批不通过成功')
|
||||
}
|
||||
// 2.2 加载最新数据
|
||||
getDetail()
|
||||
}
|
||||
|
||||
/** 转派审批人 */
|
||||
const taskTransferFormRef = ref()
|
||||
const openTaskUpdateAssigneeForm = (id: string) => {
|
||||
taskTransferFormRef.value.open(id)
|
||||
}
|
||||
|
||||
/** 处理审批退回的操作 */
|
||||
const taskDelegateForm = ref()
|
||||
const handleDelegate = async (task) => {
|
||||
taskDelegateForm.value.open(task.id)
|
||||
}
|
||||
|
||||
/** 处理审批退回的操作 */
|
||||
const taskReturnFormRef = ref()
|
||||
const handleBack = async (task: any) => {
|
||||
taskReturnFormRef.value.open(task.id)
|
||||
}
|
||||
|
||||
/** 处理审批加签的操作 */
|
||||
const taskSignCreateFormRef = ref()
|
||||
const handleSign = async (task: any) => {
|
||||
taskSignCreateFormRef.value.open(task.id)
|
||||
}
|
||||
|
||||
/** 获得详情 */
|
||||
const getDetail = async () => {
|
||||
// 1. 获得流程任务列表(审批记录)。 需要先获取任务,表单的权限设置需要根据任务来设置
|
||||
await getTaskList()
|
||||
// 2. 获得流程实例相关
|
||||
getProcessInstance()
|
||||
const getDetail = () => {
|
||||
getApprovalDetail()
|
||||
|
||||
getProcessModelView()
|
||||
}
|
||||
|
||||
/** 加载流程实例 */
|
||||
const BusinessFormComponent = ref(null) // 异步组件
|
||||
const getProcessInstance = async () => {
|
||||
const BusinessFormComponent = ref<any>(null) // 异步组件
|
||||
/** 获取审批详情 */
|
||||
const getApprovalDetail = async () => {
|
||||
processInstanceLoading.value = true
|
||||
try {
|
||||
processInstanceLoading.value = true
|
||||
const data = await ProcessInstanceApi.getProcessInstance(id)
|
||||
const param = {
|
||||
processInstanceId: props.id,
|
||||
activityId: props.activityId,
|
||||
taskId: props.taskId
|
||||
}
|
||||
const data = await ProcessInstanceApi.getApprovalDetail(param)
|
||||
if (!data) {
|
||||
message.error('查询不到审批详情信息!')
|
||||
return
|
||||
}
|
||||
if (!data.processDefinition || !data.processInstance) {
|
||||
message.error('查询不到流程信息!')
|
||||
return
|
||||
}
|
||||
processInstance.value = data
|
||||
processInstance.value = data.processInstance
|
||||
processDefinition.value = data.processDefinition
|
||||
|
||||
// 设置表单信息
|
||||
const processDefinition = data.processDefinition
|
||||
if (processDefinition.formType === 10) {
|
||||
if (processDefinition.value.formType === 10) {
|
||||
// 获取表单字段权限
|
||||
const formFieldsPermission = data.formFieldsPermission
|
||||
|
||||
if (detailForm.value.rule.length > 0) {
|
||||
detailForm.value.value = data.formVariables
|
||||
// 避免刷新 form-create 显示不了
|
||||
detailForm.value.value = processInstance.value.formVariables
|
||||
} else {
|
||||
setConfAndFields2(
|
||||
detailForm,
|
||||
processDefinition.formConf,
|
||||
processDefinition.formFields,
|
||||
data.formVariables
|
||||
processDefinition.value.formConf,
|
||||
processDefinition.value.formFields,
|
||||
processInstance.value.formVariables
|
||||
)
|
||||
}
|
||||
nextTick().then(() => {
|
||||
fApi.value?.btn.show(false)
|
||||
fApi.value?.resetBtn.show(false)
|
||||
//@ts-ignore
|
||||
fApi.value?.disabled(true)
|
||||
// 设置表单权限。后续需要改造成。只处理一个运行中的任务
|
||||
if (runningTasks.value.length > 0) {
|
||||
const task = runningTasks.value.at(0)
|
||||
if (task.fieldsPermission) {
|
||||
Object.keys(task.fieldsPermission).forEach((item) => {
|
||||
setFieldPermission(item, task.fieldsPermission[item])
|
||||
})
|
||||
}
|
||||
// 设置表单字段权限
|
||||
if (formFieldsPermission) {
|
||||
Object.keys(data.formFieldsPermission).forEach((item) => {
|
||||
setFieldPermission(item, formFieldsPermission[item])
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
|
@ -361,118 +234,61 @@ const getProcessInstance = async () => {
|
|||
BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath)
|
||||
}
|
||||
|
||||
// 加载流程图
|
||||
bpmnXml.value = (
|
||||
await DefinitionApi.getProcessDefinition(processDefinition.id as number)
|
||||
)?.bpmnXml
|
||||
// 获取审批节点,显示 Timeline 的数据
|
||||
activityNodes.value = data.activityNodes
|
||||
|
||||
// 获取待办任务显示操作按钮
|
||||
operationButtonRef.value?.loadTodoTask(data.todoTask)
|
||||
} finally {
|
||||
processInstanceLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 加载任务列表 */
|
||||
const getTaskList = async () => {
|
||||
runningTasks.value = []
|
||||
auditForms.value = []
|
||||
approveForms.value = []
|
||||
approveFormFApis.value = []
|
||||
try {
|
||||
// 获得未取消的任务
|
||||
tasksLoad.value = true
|
||||
const data = await TaskApi.getTaskListByProcessInstanceId(id)
|
||||
tasks.value = []
|
||||
// 1.1 移除已取消的审批
|
||||
data.forEach((task) => {
|
||||
if (task.status !== 4) {
|
||||
tasks.value.push(task)
|
||||
}
|
||||
})
|
||||
// 1.2 排序,将未完成的排在前面,已完成的排在后面;
|
||||
tasks.value.sort((a, b) => {
|
||||
// 有已完成的情况,按照完成时间倒序
|
||||
if (a.endTime && b.endTime) {
|
||||
return b.endTime - a.endTime
|
||||
} else if (a.endTime) {
|
||||
return 1
|
||||
} else if (b.endTime) {
|
||||
return -1
|
||||
// 都是未完成,按照创建时间倒序
|
||||
} else {
|
||||
return b.createTime - a.createTime
|
||||
}
|
||||
})
|
||||
|
||||
// 获得需要自己审批的任务
|
||||
loadRunningTask(tasks.value)
|
||||
} finally {
|
||||
tasksLoad.value = false
|
||||
/** 获取流程模型视图*/
|
||||
const getProcessModelView = async () => {
|
||||
if (BpmModelType.BPMN === processDefinition.value?.modelType) {
|
||||
// 重置,解决 BPMN 流程图刷新不会重新渲染问题
|
||||
processModelView.value = {
|
||||
bpmnXml: ''
|
||||
}
|
||||
}
|
||||
const data = await ProcessInstanceApi.getProcessInstanceBpmnModelView(props.id)
|
||||
if (data) {
|
||||
processModelView.value = data
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 runningTasks 中的任务
|
||||
*/
|
||||
const loadRunningTask = (tasks) => {
|
||||
tasks.forEach((task) => {
|
||||
if (!isEmpty(task.children)) {
|
||||
loadRunningTask(task.children)
|
||||
}
|
||||
// 2.1 只有待处理才需要
|
||||
if (task.status !== 1 && task.status !== 6) {
|
||||
return
|
||||
}
|
||||
// 2.2 自己不是处理人
|
||||
if (!task.assigneeUser || task.assigneeUser.id !== userId) {
|
||||
return
|
||||
}
|
||||
|
||||
// 2.3 添加到处理任务
|
||||
runningTasks.value.push({ ...task })
|
||||
auditForms.value.push({
|
||||
reason: '',
|
||||
copyUserIds: []
|
||||
})
|
||||
|
||||
// 2.4 处理 approve 表单
|
||||
if (task.formId && task.formConf) {
|
||||
const approveForm = {}
|
||||
setConfAndFields2(approveForm, task.formConf, task.formFields, task.formVariables)
|
||||
approveForms.value.push(approveForm)
|
||||
} else {
|
||||
approveForms.value.push({}) // 占位,避免为空
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 审批节点信息
|
||||
const activityNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([])
|
||||
/**
|
||||
* 设置表单权限
|
||||
*/
|
||||
const setFieldPermission = (field: string, permission: string) => {
|
||||
if (permission === '1') {
|
||||
if (permission === FieldPermissionType.READ) {
|
||||
//@ts-ignore
|
||||
fApi.value?.disabled(true, field)
|
||||
}
|
||||
if (permission === '2') {
|
||||
if (permission === FieldPermissionType.WRITE) {
|
||||
//@ts-ignore
|
||||
fApi.value?.disabled(false, field)
|
||||
}
|
||||
if (permission === '3') {
|
||||
if (permission === FieldPermissionType.NONE) {
|
||||
//@ts-ignore
|
||||
fApi.value?.hidden(true, field)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可以编辑字段的值
|
||||
* 操作成功后刷新
|
||||
*/
|
||||
const getWritableValueOfForm = (fieldsPermission: Object) => {
|
||||
const fieldsValue = {}
|
||||
if (fieldsPermission && fApi.value) {
|
||||
Object.keys(fieldsPermission).forEach((item) => {
|
||||
if (fieldsPermission[item] === '2') {
|
||||
fieldsValue[item] = fApi.value.getValue(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
return fieldsValue
|
||||
const refresh = () => {
|
||||
// 重新获取详情
|
||||
getDetail()
|
||||
}
|
||||
|
||||
/** 当前的Tab */
|
||||
const activeTab = ref('form')
|
||||
|
||||
/** 初始化 */
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
onMounted(async () => {
|
||||
|
@ -481,3 +297,50 @@ onMounted(async () => {
|
|||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$wrap-padding-height: 20px;
|
||||
$wrap-margin-height: 15px;
|
||||
$button-height: 51px;
|
||||
$process-header-height: 194px;
|
||||
|
||||
.processInstance-wrap-main {
|
||||
height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
|
||||
);
|
||||
max-height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
|
||||
);
|
||||
overflow: auto;
|
||||
|
||||
.form-scroll-area {
|
||||
height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
|
||||
$process-header-height - 40px
|
||||
);
|
||||
max-height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
|
||||
$process-header-height - 40px
|
||||
);
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
:deep(.box-card) {
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
|
||||
.el-card__body {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-box {
|
||||
:deep(.el-card) {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,318 +0,0 @@
|
|||
<template>
|
||||
<ContentWrap :bodyStyle="{ padding: '10px 20px' }" class="position-relative">
|
||||
<div class="processInstance-wrap-main">
|
||||
<el-scrollbar>
|
||||
<img
|
||||
class="position-absolute right-20px"
|
||||
width="150"
|
||||
:src="auditIcons[processInstance.status]"
|
||||
alt=""
|
||||
/>
|
||||
<div class="text-#878c93 h-15px">编号:{{ id }}</div>
|
||||
<el-divider class="!my-8px" />
|
||||
<div class="flex items-center gap-5 mb-10px h-40px">
|
||||
<div class="text-26px font-bold mb-5px">{{ processInstance.name }}</div>
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="processInstance.status" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-5 mb-10px text-13px h-35px">
|
||||
<div
|
||||
class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600"
|
||||
>
|
||||
<img class="rounded-full h-28px" src="@/assets/imgs/avatar.jpg" alt="" />
|
||||
{{ processInstance?.startUser?.nickname }}
|
||||
</div>
|
||||
<div class="text-#878c93"> {{ formatDate(processInstance.startTime) }} 提交 </div>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 表单信息 -->
|
||||
<el-tab-pane label="审批详情" name="form">
|
||||
<div class="form-scroll-area">
|
||||
<el-scrollbar>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="18" class="!flex !flex-col formCol">
|
||||
<!-- 表单信息 -->
|
||||
<div
|
||||
v-loading="processInstanceLoading"
|
||||
class="form-box flex flex-col mb-30px flex-1"
|
||||
>
|
||||
<!-- 情况一:流程表单 -->
|
||||
<el-col
|
||||
v-if="processInstance?.processDefinition?.formType === 10"
|
||||
:offset="6"
|
||||
:span="16"
|
||||
>
|
||||
<form-create
|
||||
v-model="detailForm.value"
|
||||
v-model:api="fApi"
|
||||
:option="detailForm.option"
|
||||
:rule="detailForm.rule"
|
||||
/>
|
||||
</el-col>
|
||||
<!-- 情况二:业务表单 -->
|
||||
<div v-if="processInstance?.processDefinition?.formType === 20">
|
||||
<BusinessFormComponent :id="processInstance.businessKey" />
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<!-- 审批记录时间线 -->
|
||||
<ProcessInstanceTimeline ref="timelineRef" :process-instance-id="id" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<!-- 流程图 -->
|
||||
<el-tab-pane label="流程图" name="diagram">
|
||||
<ProcessInstanceBpmnViewer
|
||||
:id="`${id}`"
|
||||
:bpmn-xml="bpmnXml"
|
||||
:loading="processInstanceLoading"
|
||||
:process-instance="processInstance"
|
||||
:tasks="tasks"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<!-- 流转记录 -->
|
||||
<el-tab-pane label="流转记录" name="record">
|
||||
<ProcessInstanceTaskList
|
||||
:loading="tasksLoad"
|
||||
:process-instance="processInstance"
|
||||
:tasks="tasks"
|
||||
@refresh="getTaskList"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<!-- 流转评论 TODO 待开发 -->
|
||||
<el-tab-pane label="流转评论" name="comment"> 流转评论 </el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div
|
||||
class="b-t-solid border-t-1px border-[var(--el-border-color)]"
|
||||
v-if="activeTab === 'form'"
|
||||
>
|
||||
<!-- 操作栏按钮 -->
|
||||
<ProcessInstanceOperationButton
|
||||
ref="operationButtonRef"
|
||||
:processInstance="processInstance"
|
||||
:userOptions="userOptions"
|
||||
@success="refresh"
|
||||
/>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
|
||||
import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
|
||||
import ProcessInstanceOperationButton from './ProcessInstanceOperationButton.vue'
|
||||
import ProcessInstanceTimeline from './ProcessInstanceTimeline.vue'
|
||||
import { registerComponent } from '@/utils/routerHelper'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { FieldPermissionType } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import audit1 from '@/assets/svgs/bpm/audit1.svg'
|
||||
import audit2 from '@/assets/svgs/bpm/audit2.svg'
|
||||
import audit3 from '@/assets/svgs/bpm/audit3.svg'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceDetail' })
|
||||
const props = defineProps<{
|
||||
id: string // 流程实例的编号
|
||||
taskId?: string // 任务编号
|
||||
activityId?: string //流程活动编号,用于抄送查看
|
||||
}>()
|
||||
const message = useMessage() // 消息弹窗
|
||||
const processInstanceLoading = ref(false) // 流程实例的加载中
|
||||
const processInstance = ref<any>({}) // 流程实例
|
||||
const operationButtonRef = ref()
|
||||
const timelineRef = ref()
|
||||
const bpmnXml = ref('') // BPMN XML
|
||||
const tasksLoad = ref(true) // 任务的加载中
|
||||
const tasks = ref<any[]>([]) // 任务列表
|
||||
const auditIcons = {
|
||||
1: audit1,
|
||||
2: audit2,
|
||||
3: audit3
|
||||
}
|
||||
|
||||
// ========== 申请信息 ==========
|
||||
const fApi = ref<ApiAttrs>() //
|
||||
const detailForm = ref({
|
||||
rule: [],
|
||||
option: {},
|
||||
value: {}
|
||||
}) // 流程实例的表单详情
|
||||
|
||||
/** 获得详情 */
|
||||
const getDetail = () => {
|
||||
// 1. 获得流程实例相关
|
||||
getProcessInstance()
|
||||
// 2. 获得流程任务列表(审批记录)
|
||||
getTaskList()
|
||||
}
|
||||
|
||||
/** 加载流程实例 */
|
||||
const BusinessFormComponent = ref<any>(null) // 异步组件
|
||||
const getProcessInstance = async () => {
|
||||
try {
|
||||
processInstanceLoading.value = true
|
||||
const data = await ProcessInstanceApi.getProcessInstance(props.id)
|
||||
if (!data) {
|
||||
message.error('查询不到流程信息!')
|
||||
return
|
||||
}
|
||||
processInstance.value = data
|
||||
|
||||
// 设置表单信息
|
||||
const processDefinition = data.processDefinition
|
||||
if (processDefinition.formType === 10) {
|
||||
// 获取表单字段权限
|
||||
let fieldsPermission = undefined
|
||||
if (props.taskId || props.activityId) {
|
||||
fieldsPermission = await ProcessInstanceApi.getFormFieldsPermission({
|
||||
processInstanceId: props.id,
|
||||
taskId: props.taskId,
|
||||
activityId: props.activityId
|
||||
})
|
||||
}
|
||||
setConfAndFields2(
|
||||
detailForm,
|
||||
processDefinition.formConf,
|
||||
processDefinition.formFields,
|
||||
data.formVariables
|
||||
)
|
||||
nextTick().then(() => {
|
||||
fApi.value?.btn.show(false)
|
||||
fApi.value?.resetBtn.show(false)
|
||||
fApi.value?.disabled(true)
|
||||
if (fieldsPermission) {
|
||||
Object.keys(fieldsPermission).forEach((item) => {
|
||||
setFieldPermission(item, fieldsPermission[item])
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
|
||||
BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath)
|
||||
}
|
||||
|
||||
// 加载流程图
|
||||
bpmnXml.value = (await DefinitionApi.getProcessDefinition(processDefinition.id))?.bpmnXml
|
||||
} finally {
|
||||
processInstanceLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表单权限
|
||||
*/
|
||||
const setFieldPermission = (field: string, permission: string) => {
|
||||
if (permission === FieldPermissionType.READ) {
|
||||
fApi.value?.disabled(true, field)
|
||||
}
|
||||
if (permission === FieldPermissionType.WRITE) {
|
||||
fApi.value?.disabled(false, field)
|
||||
}
|
||||
if (permission === FieldPermissionType.NONE) {
|
||||
fApi.value?.hidden(true, field)
|
||||
}
|
||||
}
|
||||
|
||||
/** 加载任务列表 */
|
||||
const getTaskList = async () => {
|
||||
try {
|
||||
// 获得未取消的任务
|
||||
tasksLoad.value = true
|
||||
const data = await TaskApi.getTaskListByProcessInstanceId(props.id)
|
||||
tasks.value = []
|
||||
// 1.1 移除已取消的审批
|
||||
data.forEach((task: any) => {
|
||||
if (task.status !== 4) {
|
||||
tasks.value.push(task)
|
||||
}
|
||||
})
|
||||
// 1.2 排序,将未完成的排在前面,已完成的排在后面;
|
||||
tasks.value.sort((a, b) => {
|
||||
// 有已完成的情况,按照完成时间倒序
|
||||
if (a.endTime && b.endTime) {
|
||||
return b.endTime - a.endTime
|
||||
} else if (a.endTime) {
|
||||
return 1
|
||||
} else if (b.endTime) {
|
||||
return -1
|
||||
// 都是未完成,按照创建时间倒序
|
||||
} else {
|
||||
return b.createTime - a.createTime
|
||||
}
|
||||
})
|
||||
|
||||
// 获得需要自己审批的任务
|
||||
operationButtonRef.value?.loadRunningTask(tasks.value)
|
||||
} finally {
|
||||
tasksLoad.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作成功后刷新
|
||||
*/
|
||||
const refresh = () => {
|
||||
// 重新获取详情
|
||||
getDetail()
|
||||
// 刷新审批详情 Timeline
|
||||
timelineRef.value?.refresh()
|
||||
}
|
||||
|
||||
/** 当前的Tab */
|
||||
const activeTab = ref('form')
|
||||
|
||||
/** 初始化 */
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
onMounted(async () => {
|
||||
getDetail()
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$wrap-padding-height: 30px;
|
||||
$wrap-margin-height: 15px;
|
||||
$button-height: 51px;
|
||||
$process-header-height: 194px;
|
||||
|
||||
.processInstance-wrap-main {
|
||||
height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 45px
|
||||
);
|
||||
max-height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 45px
|
||||
);
|
||||
overflow: auto;
|
||||
|
||||
.form-scroll-area {
|
||||
height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 45px -
|
||||
$process-header-height - 40px
|
||||
);
|
||||
max-height: calc(
|
||||
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 45px -
|
||||
$process-header-height - 40px
|
||||
);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.form-box {
|
||||
:deep(.el-card) {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -10,7 +10,7 @@
|
|||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="流程名称" prop="name">
|
||||
<el-form-item label="" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入流程名称"
|
||||
|
@ -19,21 +19,19 @@
|
|||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属流程" prop="processDefinitionKey">
|
||||
<el-input
|
||||
v-model="queryParams.processDefinitionKey"
|
||||
placeholder="请输入流程定义的标识"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="category">
|
||||
|
||||
<!-- TODO @ tuituji:style 可以使用 unocss -->
|
||||
<el-form-item label="" prop="category" :style="{ position: 'absolute', right: '130px' }">
|
||||
<!-- TODO @tuituji:应该选择好分类,就触发搜索啦。 -->
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择流程分类"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
class="!w-155px"
|
||||
>
|
||||
<el-option
|
||||
v-for="category in categoryList"
|
||||
|
@ -43,43 +41,79 @@
|
|||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择流程状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发起时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
v-hasPermi="['bpm:process-instance:query']"
|
||||
@click="handleCreate(undefined)"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 发起流程
|
||||
|
||||
<!-- 高级筛选 -->
|
||||
<!-- TODO @ tuituji:style 可以使用 unocss -->
|
||||
<el-form-item :style="{ position: 'absolute', right: '0px' }">
|
||||
<el-button v-popover="popoverRef" v-click-outside="onClickOutside" :icon="List">
|
||||
高级筛选
|
||||
</el-button>
|
||||
<el-popover
|
||||
ref="popoverRef"
|
||||
trigger="click"
|
||||
virtual-triggering
|
||||
persistent
|
||||
:width="400"
|
||||
:show-arrow="false"
|
||||
placement="bottom-end"
|
||||
>
|
||||
<el-form-item label="流程发起人" class="bold-label" label-position="top" prop="category">
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择流程发起人"
|
||||
clearable
|
||||
class="!w-390px"
|
||||
>
|
||||
<el-option
|
||||
v-for="category in categoryList"
|
||||
:key="category.code"
|
||||
:label="category.name"
|
||||
:value="category.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="所属流程"
|
||||
class="bold-label"
|
||||
label-position="top"
|
||||
prop="processDefinitionKey"
|
||||
>
|
||||
<el-input
|
||||
v-model="queryParams.processDefinitionKey"
|
||||
placeholder="请输入流程定义的标识"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-390px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程状态" class="bold-label" label-position="top" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择流程状态"
|
||||
clearable
|
||||
class="!w-390px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-popover>
|
||||
<!-- TODO @tuituji:这里应该有确认,和取消、清空搜索条件,三个按钮。 -->
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
@ -95,6 +129,8 @@
|
|||
min-width="100"
|
||||
fixed="left"
|
||||
/>
|
||||
<!-- TODO @芋艿:摘要 -->
|
||||
<!-- TODO @tuituji:流程状态。可见需求文档里 -->
|
||||
<el-table-column label="流程状态" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
|
||||
|
@ -114,7 +150,7 @@
|
|||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="160">
|
||||
<!--<el-table-column align="center" label="耗时" prop="durationInMillis" width="160">
|
||||
<template #default="scope">
|
||||
{{ scope.row.durationInMillis > 0 ? formatPast2(scope.row.durationInMillis) : '-' }}
|
||||
</template>
|
||||
|
@ -126,7 +162,7 @@
|
|||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流程编号" align="center" prop="id" min-width="320px" />
|
||||
-->
|
||||
<el-table-column label="操作" align="center" fixed="right" width="180">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
|
@ -162,11 +198,13 @@
|
|||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// TODO @tuituji:List 改成 <Icon icon="ep:plus" class="mr-5px" /> 类似这种组件哈。
|
||||
import { List } from '@element-plus/icons-vue'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
||||
import { ProcessInstanceVO } from '@/api/bpm/processInstance'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
|
||||
|
@ -189,7 +227,7 @@ const queryParams = reactive({
|
|||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const categoryList = ref([]) // 流程分类列表
|
||||
const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
|
@ -222,7 +260,6 @@ const handleCreate = async (row?: ProcessInstanceVO) => {
|
|||
const processDefinitionDetail = await DefinitionApi.getProcessDefinition(
|
||||
row.processDefinitionId
|
||||
)
|
||||
debugger
|
||||
if (processDefinitionDetail.formType === 20) {
|
||||
message.error('重新发起流程失败,原因:该流程使用业务表单,不支持重新发起')
|
||||
return
|
||||
|
@ -261,6 +298,15 @@ const handleCancel = async (row) => {
|
|||
await getList()
|
||||
}
|
||||
|
||||
// TODO @tuituji:这个 import 是不是没用哈?
|
||||
import { ClickOutside as vClickOutside } from 'element-plus'
|
||||
|
||||
// TODO @tuituji:onClickAdvancedSearch。方法名叫这个,会更好一些哇?打开高级搜索。
|
||||
const popoverRef = ref()
|
||||
const onClickOutside = () => {
|
||||
unref(popoverRef).popperRef?.delayHide?.()
|
||||
}
|
||||
|
||||
/** 激活时 **/
|
||||
onActivated(() => {
|
||||
getList()
|
||||
|
@ -272,3 +318,8 @@ onMounted(async () => {
|
|||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
})
|
||||
</script>
|
||||
<style>
|
||||
.bold-label .el-form-item__label {
|
||||
font-weight: bold; /* 将字体加粗 */
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
<template>
|
||||
<SimpleProcessDesigner :model-id="modelId" />
|
||||
<ContentWrap :bodyStyle="{ padding: '20px 16px' }">
|
||||
<SimpleProcessDesigner :model-id="modelId" @success="close" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { SimpleProcessDesigner } from '@/components/SimpleProcessDesignerV2/src/'
|
||||
|
||||
defineOptions({
|
||||
name: 'SimpleWorkflowDesignEditor'
|
||||
name: 'SimpleModelDesign'
|
||||
})
|
||||
const router = useRouter() // 路由
|
||||
const { query } = useRoute() // 路由的查询
|
||||
const modelId = query.modelId as string
|
||||
const close = () => {
|
||||
router.push({ path: '/bpm/manager/model' })
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
|
@ -45,7 +45,12 @@
|
|||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column align="center" label="流程名" prop="processInstanceName" min-width="180" />
|
||||
<el-table-column align="center" label="流程发起人" prop="startUserName" min-width="100" />
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="流程发起人"
|
||||
prop="startUser.nickname"
|
||||
min-width="100"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
|
@ -53,8 +58,11 @@
|
|||
prop="processInstanceStartTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="抄送任务" prop="taskName" min-width="180" />
|
||||
<el-table-column align="center" label="抄送人" prop="creatorName" min-width="100" />
|
||||
<el-table-column align="center" label="抄送节点" prop="activityName" min-width="180" />
|
||||
<el-table-column align="center" label="抄送人" min-width="100">
|
||||
<template #default="scope"> {{ scope.row.createUser?.nickname || '系统' }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="抄送意见" prop="reason" width="150" />
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="抄送时间"
|
||||
|
|
|
@ -49,7 +49,11 @@ const designerConfig = ref({
|
|||
switchType: [], // 是否可以切换组件类型,或者可以相互切换的字段
|
||||
autoActive: true, // 是否自动选中拖入的组件
|
||||
useTemplate: false, // 是否生成vue2语法的模板组件
|
||||
formOptions: {}, // 定义表单配置默认值
|
||||
formOptions: {
|
||||
form: {
|
||||
labelWidth: '100px' // 设置默认的 label 宽度为 100px
|
||||
}
|
||||
}, // 定义表单配置默认值
|
||||
fieldReadonly: false, // 配置field是否可以编辑
|
||||
hiddenDragMenu: false, // 隐藏拖拽操作按钮
|
||||
hiddenDragBtn: false, // 隐藏拖拽按钮
|
||||
|
|
|
@ -26,9 +26,8 @@
|
|||
<el-select
|
||||
v-model="queryParams.pickUpStoreId"
|
||||
class="!w-280px"
|
||||
clearable
|
||||
multiple
|
||||
placeholder="全部"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in pickUpStoreList"
|
||||
|
@ -73,10 +72,22 @@
|
|||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-button @click="handlePickup" type="success" plain v-hasPermi="['trade:order:pick-up']">
|
||||
<el-button
|
||||
@click="handlePickup"
|
||||
type="success"
|
||||
plain
|
||||
v-hasPermi="['trade:order:pick-up']"
|
||||
:disabled="isUse"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:check" />
|
||||
核销
|
||||
</el-button>
|
||||
<el-button type="primary" @click="connectToSerialPort" :disabled="serialPort || isUse">
|
||||
连接扫描枪
|
||||
</el-button>
|
||||
<el-button type="danger" @click="cutPort" :disabled="!serialPort || isUse">
|
||||
断开扫描枪
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
@ -216,18 +227,20 @@ import { DeliveryTypeEnum } from '@/utils/constants'
|
|||
import { TradeOrderSummaryRespVO } from '@/api/mall/trade/order'
|
||||
import { DeliveryPickUpStoreVO } from '@/api/mall/trade/delivery/pickUpStore'
|
||||
import OrderPickUpForm from '@/views/mall/trade/order/form/OrderPickUpForm.vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const port = ref('')
|
||||
const ports = ref([])
|
||||
const reader = ref('')
|
||||
|
||||
defineOptions({ name: 'PickUpOrder' })
|
||||
|
||||
// 列表的加载中
|
||||
const loading = ref(true)
|
||||
// 列表的总页数
|
||||
const total = ref(2)
|
||||
// 列表的数据
|
||||
const list = ref<TradeOrderApi.OrderVO[]>([])
|
||||
// 搜索的表单
|
||||
const queryFormRef = ref<FormInstance>()
|
||||
// 初始表单参数
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(2) // 列表的总页数
|
||||
const list = ref<TradeOrderApi.OrderVO[]>([]) // 列表的数据
|
||||
const queryFormRef = ref<FormInstance>() // 搜索的表单
|
||||
const INIT_QUERY_PARAMS = {
|
||||
// 页数
|
||||
pageNo: 1,
|
||||
|
@ -238,14 +251,15 @@ const INIT_QUERY_PARAMS = {
|
|||
// 配送方式
|
||||
deliveryType: DeliveryTypeEnum.PICK_UP.type,
|
||||
// 自提门店
|
||||
pickUpStoreId: undefined
|
||||
}
|
||||
// 表单搜索
|
||||
const queryParams = ref({ ...INIT_QUERY_PARAMS })
|
||||
// 订单搜索类型 queryParam
|
||||
const queryType = reactive({ queryParam: 'no' })
|
||||
// 订单统计数据
|
||||
const summary = ref<TradeOrderSummaryRespVO>()
|
||||
pickUpStoreId: -1
|
||||
} // 初始表单参数
|
||||
|
||||
const queryParams = ref({ ...INIT_QUERY_PARAMS }) // 表单搜索
|
||||
const queryType = reactive({ queryParam: 'no' }) // 订单搜索类型 queryParam
|
||||
const summary = ref<TradeOrderSummaryRespVO>() // 订单统计数据
|
||||
|
||||
const serialPort = ref(false) // 是否连接扫码枪
|
||||
const isUse = ref(true) // 是否可核销
|
||||
|
||||
// 订单聚合搜索 select 类型配置(动态搜索)
|
||||
const dynamicSearchList = ref([
|
||||
|
@ -294,13 +308,21 @@ const handleQuery = async () => {
|
|||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields()
|
||||
queryParams.value = { ...INIT_QUERY_PARAMS }
|
||||
if (pickUpStoreList.value.length > 0) {
|
||||
queryParams.value.pickUpStoreId = pickUpStoreList.value[0].id
|
||||
}
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 自提门店精简列表 */
|
||||
const pickUpStoreList = ref<DeliveryPickUpStoreVO[]>([])
|
||||
const getPickUpStoreList = async () => {
|
||||
pickUpStoreList.value = await PickUpStoreApi.getListAllSimple()
|
||||
pickUpStoreList.value = await PickUpStoreApi.getSimpleDeliveryPickUpStoreList()
|
||||
// 移除自己无法核销的门店
|
||||
const userId = useUserStore().getUser.id
|
||||
pickUpStoreList.value = pickUpStoreList.value.filter((item) =>
|
||||
item.verifyUserIds?.includes(userId)
|
||||
)
|
||||
}
|
||||
|
||||
/** 显示核销表单 */
|
||||
|
@ -309,10 +331,96 @@ const handlePickup = () => {
|
|||
pickUpForm.value.open()
|
||||
}
|
||||
|
||||
/** 连接扫码枪 */
|
||||
const connectToSerialPort = async () => {
|
||||
try {
|
||||
// 判断浏览器支持串口通信
|
||||
if (
|
||||
'serial' in navigator &&
|
||||
navigator.serial != null &&
|
||||
typeof navigator.serial === 'object' &&
|
||||
'requestPort' in navigator.serial
|
||||
) {
|
||||
// 提示用户选择一个串口
|
||||
port.value = await navigator.serial.requestPort()
|
||||
} else {
|
||||
message.error('浏览器不支持扫码枪连接,请更换浏览器重试')
|
||||
return
|
||||
}
|
||||
|
||||
// 获取用户之前授予该网站访问权限的所有串口。
|
||||
ports.value = await navigator.serial.getPorts()
|
||||
|
||||
// console.log(port.value, ports.value);
|
||||
// console.log(port.value)
|
||||
// 等待串口打开
|
||||
await port.value.open({ baudRate: 9600, dataBits: 8, stopBits: 2 })
|
||||
|
||||
// console.log(typeof port.value);
|
||||
message.success('成功连接扫码枪')
|
||||
serialPort.value = true
|
||||
// readData(port.value);
|
||||
readData()
|
||||
} catch (error) {
|
||||
// 处理连接串口出错的情况
|
||||
console.log('Error connecting to serial port:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/** 监听扫码枪输入 */
|
||||
const readData = async () => {
|
||||
reader.value = port.value.readable.getReader()
|
||||
let data = '' //扫码数据
|
||||
// 监听来自串口的数据
|
||||
while (true) {
|
||||
const { value, done } = await reader.value.read()
|
||||
if (done) {
|
||||
// 允许稍后关闭串口
|
||||
reader.value.releaseLock()
|
||||
break
|
||||
}
|
||||
// 获取发送的数据
|
||||
const serialData = new TextDecoder().decode(value)
|
||||
data = `${data}${serialData}`
|
||||
if (serialData.includes('\r')) {
|
||||
//读取结束
|
||||
let codeData = data.replace('\r', '')
|
||||
data = '' //清空下次读取不会叠加
|
||||
console.log(`二维码数据:${codeData}`)
|
||||
//处理拿到数据逻辑
|
||||
pickUpForm.value.open(codeData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 断开扫码枪 */
|
||||
const cutPort = async () => {
|
||||
if (port.value !== '') {
|
||||
await reader.value.cancel()
|
||||
await port.value.close()
|
||||
port.value = ''
|
||||
console.log('断开扫码枪连接')
|
||||
message.success('已成功断开扫码枪连接')
|
||||
serialPort.value = false
|
||||
} else {
|
||||
message.warning('请先连接或打开扫码枪')
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
getPickUpStoreList()
|
||||
onMounted(async () => {
|
||||
await getPickUpStoreList()
|
||||
if (pickUpStoreList.value.length === 0) {
|
||||
message.error('当前登录人没绑定任何自提点')
|
||||
loading.value = false
|
||||
isUse.value = true
|
||||
return
|
||||
}
|
||||
|
||||
// 查询
|
||||
queryParams.value.pickUpStoreId = pickUpStoreList.value[0].id
|
||||
isUse.value = false
|
||||
await getList()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible" width="20%">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="门店名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入门店名称" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="门店店员" prop="verifyUserIds">
|
||||
<el-button type="primary" @click="storeStaffTableSelect.open()">选择店员</el-button>
|
||||
</el-form-item>
|
||||
<!-- 店员列表 -->
|
||||
<ContentWrap v-if="formData.verifyUsers.length > 0">
|
||||
<el-table :data="formData.verifyUsers">
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<el-table-column
|
||||
label="用户昵称"
|
||||
align="center"
|
||||
prop="nickname"
|
||||
:show-overflow-tooltip="true"
|
||||
/>
|
||||
<el-table-column label="状态" align="center" key="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="操作">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['trade:delivery:pick-up-store:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ContentWrap>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<!-- 选择员工弹窗 -->
|
||||
<StoreStaffTableSelect ref="storeStaffTableSelect" @change="handleSelect" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as DeliveryPickUpStoreApi from '@/api/mall/trade/delivery/pickUpStore'
|
||||
import StoreStaffTableSelect from './components/StoreStaffTableSelect.vue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: '',
|
||||
verifyUserIds: [],
|
||||
verifyUsers: []
|
||||
})
|
||||
const formRules = reactive({})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const storeStaffTableSelect = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = '绑定自提门店员工'
|
||||
resetForm()
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await DeliveryPickUpStoreApi.getDeliveryPickUpStore(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = {
|
||||
id: formData.value.id,
|
||||
verifyUserIds: formData.value.verifyUsers.map((item: any) => item.id)
|
||||
}
|
||||
await DeliveryPickUpStoreApi.bindStoreStaffId(data)
|
||||
message.success('绑定成功')
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理选择员工操作 */
|
||||
const handleSelect = (checkedUsers: []) => {
|
||||
formData.value.verifyUsers = checkedUsers
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
const index = formData.value.verifyUsers.findIndex((item: any) => {
|
||||
if (item.id == id) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
formData.value.verifyUsers.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: '',
|
||||
verifyUserIds: [],
|
||||
verifyUsers: []
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,265 @@
|
|||
<!-- TODO 芋艿:这块后续抽个独立的组件出来 -->
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible" width="60%">
|
||||
<el-row :gutter="20">
|
||||
<!-- 左侧部门树 -->
|
||||
<el-col :span="4" :xs="24">
|
||||
<ContentWrap class="h-1/1">
|
||||
<DeptTree @node-click="handleDeptNodeClick" />
|
||||
</ContentWrap>
|
||||
</el-col>
|
||||
<el-col :span="20" :xs="24">
|
||||
<!-- 搜索 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="用户名称" prop="username">
|
||||
<el-input
|
||||
v-model="queryParams.username"
|
||||
placeholder="请输入用户名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号码" prop="mobile">
|
||||
<el-input
|
||||
v-model="queryParams.mobile"
|
||||
placeholder="请输入手机号码"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="用户状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="datetimerange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column width="55">
|
||||
<template #header>
|
||||
<el-checkbox
|
||||
v-model="isCheckAll"
|
||||
:indeterminate="isIndeterminate"
|
||||
@change="handleCheckAll"
|
||||
/>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<el-checkbox
|
||||
v-model="checkedStatus[row.id]"
|
||||
@change="(checked: boolean) => handleCheckOne(checked, row, true)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用户编号" align="center" key="id" prop="id" />
|
||||
<el-table-column
|
||||
label="用户名称"
|
||||
align="center"
|
||||
prop="username"
|
||||
:show-overflow-tooltip="true"
|
||||
/>
|
||||
<el-table-column
|
||||
label="用户昵称"
|
||||
align="center"
|
||||
prop="nickname"
|
||||
:show-overflow-tooltip="true"
|
||||
/>
|
||||
<el-table-column
|
||||
label="部门"
|
||||
align="center"
|
||||
key="deptName"
|
||||
prop="deptName"
|
||||
:show-overflow-tooltip="true"
|
||||
/>
|
||||
<el-table-column label="手机号码" align="center" prop="mobile" width="120" />
|
||||
<el-table-column label="状态" key="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180"
|
||||
/>
|
||||
</el-table>
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="handleEmitChange">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import DeptTree from '@/views/system/user/DeptTree.vue'
|
||||
|
||||
// 是否全选
|
||||
const isCheckAll = ref(false)
|
||||
// 全选框是否处于中间状态:不是全部选中 && 任意一个选中
|
||||
const isIndeterminate = ref(false)
|
||||
// 选中的活动
|
||||
const checkedUsers = ref([])
|
||||
// 选中状态:key为用户ID,value为是否选中
|
||||
const checkedStatus = ref<Record<string, boolean>>({})
|
||||
|
||||
const dialogTitle = '选择店员'
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
username: undefined,
|
||||
mobile: undefined,
|
||||
status: undefined,
|
||||
deptId: undefined,
|
||||
roleId: 5,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await UserApi.getUserPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 处理部门被点击 */
|
||||
const handleDeptNodeClick = async (row) => {
|
||||
queryParams.deptId = row.id
|
||||
await getList()
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async () => {
|
||||
dialogVisible.value = true
|
||||
loading.value = true
|
||||
try {
|
||||
await getList()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 全选/全不选 */
|
||||
const handleCheckAll = (checked: boolean) => {
|
||||
isCheckAll.value = checked
|
||||
isIndeterminate.value = false
|
||||
|
||||
list.value.forEach((combinationActivity) => handleCheckOne(checked, combinationActivity, false))
|
||||
}
|
||||
|
||||
/**
|
||||
* 选中一行
|
||||
* @param checked 是否选中
|
||||
* @param combinationActivity 活动
|
||||
* @param isCalcCheckAll 是否计算全选
|
||||
*/
|
||||
const handleCheckOne = (checked: boolean, combinationActivity, isCalcCheckAll: boolean) => {
|
||||
if (checked) {
|
||||
checkedUsers.value.push(combinationActivity as never)
|
||||
checkedStatus.value[combinationActivity.id] = true
|
||||
} else {
|
||||
const index = findCheckedIndex(combinationActivity)
|
||||
if (index > -1) {
|
||||
checkedUsers.value.splice(index, 1)
|
||||
checkedStatus.value[combinationActivity.id] = false
|
||||
isCheckAll.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 计算全选框状态
|
||||
if (isCalcCheckAll) {
|
||||
calculateIsCheckAll()
|
||||
}
|
||||
}
|
||||
|
||||
// 查找活动在已选中活动列表中的索引
|
||||
const findCheckedIndex = (user) => checkedUsers.value.findIndex((item) => item.id === user.id)
|
||||
|
||||
// 计算全选框状态
|
||||
const calculateIsCheckAll = () => {
|
||||
isCheckAll.value = list.value.every((user) => checkedStatus.value[user.id])
|
||||
// 计算中间状态:不是全部选中 && 任意一个选中
|
||||
isIndeterminate.value =
|
||||
!isCheckAll.value && list.value.some((user) => checkedStatus.value[user.id])
|
||||
}
|
||||
|
||||
/** 多选完成 */
|
||||
const handleEmitChange = () => {
|
||||
// 关闭弹窗
|
||||
dialogVisible.value = false
|
||||
emits('change', [...checkedUsers.value])
|
||||
}
|
||||
|
||||
/** 确认选择时的触发事件 */
|
||||
const emits = defineEmits<{
|
||||
change: [CombinationActivityApi: any]
|
||||
}>()
|
||||
</script>
|
|
@ -93,7 +93,7 @@
|
|||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="操作">
|
||||
<el-table-column align="center" label="操作" min-width="110">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['trade:delivery:pick-up-store:update']"
|
||||
|
@ -103,6 +103,14 @@
|
|||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['trade:delivery:pick-up-store:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openFormBind(scope.row.id)"
|
||||
>
|
||||
绑定店员
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['trade:delivery:pick-up-store:delete']"
|
||||
link
|
||||
|
@ -115,12 +123,16 @@
|
|||
</el-table-column>
|
||||
</el-table>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<DeliveryPickUpStoreForm ref="formRef" @success="getList" />
|
||||
<!-- 表单弹窗:绑定店员 -->
|
||||
<DeliveryPickUpStoreBindForm ref="formBindRef" />
|
||||
</template>
|
||||
<script lang="ts" name="DeliveryPickUpStore" setup>
|
||||
import * as DeliveryPickUpStoreApi from '@/api/mall/trade/delivery/pickUpStore'
|
||||
import DeliveryPickUpStoreForm from './PickUpStoreForm.vue'
|
||||
import DeliveryPickUpStoreBindForm from './DeliveryPickUpStoreBindForm.vue'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
|
||||
|
@ -146,6 +158,11 @@ const openForm = (type: string, id?: number) => {
|
|||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
const formBindRef = ref()
|
||||
const openFormBind = (id?: number) => {
|
||||
formBindRef.value.open(id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button type="primary" :disabled="formLoading" @click="getOrderByPickUpVerifyCode">
|
||||
<el-button type="primary" :disabled="formLoading" @click="getOrderByPickUpVerifyCodeClick">
|
||||
查询
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
|
@ -52,9 +52,14 @@ const formRef = ref() // 表单 Ref
|
|||
const orderDetails = ref<OrderVO>({})
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async () => {
|
||||
const open = async (pickUpVerifyCode: string) => {
|
||||
resetForm()
|
||||
dialogVisible.value = true
|
||||
if(pickUpVerifyCode != null){
|
||||
formData.value.pickUpVerifyCode = pickUpVerifyCode;
|
||||
await getOrderByPickUpVerifyCode()
|
||||
}else{
|
||||
dialogVisible.value = true
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
|
@ -83,18 +88,21 @@ const resetForm = () => {
|
|||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 查询核销码对应的订单 */
|
||||
const getOrderByPickUpVerifyCode = async () => {
|
||||
const getOrderByPickUpVerifyCodeClick = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
await getOrderByPickUpVerifyCode()
|
||||
}
|
||||
|
||||
/** 查询核销码对应的订单 */
|
||||
const getOrderByPickUpVerifyCode = async () => {
|
||||
formLoading.value = true
|
||||
const data = await TradeOrderApi.getOrderByPickUpVerifyCode(formData.value.pickUpVerifyCode)
|
||||
formLoading.value = false
|
||||
if (data?.deliveryType !== DeliveryTypeEnum.PICK_UP.type) {
|
||||
message.error('请输入正确的核销码')
|
||||
message.error('未查询到订单')
|
||||
return
|
||||
}
|
||||
if (data?.status !== TradeOrderStatusEnum.UNDELIVERED.status) {
|
||||
|
|
|
@ -351,7 +351,7 @@ const deliveryExpressList = ref<DeliveryExpressApi.DeliveryExpressVO[]>([]) //
|
|||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
pickUpStoreList.value = await PickUpStoreApi.getListAllSimple()
|
||||
pickUpStoreList.value = await PickUpStoreApi.getSimpleDeliveryPickUpStoreList()
|
||||
deliveryExpressList.value = await DeliveryExpressApi.getSimpleDeliveryExpressList()
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -273,7 +273,7 @@ const openDetail = (id: number) => {
|
|||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
pickUpStoreList.value = await PickUpStoreApi.getListAllSimple()
|
||||
pickUpStoreList.value = await PickUpStoreApi.getSimpleDeliveryPickUpStoreList()
|
||||
deliveryExpressList.value = await DeliveryExpressApi.getSimpleDeliveryExpressList()
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -30,6 +30,9 @@
|
|||
<el-form-item label="退款结果的回调地址" prop="refundNotifyUrl">
|
||||
<el-input v-model="formData.refundNotifyUrl" placeholder="请输入退款结果的回调地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="转账结果的回调地址" prop="transferNotifyUrl">
|
||||
<el-input v-model="formData.transferNotifyUrl" placeholder="请输入转账结果的回调地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
|
@ -62,7 +65,8 @@ const formData = ref({
|
|||
status: CommonStatusEnum.ENABLE,
|
||||
remark: undefined,
|
||||
orderNotifyUrl: undefined,
|
||||
refundNotifyUrl: undefined
|
||||
refundNotifyUrl: undefined,
|
||||
transferNotifyUrl: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '应用名不能为空', trigger: 'blur' }],
|
||||
|
@ -126,6 +130,7 @@ const resetForm = () => {
|
|||
remark: undefined,
|
||||
orderNotifyUrl: undefined,
|
||||
refundNotifyUrl: undefined,
|
||||
transferNotifyUrl: undefined,
|
||||
appKey: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
|
|
|
@ -257,7 +257,6 @@ const resetForm = (appId, code) => {
|
|||
const fileBeforeUpload = (file, fileAccept) => {
|
||||
let format = '.' + file.name.split('.')[1]
|
||||
if (format !== fileAccept) {
|
||||
debugger
|
||||
message.error('请上传指定格式"' + fileAccept + '"文件')
|
||||
return false
|
||||
}
|
||||
|
|