冲突修改 Merge branch 'feature/bpm' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into feature/bpm

pull/669/head
jason 2025-01-19 20:45:27 +08:00
commit aeb096a311
58 changed files with 1591 additions and 2039 deletions

View File

@ -38,7 +38,7 @@
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^1.6.8", "axios": "^1.6.8",
"benz-amr-recorder": "^1.1.5", "benz-amr-recorder": "^1.1.5",
"bpmn-js-token-simulation": "^0.10.0", "bpmn-js-token-simulation": "^0.36.0",
"camunda-bpmn-moddle": "^7.0.1", "camunda-bpmn-moddle": "^7.0.1",
"cropperjs": "^1.6.1", "cropperjs": "^1.6.1",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
@ -47,7 +47,7 @@
"driver.js": "^1.3.1", "driver.js": "^1.3.1",
"echarts": "^5.5.0", "echarts": "^5.5.0",
"echarts-wordcloud": "^2.1.0", "echarts-wordcloud": "^2.1.0",
"element-plus": "2.8.4", "element-plus": "2.9.1",
"fast-xml-parser": "^4.3.2", "fast-xml-parser": "^4.3.2",
"highlight.js": "^11.9.0", "highlight.js": "^11.9.0",
"jsencrypt": "^3.3.2", "jsencrypt": "^3.3.2",

View File

@ -45,8 +45,8 @@ dependencies:
specifier: ^1.1.5 specifier: ^1.1.5
version: 1.1.5 version: 1.1.5
bpmn-js-token-simulation: bpmn-js-token-simulation:
specifier: ^0.10.0 specifier: ^0.36.0
version: 0.10.0 version: 0.36.0
camunda-bpmn-moddle: camunda-bpmn-moddle:
specifier: ^7.0.1 specifier: ^7.0.1
version: 7.0.1 version: 7.0.1
@ -72,8 +72,8 @@ dependencies:
specifier: ^2.1.0 specifier: ^2.1.0
version: 2.1.0(echarts@5.5.1) version: 2.1.0(echarts@5.5.1)
element-plus: element-plus:
specifier: 2.8.4 specifier: 2.9.1
version: 2.8.4(vue@3.5.12) version: 2.9.1(vue@3.5.12)
fast-xml-parser: fast-xml-parser:
specifier: ^4.3.2 specifier: ^4.3.2
version: 4.5.0 version: 4.5.0
@ -149,6 +149,9 @@ dependencies:
vue-types: vue-types:
specifier: ^5.1.1 specifier: ^5.1.1
version: 5.1.3(vue@3.5.12) version: 5.1.3(vue@3.5.12)
vue3-signature:
specifier: ^0.2.4
version: 0.2.4(vue@3.5.12)
vuedraggable: vuedraggable:
specifier: ^4.1.0 specifier: ^4.1.0
version: 4.1.0(vue@3.5.12) version: 4.1.0(vue@3.5.12)
@ -2122,7 +2125,7 @@ packages:
'@form-create/element-ui': 3.2.14(vue@3.5.12) '@form-create/element-ui': 3.2.14(vue@3.5.12)
'@form-create/utils': 3.2.14 '@form-create/utils': 3.2.14
codemirror: 6.65.7 codemirror: 6.65.7
element-plus: 2.8.4(vue@3.5.12) element-plus: 2.9.1(vue@3.5.12)
vue: 3.5.12(typescript@5.3.3) vue: 3.5.12(typescript@5.3.3)
vuedraggable: 4.1.0(vue@3.5.12) vuedraggable: 4.1.0(vue@3.5.12)
transitivePeerDependencies: transitivePeerDependencies:
@ -4581,12 +4584,14 @@ packages:
min-dom: 4.2.1 min-dom: 4.2.1
dev: true dev: true
/bpmn-js-token-simulation@0.10.0: /bpmn-js-token-simulation@0.36.0:
resolution: {integrity: sha512-QuZQ/KVXKt9Vl+XENyOBoTW2Aw+uKjuBlKdCJL6El7AyM7DkJ5bZkSYURshId1SkBDdYg2mJ1flSmsrhGuSfwg==, tarball: https://registry.npmmirror.com/bpmn-js-token-simulation/-/bpmn-js-token-simulation-0.10.0.tgz} resolution: {integrity: sha512-vz+RHlbZCev/6dzk6FhJRz8M0aZ1GL7Xrza0ecWqeg4tHbgPozgyOm3tXTz75XdtOwRVVBzmCjcciXQX7A55wQ==, tarball: https://registry.npmmirror.com/bpmn-js-token-simulation/-/bpmn-js-token-simulation-0.36.0.tgz}
engines: {node: '>= 16'}
dependencies: dependencies:
min-dash: 3.8.1 inherits-browser: 0.1.0
min-dom: 0.2.0 min-dash: 4.2.2
svg.js: 2.7.1 min-dom: 4.2.1
randomcolor: 0.6.2
dev: false dev: false
/bpmn-js@17.11.1: /bpmn-js@17.11.1:
@ -4927,51 +4932,13 @@ packages:
dot-prop: 5.3.0 dot-prop: 5.3.0
dev: true dev: true
/component-classes@1.2.6:
resolution: {integrity: sha512-hPFGULxdwugu1QWW3SvVOCUHLzO34+a2J6Wqy0c5ASQkfi9/8nZcBB0ZohaEbXOQlCflMAEMmEWk7u7BVs4koA==, tarball: https://registry.npmmirror.com/component-classes/-/component-classes-1.2.6.tgz}
dependencies:
component-indexof: 0.0.3
dev: false
/component-closest@0.1.4:
resolution: {integrity: sha512-NF9hMj6JKGM5sb6wP/dg7GdJOttaIH9PcTsUNdWcrvu7Kw/5R5swQAFpgaYEHlARrNMyn4Wf7O1PlRej+pt76Q==, tarball: https://registry.npmmirror.com/component-closest/-/component-closest-0.1.4.tgz}
dependencies:
component-matches-selector: 0.1.7
dev: false
/component-delegate@0.2.4:
resolution: {integrity: sha512-OlpcB/6Fi+kXQPh/TfXnSvvmrU04ghz7vcJh/jgLF0Ni+I+E3WGlKJQbBGDa5X+kVUG8WxOgjP+8iWbz902fPg==, tarball: https://registry.npmmirror.com/component-delegate/-/component-delegate-0.2.4.tgz}
dependencies:
component-closest: 0.1.4
component-event: 0.1.4
dev: false
/component-emitter@1.3.1: /component-emitter@1.3.1:
resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==, tarball: https://registry.npmmirror.com/component-emitter/-/component-emitter-1.3.1.tgz} resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==, tarball: https://registry.npmmirror.com/component-emitter/-/component-emitter-1.3.1.tgz}
dev: true dev: true
/component-event@0.1.4:
resolution: {integrity: sha512-GMwOG8MnUHP1l8DZx1ztFO0SJTFnIzZnBDkXAj8RM2ntV2A6ALlDxgbMY1Fvxlg6WPQ+5IM/a6vg4PEYbjg/Rw==, tarball: https://registry.npmmirror.com/component-event/-/component-event-0.1.4.tgz}
dev: false
/component-event@0.2.1: /component-event@0.2.1:
resolution: {integrity: sha512-wGA++isMqiDq1jPYeyv2as/Bt/u+3iLW0rEa+8NQ82jAv3TgqMiCM+B2SaBdn2DfLilLjjq736YcezihRYhfxw==, tarball: https://registry.npmmirror.com/component-event/-/component-event-0.2.1.tgz} resolution: {integrity: sha512-wGA++isMqiDq1jPYeyv2as/Bt/u+3iLW0rEa+8NQ82jAv3TgqMiCM+B2SaBdn2DfLilLjjq736YcezihRYhfxw==, tarball: https://registry.npmmirror.com/component-event/-/component-event-0.2.1.tgz}
/component-indexof@0.0.3:
resolution: {integrity: sha512-puDQKvx/64HZXb4hBwIcvQLaLgux8o1CbWl39s41hrIIZDl1lJiD5jc22gj3RBeGK0ovxALDYpIbyjqDUUl0rw==, tarball: https://registry.npmmirror.com/component-indexof/-/component-indexof-0.0.3.tgz}
dev: false
/component-matches-selector@0.1.7:
resolution: {integrity: sha512-Yb2+pVBvrqkQVpPaDBF0DYXRreBveXJNrpJs9FnFu8PF6/5IIcz5oDZqiH9nB5hbD2/TmFVN5ZCxBzqu7yFFYQ==, tarball: https://registry.npmmirror.com/component-matches-selector/-/component-matches-selector-0.1.7.tgz}
dependencies:
component-query: 0.0.3
global-object: 1.0.0
dev: false
/component-query@0.0.3:
resolution: {integrity: sha512-VgebQseT1hz1Ps7vVp2uaSg+N/gsI5ts3AZUSnN6GMA2M82JH7o+qYifWhmVE/e8w/H48SJuA3nA9uX8zRe95Q==, tarball: https://registry.npmmirror.com/component-query/-/component-query-0.0.3.tgz}
dev: false
/compute-scroll-into-view@1.0.20: /compute-scroll-into-view@1.0.20:
resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==, tarball: https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz} resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==, tarball: https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz}
dev: false dev: false
@ -5521,6 +5488,10 @@ packages:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, tarball: https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz} resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, tarball: https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz}
dev: true dev: true
/default-passive-events@2.0.0:
resolution: {integrity: sha512-eMtt76GpDVngZQ3ocgvRcNCklUMwID1PaNbCNxfpDXuiOXttSh0HzBbda1HU9SIUsDc02vb7g9+3I5tlqe/qMQ==, tarball: https://registry.npmmirror.com/default-passive-events/-/default-passive-events-2.0.0.tgz}
dev: false
/define-data-property@1.1.4: /define-data-property@1.1.4:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==, tarball: https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz} resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==, tarball: https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -5795,8 +5766,8 @@ packages:
resolution: {integrity: sha512-nz88NNBsD7kQSAGGJyp8hS6xSPtWwqNogA0mjtc2nUYeEf3nURK9qpV18TuBdDmEDgVWotS8Wkzf+V52dSQ/LQ==, tarball: https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.67.tgz} resolution: {integrity: sha512-nz88NNBsD7kQSAGGJyp8hS6xSPtWwqNogA0mjtc2nUYeEf3nURK9qpV18TuBdDmEDgVWotS8Wkzf+V52dSQ/LQ==, tarball: https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.67.tgz}
dev: true dev: true
/element-plus@2.8.4(vue@3.5.12): /element-plus@2.9.1(vue@3.5.12):
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-9Agqf/jt4Ugk7EZ6C5LME71sgkvauPCsnvJN12Xid2XVobjufxMGpRE4L7pS4luJMOmFAH3J0NgYEGZT5r+NDg==, tarball: https://registry.npmmirror.com/element-plus/-/element-plus-2.9.1.tgz}
peerDependencies: peerDependencies:
vue: ^3.2.0 vue: ^3.2.0
dependencies: dependencies:
@ -6674,10 +6645,6 @@ packages:
global-prefix: 3.0.0 global-prefix: 3.0.0
dev: true dev: true
/global-object@1.0.0:
resolution: {integrity: sha512-mSPSkY6UsHv6hgW0V2dfWBWTS8TnPnLx3ECVNoWp6rBI2Bg66VYoqGoTFlH/l7XhAZ/l+StYlntXlt87BEeCcg==, tarball: https://registry.npmmirror.com/global-object/-/global-object-1.0.0.tgz}
dev: false
/global-prefix@3.0.0: /global-prefix@3.0.0:
resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==, tarball: https://registry.npmmirror.com/global-prefix/-/global-prefix-3.0.0.tgz} resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==, tarball: https://registry.npmmirror.com/global-prefix/-/global-prefix-3.0.0.tgz}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -7899,10 +7866,6 @@ packages:
engines: {node: '>=18'} engines: {node: '>=18'}
dev: true dev: true
/min-dash@3.8.1:
resolution: {integrity: sha512-evumdlmIlg9mbRVPbC4F5FuRhNmcMS5pvuBUbqb1G9v09Ro0ImPEgz5n3khir83lFok1inKqVDjnKEg3GpDxQg==, tarball: https://registry.npmmirror.com/min-dash/-/min-dash-3.8.1.tgz}
dev: false
/min-dash@4.2.2: /min-dash@4.2.2:
resolution: {integrity: sha512-qbhSYUxk6mBaF096B3JOQSumXbKWHenmT97cSpdNzgkWwGjhjhE/KZODCoDNhI2I4C9Cb6R/Q13S4BYkUSXoXQ==, tarball: https://registry.npmmirror.com/min-dash/-/min-dash-4.2.2.tgz} resolution: {integrity: sha512-qbhSYUxk6mBaF096B3JOQSumXbKWHenmT97cSpdNzgkWwGjhjhE/KZODCoDNhI2I4C9Cb6R/Q13S4BYkUSXoXQ==, tarball: https://registry.npmmirror.com/min-dash/-/min-dash-4.2.2.tgz}
@ -7912,18 +7875,6 @@ packages:
dom-walk: 0.1.2 dom-walk: 0.1.2
dev: false dev: false
/min-dom@0.2.0:
resolution: {integrity: sha512-VmxugbnAcVZGqvepjhOA4d4apmrpX8mMaRS+/jo0dI5Yorzrr4Ru9zc9KVALlY/+XakVCb8iQ+PYXljihQcsNw==, tarball: https://registry.npmmirror.com/min-dom/-/min-dom-0.2.0.tgz}
dependencies:
component-classes: 1.2.6
component-closest: 0.1.4
component-delegate: 0.2.4
component-event: 0.1.4
component-matches-selector: 0.1.7
component-query: 0.0.3
domify: 1.4.2
dev: false
/min-dom@4.2.1: /min-dom@4.2.1:
resolution: {integrity: sha512-TMoL8SEEIhUWYgkj7XMSgxmwSyGI+4fP2KFFGnN3FbHfbGHVdsLYSz8LoIsgPhz4dWRmLvxWWSMgzZMJW5sZuA==, tarball: https://registry.npmmirror.com/min-dom/-/min-dom-4.2.1.tgz} resolution: {integrity: sha512-TMoL8SEEIhUWYgkj7XMSgxmwSyGI+4fP2KFFGnN3FbHfbGHVdsLYSz8LoIsgPhz4dWRmLvxWWSMgzZMJW5sZuA==, tarball: https://registry.npmmirror.com/min-dom/-/min-dom-4.2.1.tgz}
dependencies: dependencies:
@ -8714,6 +8665,10 @@ packages:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, tarball: https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, tarball: https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz}
dev: true dev: true
/randomcolor@0.6.2:
resolution: {integrity: sha512-Mn6TbyYpFgwFuQ8KJKqf3bqqY9O1y37/0jgSK/61PUxV4QfIMv0+K2ioq8DfOjkBslcjwSzRfIDEXfzA9aCx7A==, tarball: https://registry.npmmirror.com/randomcolor/-/randomcolor-0.6.2.tgz}
dev: false
/rd@2.0.1: /rd@2.0.1:
resolution: {integrity: sha512-/XdKU4UazUZTXFmI0dpABt8jSXPWcEyaGdk340KdHnsEOdkTctlX23aAK7ChQDn39YGNlAJr1M5uvaKt4QnpNw==, tarball: https://registry.npmmirror.com/rd/-/rd-2.0.1.tgz} resolution: {integrity: sha512-/XdKU4UazUZTXFmI0dpABt8jSXPWcEyaGdk340KdHnsEOdkTctlX23aAK7ChQDn39YGNlAJr1M5uvaKt4QnpNw==, tarball: https://registry.npmmirror.com/rd/-/rd-2.0.1.tgz}
dependencies: dependencies:
@ -9128,6 +9083,10 @@ packages:
engines: {node: '>=14'} engines: {node: '>=14'}
dev: true dev: true
/signature_pad@3.0.0-beta.4:
resolution: {integrity: sha512-cOf2NhVuTiuNqe2X/ycEmizvCDXk0DoemhsEpnkcGnA4kS5iJYTCqZ9As7tFBbsch45Q1EdX61833+6sjJ8rrw==, tarball: https://registry.npmmirror.com/signature_pad/-/signature_pad-3.0.0-beta.4.tgz}
dev: false
/sirv@2.0.4: /sirv@2.0.4:
resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==, tarball: https://registry.npmmirror.com/sirv/-/sirv-2.0.4.tgz} resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==, tarball: https://registry.npmmirror.com/sirv/-/sirv-2.0.4.tgz}
engines: {node: '>= 10'} engines: {node: '>= 10'}
@ -9561,10 +9520,6 @@ packages:
resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==, tarball: https://registry.npmmirror.com/svg-tags/-/svg-tags-1.0.0.tgz} resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==, tarball: https://registry.npmmirror.com/svg-tags/-/svg-tags-1.0.0.tgz}
dev: true dev: true
/svg.js@2.7.1:
resolution: {integrity: sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==, tarball: https://registry.npmmirror.com/svg.js/-/svg.js-2.7.1.tgz}
dev: false
/svgo@2.8.0: /svgo@2.8.0:
resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==, tarball: https://registry.npmmirror.com/svgo/-/svgo-2.8.0.tgz} resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==, tarball: https://registry.npmmirror.com/svgo/-/svgo-2.8.0.tgz}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
@ -10324,6 +10279,16 @@ packages:
vue: 3.5.12(typescript@5.3.3) vue: 3.5.12(typescript@5.3.3)
dev: false dev: false
/vue3-signature@0.2.4(vue@3.5.12):
resolution: {integrity: sha512-XFwwFVK9OG3F085pKIq2SlNVqx32WdFH+TXbGEWc5FfEKpx8oMmZuAwZZ50K/pH2FgmJSE8IRwU9DDhrLpd6iA==, tarball: https://registry.npmmirror.com/vue3-signature/-/vue3-signature-0.2.4.tgz}
peerDependencies:
vue: ^3.2.0
dependencies:
default-passive-events: 2.0.0
signature_pad: 3.0.0-beta.4
vue: 3.5.12(typescript@5.3.3)
dev: false
/vue@3.5.12(typescript@5.3.3): /vue@3.5.12(typescript@5.3.3):
resolution: {integrity: sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==, tarball: https://registry.npmmirror.com/vue/-/vue-3.5.12.tgz} resolution: {integrity: sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==, tarball: https://registry.npmmirror.com/vue/-/vue-3.5.12.tgz}
peerDependencies: peerDependencies:

View File

@ -72,3 +72,7 @@ export const deleteModel = async (id: number) => {
export const deployModel = async (id: number) => { export const deployModel = async (id: number) => {
return await request.post({ url: '/bpm/model/deploy?id=' + id }) return await request.post({ url: '/bpm/model/deploy?id=' + id })
} }
export const cleanModel = async (id: number) => {
return await request.delete({ url: '/bpm/model/clean?id=' + id })
}

View File

@ -36,6 +36,7 @@ export type ApprovalTaskInfo = {
assigneeUser: User assigneeUser: User
status: number status: number
reason: string reason: string
signPicUrl: string
} }
// 审批节点信息 // 审批节点信息
@ -89,7 +90,7 @@ export const getProcessInstanceCopyPage = async (params: any) => {
// 获取审批详情 // 获取审批详情
export const getApprovalDetail = async (params: any) => { export const getApprovalDetail = async (params: any) => {
return await request.get({ url: 'bpm/process-instance/get-approval-detail' , params }) return await request.get({ url: 'bpm/process-instance/get-approval-detail', params })
} }
// 获取表单字段权限 // 获取表单字段权限

View File

@ -22,11 +22,6 @@ export const register = (data: RegisterVO) => {
return request.post({ url: '/system/auth/register', data }) return request.post({ url: '/system/auth/register', data })
} }
// 刷新访问令牌
export const refreshToken = () => {
return request.post({ url: '/system/auth/refresh-token?refreshToken=' + getRefreshToken() })
}
// 使用租户名,获得租户编号 // 使用租户名,获得租户编号
export const getTenantIdByName = (name: string) => { export const getTenantIdByName = (name: string) => {
return request.get({ url: '/system/tenant/get-id-by-name?name=' + name }) return request.get({ url: '/system/tenant/get-id-by-name?name=' + name })
@ -76,12 +71,17 @@ export const socialAuthRedirect = (type: number, redirectUri: string) => {
}) })
} }
// 获取验证图片以及 token // 获取验证图片以及 token
export const getCode = (data) => { export const getCode = (data: any) => {
debugger debugger
return request.postOriginal({ url: 'system/captcha/get', data }) return request.postOriginal({ url: 'system/captcha/get', data })
} }
// 滑动或者点选验证 // 滑动或者点选验证
export const reqCheck = (data) => { export const reqCheck = (data: any) => {
return request.postOriginal({ url: 'system/captcha/check', data }) return request.postOriginal({ url: 'system/captcha/check', data })
} }
// 通过短信重置密码
export const smsResetPassword = (data: any) => {
return request.post({ url: '/system/auth/sms-reset-password', data })
}

View File

@ -9,6 +9,10 @@ import { useAppStore } from '@/store/modules/app'
import { isString } from '@/utils/is' import { isString } from '@/utils/is'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import 'echarts/lib/component/markPoint'
import 'echarts/lib/component/markLine'
import 'echarts/lib/component/markArea'
defineOptions({ name: 'EChart' }) defineOptions({ name: 'EChart' })
const { getPrefixCls, variables } = useDesign() const { getPrefixCls, variables } = useDesign()

View File

@ -79,9 +79,14 @@ function remoteMethod(data) {
function handleChange(path) { function handleChange(path) {
router.push({ path }) router.push({ path })
hiddenSearch()
hiddenTopSearch() hiddenTopSearch()
} }
function hiddenSearch() {
showSearch.value = false
}
function hiddenTopSearch() { function hiddenTopSearch() {
showTopSearch.value = false showTopSearch.value = false
} }

View File

@ -46,7 +46,7 @@
</div> </div>
<div class="handler-item-text">延迟器</div> <div class="handler-item-text">延迟器</div>
</div> </div>
<div class="handler-item" @click="addNode(NodeType.ROUTE_BRANCH_NODE)"> <div class="handler-item" @click="addNode(NodeType.ROUTER_BRANCH_NODE)">
<!-- TODO @芋艿 需要更换一下iconfont的图标 --> <!-- TODO @芋艿 需要更换一下iconfont的图标 -->
<div class="handler-item-icon copy"> <div class="handler-item-icon copy">
<span class="iconfont icon-size icon-copy"></span> <span class="iconfont icon-size icon-copy"></span>
@ -67,12 +67,13 @@ import {
ApproveMethodType, ApproveMethodType,
AssignEmptyHandlerType, AssignEmptyHandlerType,
AssignStartUserHandlerType, AssignStartUserHandlerType,
ConditionType,
NODE_DEFAULT_NAME, NODE_DEFAULT_NAME,
NodeType, NodeType,
RejectHandlerType, RejectHandlerType,
SimpleFlowNode SimpleFlowNode
} from './consts' } from './consts'
import { generateUUID } from '@/utils' import {generateUUID} from '@/utils'
defineOptions({ defineOptions({
name: 'NodeHandler' name: 'NodeHandler'
@ -164,9 +165,24 @@ const addNode = (type: number) => {
type: NodeType.CONDITION_NODE, type: NodeType.CONDITION_NODE,
childNode: undefined, childNode: undefined,
conditionSetting: { conditionSetting: {
defaultFlow: false defaultFlow: false,
}, conditionType: ConditionType.RULE,
conditionGroups: {
and: true,
conditions: [
{
and: true,
rules: [
{
opCode: '==',
leftSide: '',
rightSide: ''
}
]
}
]
}
}
}, },
{ {
id: 'Flow_' + generateUUID(), id: 'Flow_' + generateUUID(),
@ -221,9 +237,24 @@ const addNode = (type: number) => {
type: NodeType.CONDITION_NODE, type: NodeType.CONDITION_NODE,
childNode: undefined, childNode: undefined,
conditionSetting: { conditionSetting: {
defaultFlow: false defaultFlow: false,
conditionType: ConditionType.RULE,
conditionGroups: {
and: true,
conditions: [
{
and: true,
rules: [
{
opCode: '==',
leftSide: '',
rightSide: ''
}
]
}
]
}
} }
}, },
{ {
id: 'Flow_' + generateUUID(), id: 'Flow_' + generateUUID(),
@ -249,14 +280,13 @@ const addNode = (type: number) => {
} }
emits('update:childNode', data) emits('update:childNode', data)
} }
if (type === NodeType.ROUTE_BRANCH_NODE) { if (type === NodeType.ROUTER_BRANCH_NODE) {
const data: SimpleFlowNode = { const data: SimpleFlowNode = {
id: 'GateWay_' + generateUUID(), id: 'GateWay_' + generateUUID(),
name: NODE_DEFAULT_NAME.get(NodeType.ROUTE_BRANCH_NODE) as string, name: NODE_DEFAULT_NAME.get(NodeType.ROUTER_BRANCH_NODE) as string,
showText: '', showText: '',
type: NodeType.ROUTE_BRANCH_NODE, type: NodeType.ROUTER_BRANCH_NODE,
childNode: props.childNode, childNode: props.childNode
defaultFlowId: 'Flow_' + generateUUID()
} }
emits('update:childNode', data) emits('update:childNode', data)
} }

View File

@ -45,8 +45,8 @@
@update:flow-node="handleModelValueUpdate" @update:flow-node="handleModelValueUpdate"
/> />
<!-- 路由分支节点 --> <!-- 路由分支节点 -->
<RouteNode <RouterNode
v-if="currentNode && currentNode.type === NodeType.ROUTE_BRANCH_NODE" v-if="currentNode && currentNode.type === NodeType.ROUTER_BRANCH_NODE"
:flow-node="currentNode" :flow-node="currentNode"
@update:flow-node="handleModelValueUpdate" @update:flow-node="handleModelValueUpdate"
/> />
@ -73,7 +73,7 @@ import ExclusiveNode from './nodes/ExclusiveNode.vue'
import ParallelNode from './nodes/ParallelNode.vue' import ParallelNode from './nodes/ParallelNode.vue'
import InclusiveNode from './nodes/InclusiveNode.vue' import InclusiveNode from './nodes/InclusiveNode.vue'
import DelayTimerNode from './nodes/DelayTimerNode.vue' import DelayTimerNode from './nodes/DelayTimerNode.vue'
import RouteNode from './nodes/RouteNode.vue' import RouterNode from './nodes/RouterNode.vue'
import { SimpleFlowNode, NodeType } from './consts' import { SimpleFlowNode, NodeType } from './consts'
import { useWatchNode } from './node' import { useWatchNode } from './node'
defineOptions({ defineOptions({

View File

@ -40,7 +40,7 @@ defineOptions({
name: 'SimpleProcessDesigner' name: 'SimpleProcessDesigner'
}) })
const emits = defineEmits(['success', 'init-finished']) // const emits = defineEmits(['success']) //
const props = defineProps({ const props = defineProps({
modelId: { modelId: {
@ -59,13 +59,10 @@ const props = defineProps({
startUserIds: { startUserIds: {
type: Array, type: Array,
required: false required: false
},
value: {
type: [String, Object],
required: false
} }
}) })
const processData = inject('processData') as Ref
const loading = ref(false) const loading = ref(false)
const formFields = ref<string[]>([]) const formFields = ref<string[]>([])
const formType = ref(20) const formType = ref(20)
@ -76,9 +73,6 @@ const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表
const deptTreeOptions = ref() const deptTreeOptions = ref()
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) //
//
const currentValue = ref<SimpleFlowNode | undefined>()
provide('formFields', formFields) provide('formFields', formFields)
provide('formType', formType) provide('formType', formType)
provide('roleList', roleOptions) provide('roleList', roleOptions)
@ -88,7 +82,8 @@ provide('deptList', deptOptions)
provide('userGroupList', userGroupOptions) provide('userGroupList', userGroupOptions)
provide('deptTree', deptTreeOptions) provide('deptTree', deptTreeOptions)
provide('startUserIds', props.startUserIds) provide('startUserIds', props.startUserIds)
provide('tasks', [])
provide('processInstance', {})
const message = useMessage() // const message = useMessage() //
const processNodeTree = ref<SimpleFlowNode | undefined>() const processNodeTree = ref<SimpleFlowNode | undefined>()
provide('processNodeTree', processNodeTree) provide('processNodeTree', processNodeTree)
@ -113,70 +108,13 @@ const updateModel = () => {
} }
} }
//
const loadProcessData = async (data: any) => {
try {
if (data) {
const parsedData = typeof data === 'string' ? JSON.parse(data) : data
processNodeTree.value = parsedData
currentValue.value = parsedData
//
await nextTick()
if (simpleProcessModelRef.value?.refresh) {
await simpleProcessModelRef.value.refresh()
}
}
} catch (error) {
console.error('加载流程数据失败:', error)
}
}
//
watch(
() => props.value,
async (newValue, oldValue) => {
if (newValue && newValue !== oldValue) {
await loadProcessData(newValue)
}
},
{ immediate: true, deep: true }
)
//
watch(
() => processNodeTree.value,
async (newValue, oldValue) => {
if (newValue && oldValue && JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
await saveSimpleFlowModel(newValue)
}
},
{ deep: true }
)
const saveSimpleFlowModel = async (simpleModelNode: SimpleFlowNode) => { const saveSimpleFlowModel = async (simpleModelNode: SimpleFlowNode) => {
if (!simpleModelNode) { if (!simpleModelNode) {
return return
} }
//
errorNodes = []
validateNode(simpleModelNode, errorNodes)
if (errorNodes.length > 0) {
errorDialogVisible.value = true
return
}
try { try {
if (props.modelId) { processData.value = simpleModelNode
//
const data = {
id: props.modelId,
simpleModel: simpleModelNode
}
await updateBpmSimpleModel(data)
}
//
currentValue.value = simpleModelNode
emits('success', simpleModelNode) emits('success', simpleModelNode)
} catch (error) { } catch (error) {
console.error('保存失败:', error) console.error('保存失败:', error)
@ -247,61 +185,18 @@ onMounted(async () => {
deptTreeOptions.value = handleTree(deptOptions.value as DeptApi.DeptVO[], 'id') deptTreeOptions.value = handleTree(deptOptions.value as DeptApi.DeptVO[], 'id')
// //
userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList() userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
// //
if (props.modelId) { if (processData.value) {
// SIMPLE processNodeTree.value = processData?.value
const result = await getBpmSimpleModel(props.modelId)
if (result) {
await loadProcessData(result)
} else {
updateModel()
}
} else if (props.value) {
await loadProcessData(props.value)
} else { } else {
updateModel() updateModel()
} }
} finally { } finally {
loading.value = false loading.value = false
emits('init-finished')
} }
}) })
const simpleProcessModelRef = ref() const simpleProcessModelRef = ref()
/** 获取当前流程数据 */ defineExpose({})
const getCurrentFlowData = async () => {
try {
if (simpleProcessModelRef.value) {
const data = await simpleProcessModelRef.value.getCurrentFlowData()
if (data) {
currentValue.value = data
return data
}
}
return currentValue.value
} catch (error) {
console.error('获取流程数据失败:', error)
return currentValue.value
}
}
//
const refresh = async () => {
try {
if (currentValue.value) {
await loadProcessData(currentValue.value)
}
} catch (error) {
console.error('刷新失败:', error)
}
}
defineExpose({
getCurrentFlowData,
updateModel,
loadProcessData,
refresh
})
</script> </script>

View File

@ -3,6 +3,22 @@
<div class="position-absolute top-0px right-0px bg-#fff"> <div class="position-absolute top-0px right-0px bg-#fff">
<el-row type="flex" justify="end"> <el-row type="flex" justify="end">
<el-button-group key="scale-control" size="default"> <el-button-group key="scale-control" size="default">
<el-button v-if="!readonly" size="default" @click="exportJson">
<Icon icon="ep:download" /> 导出
</el-button>
<el-button v-if="!readonly" size="default" @click="importJson">
<Icon icon="ep:upload" />导入
</el-button>
<!-- 用于打开本地文件-->
<input
v-if="!readonly"
type="file"
id="files"
ref="refFile"
style="display: none"
accept=".json"
@change="importLocalFile"
/>
<el-button size="default" :icon="ScaleToOriginal" @click="processReZoom()" /> <el-button size="default" :icon="ScaleToOriginal" @click="processReZoom()" />
<el-button size="default" :plain="true" :icon="ZoomOut" @click="zoomOut()" /> <el-button size="default" :plain="true" :icon="ZoomOut" @click="zoomOut()" />
<el-button size="default" class="w-80px"> {{ scaleValue }}% </el-button> <el-button size="default" class="w-80px"> {{ scaleValue }}% </el-button>
@ -34,6 +50,8 @@ import ProcessNodeTree from './ProcessNodeTree.vue'
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from './consts' import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from './consts'
import { useWatchNode } from './node' import { useWatchNode } from './node'
import { ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue' import { ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue'
import { isString } from '@/utils/is'
import download from '@/utils/download'
defineOptions({ defineOptions({
name: 'SimpleProcessModel' name: 'SimpleProcessModel'
@ -52,7 +70,7 @@ const props = defineProps({
}) })
const emits = defineEmits<{ const emits = defineEmits<{
'save': [node: SimpleFlowNode | undefined] save: [node: SimpleFlowNode | undefined]
}>() }>()
const processNodeTree = useWatchNode(props) const processNodeTree = useWatchNode(props)
@ -85,6 +103,16 @@ const processReZoom = () => {
const errorDialogVisible = ref(false) const errorDialogVisible = ref(false)
let errorNodes: SimpleFlowNode[] = [] let errorNodes: SimpleFlowNode[] = []
const saveSimpleFlowModel = async () => {
errorNodes = []
validateNode(processNodeTree.value, errorNodes)
if (errorNodes.length > 0) {
errorDialogVisible.value = true
return
}
emits('save', processNodeTree.value)
}
// showText // showText
const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => { const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => {
if (node) { if (node) {
@ -143,6 +171,28 @@ const getCurrentFlowData = async () => {
defineExpose({ defineExpose({
getCurrentFlowData getCurrentFlowData
}) })
/** 导出 JSON */
const exportJson = () => {
download.json(new Blob([JSON.stringify(processNodeTree.value)]), 'model.json')
}
/** 导入 JSON */
const refFile = ref()
const importJson = () => {
refFile.value.click()
}
const importLocalFile = () => {
const file = refFile.value.files[0]
const reader = new FileReader()
reader.readAsText(file)
reader.onload = function () {
if (isString(this.result)) {
processNodeTree.value = JSON.parse(this.result)
emits('save', processNodeTree.value)
}
}
}
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@ -48,7 +48,7 @@ export enum NodeType {
/** /**
* *
*/ */
ROUTE_BRANCH_NODE = 54 ROUTER_BRANCH_NODE = 54
} }
export enum NodeId { export enum NodeId {
@ -110,10 +110,12 @@ export interface SimpleFlowNode {
// 延迟设置 // 延迟设置
delaySetting?: DelaySetting delaySetting?: DelaySetting
// 路由分支 // 路由分支
routerGroups?: RouteCondition[] routerGroups?: RouterCondition[]
defaultFlowId?: string defaultFlowId?: string
// 签名 // 签名
signEnable?: boolean signEnable?: boolean
// 审批意见
reasonRequire?: boolean
} }
// 候选人策略枚举 用于审批节点。抄送节点 ) // 候选人策略枚举 用于审批节点。抄送节点 )
export enum CandidateStrategy { export enum CandidateStrategy {
@ -447,8 +449,6 @@ export enum OperationButtonType {
* *
*/ */
export type ConditionRule = { export type ConditionRule = {
type: number
opName: string
opCode: string opCode: string
leftSide: string leftSide: string
rightSide: string rightSide: string
@ -479,7 +479,7 @@ NODE_DEFAULT_TEXT.set(NodeType.COPY_TASK_NODE, '请配置抄送人')
NODE_DEFAULT_TEXT.set(NodeType.CONDITION_NODE, '请设置条件') NODE_DEFAULT_TEXT.set(NodeType.CONDITION_NODE, '请设置条件')
NODE_DEFAULT_TEXT.set(NodeType.START_USER_NODE, '请设置发起人') NODE_DEFAULT_TEXT.set(NodeType.START_USER_NODE, '请设置发起人')
NODE_DEFAULT_TEXT.set(NodeType.DELAY_TIMER_NODE, '请设置延迟器') NODE_DEFAULT_TEXT.set(NodeType.DELAY_TIMER_NODE, '请设置延迟器')
NODE_DEFAULT_TEXT.set(NodeType.ROUTE_BRANCH_NODE, '请设置路由节点') NODE_DEFAULT_TEXT.set(NodeType.ROUTER_BRANCH_NODE, '请设置路由节点')
export const NODE_DEFAULT_NAME = new Map<number, string>() export const NODE_DEFAULT_NAME = new Map<number, string>()
NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人') NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人')
@ -487,7 +487,7 @@ NODE_DEFAULT_NAME.set(NodeType.COPY_TASK_NODE, '抄送人')
NODE_DEFAULT_NAME.set(NodeType.CONDITION_NODE, '条件') NODE_DEFAULT_NAME.set(NodeType.CONDITION_NODE, '条件')
NODE_DEFAULT_NAME.set(NodeType.START_USER_NODE, '发起人') NODE_DEFAULT_NAME.set(NodeType.START_USER_NODE, '发起人')
NODE_DEFAULT_NAME.set(NodeType.DELAY_TIMER_NODE, '延迟器') NODE_DEFAULT_NAME.set(NodeType.DELAY_TIMER_NODE, '延迟器')
NODE_DEFAULT_NAME.set(NodeType.ROUTE_BRANCH_NODE, '路由分支') NODE_DEFAULT_NAME.set(NodeType.ROUTER_BRANCH_NODE, '路由分支')
// 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序 // 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序
export const CANDIDATE_STRATEGY: DictDataVO[] = [ export const CANDIDATE_STRATEGY: DictDataVO[] = [
@ -668,7 +668,7 @@ export const DELAY_TYPE = [
/** /**
* *
*/ */
export type RouteCondition = { export type RouterCondition = {
nodeId: string nodeId: string
conditionType: ConditionType conditionType: ConditionType
conditionExpression: string conditionExpression: string

View File

@ -148,6 +148,7 @@ export type UserTaskFormType = {
taskCompleteListenerHeader?: ListenerParam[] taskCompleteListenerHeader?: ListenerParam[]
taskCompleteListenerBody?: ListenerParam[] taskCompleteListenerBody?: ListenerParam[]
signEnable: boolean signEnable: boolean
reasonRequire: boolean
} }
export type CopyTaskFormType = { export type CopyTaskFormType = {

View File

@ -26,127 +26,11 @@
</div> </div>
</template> </template>
<div> <div>
<div class="mb-3 font-size-16px" v-if="currentNode.conditionSetting?.defaultFlow"> <div class="mb-3 font-size-16px" v-if="currentNode.conditionSetting?.defaultFlow"
未满足其它条件时将进入此分支该分支不可编辑和删除 >未满足其它条件时将进入此分支该分支不可编辑和删除</div
</div> >
<div v-else> <div v-else>
<el-form ref="formRef" :model="currentNode.conditionSetting" :rules="formRules" label-position="top"> <Condition ref="conditionRef" v-model="condition" />
<el-form-item label="配置方式" prop="conditionType">
<el-radio-group
v-model="currentNode.conditionSetting!.conditionType"
@change="changeConditionType"
>
<el-radio
v-for="(dict, index) in conditionConfigTypes"
:key="index"
:value="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="currentNode.conditionSetting?.conditionType === ConditionType.EXPRESSION"
label="条件表达式"
prop="conditionExpression"
>
<el-input
type="textarea"
v-model="currentNode.conditionSetting.conditionExpression"
clearable
style="width: 100%"
/>
</el-form-item>
<el-form-item
v-if="currentNode.conditionSetting?.conditionType === ConditionType.RULE"
label="条件规则"
>
<div class="condition-group-tool">
<div class="flex items-center">
<div class="mr-4">条件组关系</div>
<el-switch
v-model="conditionGroups.and"
inline-prompt
active-text="且"
inactive-text="或"
/>
</div>
</div>
<el-space direction="vertical" :spacer="conditionGroups.and ? '且' : '或'">
<el-card
class="condition-group"
style="width: 530px"
v-for="(condition, cIdx) in conditionGroups.conditions"
:key="cIdx"
>
<div class="condition-group-delete" v-if="conditionGroups.conditions.length > 1">
<Icon
color="#0089ff"
icon="ep:circle-close-filled"
:size="18"
@click="deleteConditionGroup(cIdx)"
/>
</div>
<template #header>
<div class="flex items-center justify-between">
<div>条件组</div>
<div class="flex">
<div class="mr-4">规则关系</div>
<el-switch
v-model="condition.and"
inline-prompt
active-text="且"
inactive-text="或"
/>
</div>
</div>
</template>
<div class="flex pt-2" v-for="(rule, rIdx) in condition.rules" :key="rIdx">
<div class="mr-2">
<el-select style="width: 160px" v-model="rule.leftSide">
<el-option
v-for="(item, index) in fieldOptions"
:key="index"
:label="item.title"
:value="item.field"
:disabled="!item.required"
/>
</el-select>
</div>
<div class="mr-2">
<el-select v-model="rule.opCode" style="width: 100px">
<el-option
v-for="item in COMPARISON_OPERATORS"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
<div class="mr-2">
<el-input v-model="rule.rightSide" style="width: 160px" />
</div>
<div class="mr-1 flex items-center" v-if="condition.rules.length > 1">
<Icon
icon="ep:delete"
:size="18"
@click="deleteConditionRule(condition, rIdx)"
/>
</div>
<div class="flex items-center">
<Icon icon="ep:plus" :size="18" @click="addConditionRule(condition, rIdx)" />
</div>
</div>
</el-card>
</el-space>
<div title="添加条件组" class="mt-4 cursor-pointer">
<Icon color="#0089ff" icon="ep:plus" :size="24" @click="addConditionGroup" />
</div>
</el-form-item>
</el-form>
</div> </div>
</div> </div>
<template #footer> <template #footer>
@ -161,33 +45,18 @@
<script setup lang="ts"> <script setup lang="ts">
import { import {
SimpleFlowNode, SimpleFlowNode,
CONDITION_CONFIG_TYPES,
ConditionType, ConditionType,
ConditionSetting,
COMPARISON_OPERATORS, COMPARISON_OPERATORS,
ConditionGroup,
Condition,
ConditionRule,
ProcessVariableEnum ProcessVariableEnum
} from '../consts' } from '../consts'
import { getDefaultConditionNodeName } from '../utils' import { getDefaultConditionNodeName } from '../utils'
import { useFormFields } from '../node' import { useFormFields } from '../node'
import { BpmModelFormType } from '@/utils/constants' import Condition from './components/Condition.vue'
const message = useMessage() // const message = useMessage() //
defineOptions({ defineOptions({
name: 'ConditionNodeConfig' name: 'ConditionNodeConfig'
}) })
const formType = inject<Ref<number>>('formType') //
const conditionConfigTypes = computed(() => {
return CONDITION_CONFIG_TYPES.filter((item) => {
//
if (formType?.value === BpmModelFormType.CUSTOM && item.value === ConditionType.RULE) {
return false
} else {
return true
}
})
})
const props = defineProps({ const props = defineProps({
conditionNode: { conditionNode: {
type: Object as () => SimpleFlowNode, type: Object as () => SimpleFlowNode,
@ -199,12 +68,10 @@ const props = defineProps({
} }
}) })
const settingVisible = ref(false) const settingVisible = ref(false)
const currentNode = ref<SimpleFlowNode>(props.conditionNode)
const condition = ref<ConditionSetting>()
const open = () => { const open = () => {
if (currentNode.value.conditionSetting?.conditionType === ConditionType.RULE) { condition.value = currentNode.value.conditionSetting
if (currentNode.value.conditionSetting?.conditionGroups) {
conditionGroups.value = currentNode.value.conditionSetting.conditionGroups
}
}
settingVisible.value = true settingVisible.value = true
} }
@ -228,7 +95,7 @@ const blurEvent = () => {
getDefaultConditionNodeName(props.nodeIndex, currentNode.value?.conditionSetting?.defaultFlow) getDefaultConditionNodeName(props.nodeIndex, currentNode.value?.conditionSetting?.defaultFlow)
} }
const currentNode = ref<SimpleFlowNode>(props.conditionNode)
defineExpose({ open }) // open defineExpose({ open }) // open
@ -245,31 +112,27 @@ const handleClose = async (done: (cancel?: boolean) => void) => {
done() done()
} }
} }
//
const formRules = reactive({
conditionType: [{ required: true, message: '配置方式不能为空', trigger: 'blur' }],
conditionExpression: [{ required: true, message: '条件表达式不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
const conditionRef = ref()
// //
const saveConfig = async () => { const saveConfig = async () => {
if (!currentNode.value.conditionSetting?.defaultFlow) { if (!currentNode.value.conditionSetting?.defaultFlow) {
// //
if (!formRef) return false const valid = await conditionRef.value.validate()
const valid = await formRef.value.validate()
if (!valid) return false if (!valid) return false
const showText = getShowText() const showText = getShowText()
if (!showText) { if (!showText) {
return false return false
} }
currentNode.value.showText = showText currentNode.value.showText = showText
currentNode.value.conditionSetting!.conditionType = condition.value?.conditionType
if (currentNode.value.conditionSetting?.conditionType === ConditionType.EXPRESSION) { if (currentNode.value.conditionSetting?.conditionType === ConditionType.EXPRESSION) {
currentNode.value.conditionSetting.conditionGroups = undefined currentNode.value.conditionSetting.conditionGroups = undefined
currentNode.value.conditionSetting.conditionExpression = condition.value?.conditionExpression
} }
if (currentNode.value.conditionSetting?.conditionType === ConditionType.RULE) { if (currentNode.value.conditionSetting!.conditionType === ConditionType.RULE) {
currentNode.value.conditionSetting.conditionExpression = undefined currentNode.value.conditionSetting!.conditionExpression = undefined
currentNode.value.conditionSetting.conditionGroups = conditionGroups.value currentNode.value.conditionSetting!.conditionGroups = condition.value?.conditionGroups
} }
} }
settingVisible.value = false settingVisible.value = false
@ -277,16 +140,16 @@ const saveConfig = async () => {
} }
const getShowText = (): string => { const getShowText = (): string => {
let showText = '' let showText = ''
if (currentNode.value.conditionSetting?.conditionType === ConditionType.EXPRESSION) { if (condition.value?.conditionType === ConditionType.EXPRESSION) {
if (currentNode.value.conditionSetting.conditionExpression) { if (condition.value.conditionExpression) {
showText = `表达式:${currentNode.value.conditionSetting.conditionExpression}` showText = `表达式:${condition.value.conditionExpression}`
} }
} }
if (currentNode.value.conditionSetting?.conditionType === ConditionType.RULE) { if (condition.value?.conditionType === ConditionType.RULE) {
// //
const groupAnd = conditionGroups.value.and const groupAnd = condition.value.conditionGroups?.and
let warningMesg: undefined | string = undefined let warningMesg: undefined | string = undefined
const conditionGroup = conditionGroups.value.conditions.map((item) => { const conditionGroup = condition.value.conditionGroups?.conditions.map((item) => {
return ( return (
'(' + '(' +
item.rules item.rules
@ -309,70 +172,13 @@ const getShowText = (): string => {
message.warning(warningMesg) message.warning(warningMesg)
showText = '' showText = ''
} else { } else {
showText = conditionGroup.join(groupAnd ? ' 且 ' : ' 或 ') showText = conditionGroup!.join(groupAnd ? ' 且 ' : ' 或 ')
} }
} }
return showText return showText
} }
//
const changeConditionType = () => {}
const conditionGroups = ref<ConditionGroup>({
and: true,
conditions: [
{
and: true,
rules: [
{
type: 1,
opName: '等于',
opCode: '==',
leftSide: '',
rightSide: ''
}
]
}
]
})
//
const addConditionGroup = () => {
const condition = {
and: true,
rules: [
{
type: 1,
opName: '等于',
opCode: '==',
leftSide: '',
rightSide: ''
}
]
}
conditionGroups.value.conditions.push(condition)
}
//
const deleteConditionGroup = (idx: number) => {
conditionGroups.value.conditions.splice(idx, 1)
}
//
const addConditionRule = (condition: Condition, idx: number) => {
const rule: ConditionRule = {
type: 1,
opName: '等于',
opCode: '==',
leftSide: '',
rightSide: ''
}
condition.rules.splice(idx + 1, 0, rule)
}
const deleteConditionRule = (condition: Condition, idx: number) => {
condition.rules.splice(idx, 1)
}
const fieldsInfo = useFormFields() const fieldsInfo = useFormFields()
/** 条件规则可选择的表单字段 */ /** 条件规则可选择的表单字段 */
const fieldOptions = computed(() => { const fieldOptions = computed(() => {
const fieldsCopy = fieldsInfo.slice() const fieldsCopy = fieldsInfo.slice()

View File

@ -37,16 +37,19 @@
:value="node.value" :value="node.value"
/> />
</el-select> </el-select>
<el-button class="mla" type="danger" link @click="deleteRouteGroup(index)" <el-button class="mla" type="danger" link @click="deleteRouterGroup(index)">
>删除</el-button 删除
> </el-button>
</div> </div>
</template> </template>
<Condition v-model="routerGroups[index]" /> <Condition
:ref="($event) => (conditionRef[index] = $event)"
v-model="routerGroups[index]"
/>
</el-card> </el-card>
</el-form> </el-form>
<el-button class="w-1/1" type="primary" :icon="Plus" @click="addRouteGroup"> <el-button class="w-1/1" type="primary" :icon="Plus" @click="addRouterGroup">
新增路由分支 新增路由分支
</el-button> </el-button>
</div> </div>
@ -61,11 +64,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Plus } from '@element-plus/icons-vue' import { Plus } from '@element-plus/icons-vue'
import { SimpleFlowNode, NodeType, ConditionType, RouteCondition } from '../consts' import { SimpleFlowNode, NodeType, ConditionType, RouterCondition } from '../consts'
import { useWatchNode, useDrawer, useNodeName } from '../node' import { useWatchNode, useDrawer, useNodeName } from '../node'
import Condition from './components/Condition.vue' import Condition from './components/Condition.vue'
defineOptions({ defineOptions({
name: 'RouteNodeConfig' name: 'RouterNodeConfig'
}) })
const message = useMessage() // const message = useMessage() //
const props = defineProps({ const props = defineProps({
@ -80,12 +84,21 @@ const { settingVisible, closeDrawer, openDrawer } = useDrawer()
// //
const currentNode = useWatchNode(props) const currentNode = useWatchNode(props)
// //
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.ROUTE_BRANCH_NODE) const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.ROUTER_BRANCH_NODE)
const routerGroups = ref<RouteCondition[]>([]) const routerGroups = ref<RouterCondition[]>([])
const nodeOptions = ref() const nodeOptions = ref<any>([])
const conditionRef = ref([])
// /** 保存配置 */
const saveConfig = async () => { const saveConfig = async () => {
//
let valid = true
for (const item of conditionRef.value) {
if (item && !(await item.validate())) {
valid = false
}
}
if (!valid) return false
const showText = getShowText() const showText = getShowText()
if (!showText) return false if (!showText) return false
currentNode.value.name = nodeName.value! currentNode.value.name = nodeName.value!
@ -96,7 +109,7 @@ const saveConfig = async () => {
} }
// //
const showRouteNodeConfig = (node: SimpleFlowNode) => { const showRouteNodeConfig = (node: SimpleFlowNode) => {
getRoutableNode() getRouterNode(processNodeTree?.value)
routerGroups.value = [] routerGroups.value = []
nodeName.value = node.name nodeName.value = node.name
if (node.routerGroups) { if (node.routerGroups) {
@ -132,7 +145,7 @@ const getShowText = () => {
return `${routerGroups.value.length}条路由分支` return `${routerGroups.value.length}条路由分支`
} }
const addRouteGroup = () => { const addRouterGroup = () => {
routerGroups.value.push({ routerGroups.value.push({
nodeId: '', nodeId: '',
conditionType: ConditionType.RULE, conditionType: ConditionType.RULE,
@ -144,8 +157,6 @@ const addRouteGroup = () => {
and: true, and: true,
rules: [ rules: [
{ {
type: 1,
opName: '等于',
opCode: '==', opCode: '==',
leftSide: '', leftSide: '',
rightSide: '' rightSide: ''
@ -157,20 +168,18 @@ const addRouteGroup = () => {
}) })
} }
const deleteRouteGroup = (index: number) => { const deleteRouterGroup = (index: number) => {
routerGroups.value.splice(index, 1) routerGroups.value.splice(index, 1)
} }
// TODO @lesan router //
const getRoutableNode = () => { const getRouterNode = (node) => {
// TODO @lesan // TODO
// //
// //
let node = processNodeTree?.value
nodeOptions.value = []
while (true) { while (true) {
if (!node) break if (!node) break
if (node.type !== NodeType.ROUTE_BRANCH_NODE) { if (node.type !== NodeType.ROUTER_BRANCH_NODE && node.type !== NodeType.CONDITION_NODE) {
nodeOptions.value.push({ nodeOptions.value.push({
label: node.name, label: node.name,
value: node.id value: node.id
@ -179,6 +188,11 @@ const getRoutableNode = () => {
if (!node.childNode || node.type === NodeType.END_EVENT_NODE) { if (!node.childNode || node.type === NodeType.END_EVENT_NODE) {
break break
} }
if (node.conditionNodes && node.conditionNodes.length) {
node.conditionNodes.forEach((item) => {
getRouterNode(item)
})
}
node = node.childNode node = node.childNode
} }
} }

View File

@ -359,11 +359,12 @@
<el-divider content-position="left">是否需要签名</el-divider> <el-divider content-position="left">是否需要签名</el-divider>
<el-form-item prop="signEnable"> <el-form-item prop="signEnable">
<el-switch <el-switch v-model="configForm.signEnable" active-text="" inactive-text="" />
v-model="configForm.signEnable" </el-form-item>
active-text="是"
inactive-text="否" <el-divider content-position="left">审批意见</el-divider>
/> <el-form-item prop="reasonRequire">
<el-switch v-model="configForm.reasonRequire" active-text="" inactive-text="" />
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
@ -445,161 +446,7 @@
</div> </div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="监听器" name="listener"> <el-tab-pane label="监听器" name="listener">
<el-form :model="configForm" label-position="top"> <UserTaskListener ref="userTaskListenerRef" v-model="configForm" :form-field-options="formFieldOptions" />
<div v-for="(listener, listenerIdx) in taskListener" :key="listenerIdx">
<el-divider content-position="left">
<el-text tag="b" size="large">{{ listener.name }}</el-text>
</el-divider>
<el-form-item>
<el-switch
v-model="configForm[`task${listener.type}ListenerEnable`]"
active-text="开启"
inactive-text="关闭"
/>
</el-form-item>
<div v-if="configForm[`task${listener.type}ListenerEnable`]">
<el-form-item>
<el-alert
title="仅支持 POST 请求,以请求体方式接收参数"
type="warning"
show-icon
:closable="false"
/>
</el-form-item>
<el-form-item
label="请求地址"
:prop="`task${listener.type}ListenerPath`"
:rules="{
required: true,
message: '请求地址不能为空',
trigger: 'blur'
}"
>
<el-input v-model="configForm[`task${listener.type}ListenerPath`]" />
</el-form-item>
<el-form-item label="请求头">
<div
class="flex pt-2"
v-for="(item, index) in configForm[`task${listener.type}ListenerHeader`]"
:key="index"
>
<div class="mr-2">
<el-input class="w-160px" v-model="item.key" />
</div>
<div class="mr-2">
<el-select class="w-100px!" v-model="item.type">
<el-option
v-for="types in LISTENER_MAP_TYPES"
:key="types.value"
:label="types.label"
:value="types.value"
/>
</el-select>
</div>
<div class="mr-2">
<el-input
v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
class="w-160px"
v-model="item.value"
/>
<el-select
v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
class="w-160px!"
v-model="item.value"
>
<el-option
v-for="(field, fIdx) in formFieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="!field.required"
/>
</el-select>
</div>
<div class="mr-1 flex items-center">
<Icon
icon="ep:delete"
:size="18"
@click="
deleteTaskListenerParam(
configForm[`task${listener.type}ListenerHeader`],
index
)
"
/>
</div>
</div>
<el-button
type="primary"
text
@click="addTaskListenerParam(configForm[`task${listener.type}ListenerHeader`])"
>
<Icon icon="ep:plus" class="mr-5px" />添加一行
</el-button>
</el-form-item>
<el-form-item label="请求体">
<div
class="flex pt-2"
v-for="(item, index) in configForm[`task${listener.type}ListenerBody`]"
:key="index"
>
<div class="mr-2">
<el-input class="w-160px" v-model="item.key" />
</div>
<div class="mr-2">
<el-select class="w-100px!" v-model="item.type">
<el-option
v-for="types in LISTENER_MAP_TYPES"
:key="types.value"
:label="types.label"
:value="types.value"
/>
</el-select>
</div>
<div class="mr-2">
<el-input
v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
class="w-160px"
v-model="item.value"
/>
<el-select
v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
class="w-160px!"
v-model="item.value"
>
<el-option
v-for="(field, fIdx) in formFieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="!field.required"
/>
</el-select>
</div>
<div class="mr-1 flex items-center">
<Icon
icon="ep:delete"
:size="18"
@click="
deleteTaskListenerParam(
configForm[`task${listener.type}ListenerBody`],
index
)
"
/>
</div>
</div>
<el-button
type="primary"
text
@click="addTaskListenerParam(configForm[`task${listener.type}ListenerBody`])"
>
<Icon icon="ep:plus" class="mr-5px" />添加一行
</el-button>
</el-form-item>
</div>
</div>
</el-form>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<template #footer> <template #footer>
@ -636,9 +483,7 @@ import {
ASSIGN_EMPTY_HANDLER_TYPES, ASSIGN_EMPTY_HANDLER_TYPES,
AssignEmptyHandlerType, AssignEmptyHandlerType,
FieldPermissionType, FieldPermissionType,
ProcessVariableEnum, ProcessVariableEnum
LISTENER_MAP_TYPES,
ListenerParamTypeEnum
} from '../consts' } from '../consts'
import { import {
@ -652,6 +497,7 @@ import {
import { defaultProps } from '@/utils/tree' import { defaultProps } from '@/utils/tree'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { convertTimeUnit, getApproveTypeText } from '../utils' import { convertTimeUnit, getApproveTypeText } from '../utils'
import UserTaskListener from './components/UserTaskListener.vue'
defineOptions({ defineOptions({
name: 'UserTaskNodeConfig' name: 'UserTaskNodeConfig'
}) })
@ -729,21 +575,6 @@ const formRules = reactive({
assignEmptyHandlerUserIds: [{ required: true, message: '用户不能为空', trigger: 'change' }], assignEmptyHandlerUserIds: [{ required: true, message: '用户不能为空', trigger: 'change' }],
assignStartUserHandlerType: [{ required: true }] assignStartUserHandlerType: [{ required: true }]
}) })
//
const taskListener = ref([
{
name: '创建任务',
type: 'Create'
},
{
name: '指派任务执行人员',
type: 'Assign'
},
{
name: '完成任务',
type: 'Complete'
}
])
const { const {
configForm: tempConfigForm, configForm: tempConfigForm,
@ -792,6 +623,8 @@ const {
cTimeoutMaxRemindCount cTimeoutMaxRemindCount
} = useTimeoutHandler() } = useTimeoutHandler()
const userTaskListenerRef = ref()
// //
const saveConfig = async () => { const saveConfig = async () => {
activeTabName.value = 'user' activeTabName.value = 'user'
@ -807,7 +640,8 @@ const saveConfig = async () => {
} }
if (!formRef) return false if (!formRef) return false
const valid = await formRef.value.validate() if (!userTaskListenerRef) return false
const valid = (await formRef.value.validate()) && (await userTaskListenerRef.value.validate())
if (!valid) return false if (!valid) return false
const showText = getShowText() const showText = getShowText()
if (!showText) return false if (!showText) return false
@ -869,6 +703,8 @@ const saveConfig = async () => {
} }
// //
currentNode.value.signEnable = configForm.value.signEnable currentNode.value.signEnable = configForm.value.signEnable
//
currentNode.value.reasonRequire = configForm.value.reasonRequire
currentNode.value.showText = showText currentNode.value.showText = showText
settingVisible.value = false settingVisible.value = false
@ -937,7 +773,9 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
configForm.value.taskCompleteListenerHeader = node.taskCompleteListener?.header ?? [] configForm.value.taskCompleteListenerHeader = node.taskCompleteListener?.header ?? []
configForm.value.taskCompleteListenerBody = node.taskCompleteListener?.body ?? [] configForm.value.taskCompleteListenerBody = node.taskCompleteListener?.body ?? []
// 6. // 6.
configForm.value.signEnable = node.signEnable ?? false configForm.value.signEnable = node?.signEnable ?? false
// 7.
configForm.value.reasonRequire = node?.reasonRequire ?? false
} }
defineExpose({ openDrawer, showUserTaskNodeConfig }) // defineExpose({ openDrawer, showUserTaskNodeConfig }) //
@ -1050,17 +888,6 @@ function useTimeoutHandler() {
cTimeoutMaxRemindCount cTimeoutMaxRemindCount
} }
} }
const addTaskListenerParam = (arr) => {
arr.push({
key: '',
type: 1,
value: ''
})
}
const deleteTaskListenerParam = (arr, index) => {
arr.splice(index, 1)
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -1,9 +1,7 @@
<!-- TODO @lesan其它路由条件可以使用这个哇 -->
<template> <template>
<el-form ref="formRef" :model="condition" :rules="formRules" label-position="top"> <el-form ref="formRef" :model="condition" :rules="formRules" label-position="top">
<!-- TODO @lesan1默认选中 条件规则2条件规则放前面因为更常用-->
<el-form-item label="配置方式" prop="conditionType"> <el-form-item label="配置方式" prop="conditionType">
<el-radio-group v-model="condition.conditionType"> <el-radio-group v-model="condition!.conditionType">
<el-radio <el-radio
v-for="(dict, indexConditionType) in conditionConfigTypes" v-for="(dict, indexConditionType) in conditionConfigTypes"
:key="indexConditionType" :key="indexConditionType"
@ -14,46 +12,34 @@
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item
v-if="condition.conditionType === ConditionType.EXPRESSION"
label="条件表达式"
prop="conditionExpression"
>
<el-input
type="textarea"
v-model="condition.conditionExpression"
clearable
style="width: 100%"
/>
</el-form-item>
<el-form-item v-if="condition.conditionType === ConditionType.RULE" label="条件规则"> <el-form-item v-if="condition.conditionType === ConditionType.RULE" label="条件规则">
<div class="condition-group-tool"> <div class="condition-group-tool">
<div class="flex items-center"> <div class="flex items-center">
<div class="mr-4">条件组关系</div> <div class="mr-4">条件组关系</div>
<el-switch <el-switch
v-model="condition.conditionGroups.and" v-model="condition!.conditionGroups!.and"
inline-prompt inline-prompt
active-text="且" active-text="且"
inactive-text="或" inactive-text="或"
/> />
</div> </div>
</div> </div>
<el-space direction="vertical" :spacer="condition.conditionGroups.and ? '且' : '或'"> <el-space direction="vertical" :spacer="condition!.conditionGroups!.and ? '且' : '或'">
<el-card <el-card
class="condition-group" class="condition-group"
style="width: 530px" style="width: 530px"
v-for="(equation, cIdx) in condition.conditionGroups.conditions" v-for="(equation, cIdx) in condition!.conditionGroups!.conditions"
:key="cIdx" :key="cIdx"
> >
<div <div
class="condition-group-delete" class="condition-group-delete"
v-if="condition.conditionGroups.conditions.length > 1" v-if="condition!.conditionGroups!.conditions.length > 1"
> >
<Icon <Icon
color="#0089ff" color="#0089ff"
icon="ep:circle-close-filled" icon="ep:circle-close-filled"
:size="18" :size="18"
@click="deleteConditionGroup(condition.conditionGroups.conditions, cIdx)" @click="deleteConditionGroup(condition!.conditionGroups!.conditions, cIdx)"
/> />
</div> </div>
<template #header> <template #header>
@ -73,15 +59,24 @@
<div class="flex pt-2" v-for="(rule, rIdx) in equation.rules" :key="rIdx"> <div class="flex pt-2" v-for="(rule, rIdx) in equation.rules" :key="rIdx">
<div class="mr-2"> <div class="mr-2">
<el-select style="width: 160px" v-model="rule.leftSide"> <el-form-item
<el-option :prop="`conditionGroups.conditions.${cIdx}.rules.${rIdx}.leftSide`"
v-for="(field, fIdx) in fieldOptions" :rules="{
:key="fIdx" required: true,
:label="field.title" message: '左值不能为空',
:value="field.field" trigger: 'change'
:disabled="!field.required" }"
/> >
</el-select> <el-select style="width: 160px" v-model="rule.leftSide">
<el-option
v-for="(field, fIdx) in fieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="!field.required"
/>
</el-select>
</el-form-item>
</div> </div>
<div class="mr-2"> <div class="mr-2">
<el-select v-model="rule.opCode" style="width: 100px"> <el-select v-model="rule.opCode" style="width: 100px">
@ -94,7 +89,16 @@
</el-select> </el-select>
</div> </div>
<div class="mr-2"> <div class="mr-2">
<el-input v-model="rule.rightSide" style="width: 160px" /> <el-form-item
:prop="`conditionGroups.conditions.${cIdx}.rules.${rIdx}.rightSide`"
:rules="{
required: true,
message: '右值不能为空',
trigger: 'blur'
}"
>
<el-input v-model="rule.rightSide" style="width: 160px" />
</el-form-item>
</div> </div>
<div class="mr-1 flex items-center" v-if="equation.rules.length > 1"> <div class="mr-1 flex items-center" v-if="equation.rules.length > 1">
<Icon icon="ep:delete" :size="18" @click="deleteConditionRule(equation, rIdx)" /> <Icon icon="ep:delete" :size="18" @click="deleteConditionRule(equation, rIdx)" />
@ -110,29 +114,37 @@
color="#0089ff" color="#0089ff"
icon="ep:plus" icon="ep:plus"
:size="24" :size="24"
@click="addConditionGroup(condition.conditionGroups.conditions)" @click="addConditionGroup(condition?.conditionGroups!.conditions)"
/> />
</div> </div>
</el-form-item> </el-form-item>
<el-form-item
v-if="condition.conditionType === ConditionType.EXPRESSION"
label="条件表达式"
prop="conditionExpression"
>
<el-input
type="textarea"
v-model="condition!.conditionExpression"
clearable
style="width: 100%"
/>
</el-form-item>
</el-form> </el-form>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { import {
CONDITION_CONFIG_TYPES,
COMPARISON_OPERATORS, COMPARISON_OPERATORS,
CONDITION_CONFIG_TYPES,
ConditionType, ConditionType,
ConditionSetting,
ProcessVariableEnum ProcessVariableEnum
} from '../../consts' } from '../../consts'
import { BpmModelFormType } from '@/utils/constants' import { BpmModelFormType } from '@/utils/constants'
import { useFormFields } from '../../node' import { useFormFields } from '../../node'
const props = defineProps({ const props = defineProps<{modelValue: ConditionSetting}>()
modelValue: {
type: Object,
required: true
}
})
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const condition = computed({ const condition = computed({
get() { get() {
@ -181,8 +193,6 @@ const deleteConditionRule = (condition, index) => {
const addConditionRule = (condition, index) => { const addConditionRule = (condition, index) => {
const rule = { const rule = {
type: 1,
opName: '等于',
opCode: '==', opCode: '==',
leftSide: '', leftSide: '',
rightSide: '' rightSide: ''
@ -195,8 +205,6 @@ const addConditionGroup = (conditions) => {
and: true, and: true,
rules: [ rules: [
{ {
type: 1, // TODO @lesan~
opName: '等于',
opCode: '==', opCode: '==',
leftSide: '', leftSide: '',
rightSide: '' rightSide: ''
@ -205,6 +213,13 @@ const addConditionGroup = (conditions) => {
} }
conditions.push(condition) conditions.push(condition)
} }
const validate = async () => {
if (!formRef) return false
return await formRef.value.validate()
}
defineExpose({ validate })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -0,0 +1,261 @@
<template>
<el-form ref="listenerFormRef" :model="configForm" label-position="top">
<div v-for="(listener, listenerIdx) in taskListener" :key="listenerIdx">
<el-divider content-position="left">
<el-text tag="b" size="large">{{ listener.name }}</el-text>
</el-divider>
<el-form-item>
<el-switch
v-model="configForm[`task${listener.type}ListenerEnable`]"
active-text="开启"
inactive-text="关闭"
/>
</el-form-item>
<div v-if="configForm[`task${listener.type}ListenerEnable`]">
<el-form-item>
<el-alert
title="仅支持 POST 请求,以请求体方式接收参数"
type="warning"
show-icon
:closable="false"
/>
</el-form-item>
<el-form-item
label="请求地址"
:prop="`task${listener.type}ListenerPath`"
:rules="{
required: true,
message: '请求地址不能为空',
trigger: 'blur'
}"
>
<el-input v-model="configForm[`task${listener.type}ListenerPath`]" />
</el-form-item>
<el-form-item label="请求头">
<div
class="flex pt-2"
v-for="(item, index) in configForm[`task${listener.type}ListenerHeader`]"
:key="index"
>
<div class="mr-2">
<el-form-item
:prop="`task${listener.type}ListenerHeader.${index}.key`"
:rules="{
required: true,
message: '参数名不能为空',
trigger: 'blur'
}"
>
<el-input class="w-160px" v-model="item.key" />
</el-form-item>
</div>
<div class="mr-2">
<el-select class="w-100px!" v-model="item.type">
<el-option
v-for="types in LISTENER_MAP_TYPES"
:key="types.value"
:label="types.label"
:value="types.value"
/>
</el-select>
</div>
<div class="mr-2">
<el-form-item
:prop="`task${listener.type}ListenerHeader.${index}.value`"
:rules="{
required: true,
message: '参数值不能为空',
trigger: 'blur'
}"
>
<el-input
v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
class="w-160px"
v-model="item.value"
/>
</el-form-item>
<el-form-item
:prop="`task${listener.type}ListenerHeader.${index}.value`"
:rules="{
required: true,
message: '参数值不能为空',
trigger: 'change'
}"
>
<el-select
v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
class="w-160px!"
v-model="item.value"
>
<el-option
v-for="(field, fIdx) in formFieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="!field.required"
/>
</el-select>
</el-form-item>
</div>
<div class="mr-1 flex items-center">
<Icon
icon="ep:delete"
:size="18"
@click="
deleteTaskListenerParam(configForm[`task${listener.type}ListenerHeader`], index)
"
/>
</div>
</div>
<el-button
type="primary"
text
@click="addTaskListenerParam(configForm[`task${listener.type}ListenerHeader`])"
>
<Icon icon="ep:plus" class="mr-5px" />添加一行
</el-button>
</el-form-item>
<el-form-item label="请求体">
<div
class="flex pt-2"
v-for="(item, index) in configForm[`task${listener.type}ListenerBody`]"
:key="index"
>
<div class="mr-2">
<el-form-item
:prop="`task${listener.type}ListenerBody.${index}.key`"
:rules="{
required: true,
message: '参数名不能为空',
trigger: 'blur'
}"
>
<el-input class="w-160px" v-model="item.key" />
</el-form-item>
</div>
<div class="mr-2">
<el-select class="w-100px!" v-model="item.type">
<el-option
v-for="types in LISTENER_MAP_TYPES"
:key="types.value"
:label="types.label"
:value="types.value"
/>
</el-select>
</div>
<div class="mr-2">
<el-form-item
:prop="`task${listener.type}ListenerBody.${index}.value`"
:rules="{
required: true,
message: '参数值不能为空',
trigger: 'blur'
}"
>
<el-input
v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
class="w-160px"
v-model="item.value"
/>
</el-form-item>
<el-form-item
:prop="`task${listener.type}ListenerBody.${index}.value`"
:rules="{
required: true,
message: '参数值不能为空',
trigger: 'change'
}"
>
<el-select
v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
class="w-160px!"
v-model="item.value"
>
<el-option
v-for="(field, fIdx) in formFieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="!field.required"
/>
</el-select>
</el-form-item>
</div>
<div class="mr-1 flex items-center">
<Icon
icon="ep:delete"
:size="18"
@click="
deleteTaskListenerParam(configForm[`task${listener.type}ListenerBody`], index)
"
/>
</div>
</div>
<el-button
type="primary"
text
@click="addTaskListenerParam(configForm[`task${listener.type}ListenerBody`])"
>
<Icon icon="ep:plus" class="mr-5px" />添加一行
</el-button>
</el-form-item>
</div>
</div>
</el-form>
</template>
<script setup lang="ts">
import { LISTENER_MAP_TYPES, ListenerParamTypeEnum } from '../../consts'
const props = defineProps({
modelValue: {
type: Object,
required: true
},
formFieldOptions: {
type: Object,
required: true
}
})
const emit = defineEmits(['update:modelValue'])
const listenerFormRef = ref()
const configForm = computed({
get() {
return props.modelValue
},
set(newValue) {
emit('update:modelValue', newValue)
}
})
const taskListener = ref([
{
name: '创建任务',
type: 'Create'
},
{
name: '指派任务执行人员',
type: 'Assign'
},
{
name: '完成任务',
type: 'Complete'
}
])
const addTaskListenerParam = (arr) => {
arr.push({
key: '',
type: 1,
value: ''
})
}
const deleteTaskListenerParam = (arr, index) => {
arr.splice(index, 1)
}
const validate = async () => {
if (!listenerFormRef) return false
return await listenerFormRef.value.validate()
}
defineExpose({ validate })
</script>

View File

@ -31,7 +31,7 @@
{{ currentNode.showText }} {{ currentNode.showText }}
</div> </div>
<div class="node-text" v-else> <div class="node-text" v-else>
{{ NODE_DEFAULT_TEXT.get(NodeType.ROUTE_BRANCH_NODE) }} {{ NODE_DEFAULT_TEXT.get(NodeType.ROUTER_BRANCH_NODE) }}
</div> </div>
<Icon v-if="!readonly" icon="ep:arrow-right-bold" /> <Icon v-if="!readonly" icon="ep:arrow-right-bold" />
</div> </div>
@ -49,17 +49,17 @@
:current-node="currentNode" :current-node="currentNode"
/> />
</div> </div>
<RouteNodeConfig v-if="!readonly && currentNode" ref="nodeSetting" :flow-node="currentNode" /> <RouterNodeConfig v-if="!readonly && currentNode" ref="nodeSetting" :flow-node="currentNode" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts' import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
import NodeHandler from '../NodeHandler.vue' import NodeHandler from '../NodeHandler.vue'
import { useNodeName2, useWatchNode, useTaskStatusClass } from '../node' import { useNodeName2, useWatchNode, useTaskStatusClass } from '../node'
import RouteNodeConfig from '../nodes-config/RouteNodeConfig.vue' import RouterNodeConfig from '../nodes-config/RouterNodeConfig.vue'
defineOptions({ defineOptions({
name: 'RouteNode' name: 'RouterNode'
}) })
const props = defineProps({ const props = defineProps({
@ -77,7 +77,7 @@ const readonly = inject<Boolean>('readonly')
// //
const currentNode = useWatchNode(props) const currentNode = useWatchNode(props)
// //
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.ROUTE_BRANCH_NODE) const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.ROUTER_BRANCH_NODE)
const nodeSetting = ref() const nodeSetting = ref()
// //

View File

@ -308,28 +308,6 @@ const props = defineProps({
} }
}) })
// value,
watch(
() => props.value,
(newValue) => {
if (newValue && bpmnModeler) {
createNewDiagram(newValue)
}
},
{ immediate: true }
)
// processIdprocessName
watch(
[() => props.processId, () => props.processName],
([newId, newName]) => {
if (newId && newName && !props.value) {
createNewDiagram(null)
}
},
{ immediate: true }
)
provide('configGlobal', props) provide('configGlobal', props)
let bpmnModeler: any = null let bpmnModeler: any = null
const defaultZoom = ref(1) const defaultZoom = ref(1)
@ -480,6 +458,7 @@ const initModelListeners = () => {
emit('commandStack-changed', event) emit('commandStack-changed', event)
emit('input', xml) emit('input', xml)
emit('change', xml) emit('change', xml)
emit('save', xml)
} catch (e: any) { } catch (e: any) {
console.error(`[Process Designer Warn]: ${e.message || e}`) console.error(`[Process Designer Warn]: ${e.message || e}`)
} }
@ -568,6 +547,7 @@ const importLocalFile = () => {
reader.onload = function () { reader.onload = function () {
let xmlStr = this.result let xmlStr = this.result
createNewDiagram(xmlStr) createNewDiagram(xmlStr)
emit('save', xmlStr)
} }
} }
/* ------------------------------------------------ refs methods ------------------------------------------------------ */ /* ------------------------------------------------ refs methods ------------------------------------------------------ */

View File

@ -1438,6 +1438,45 @@
"isBody": true "isBody": true
} }
] ]
},
{
"name": "SignEnable",
"superClass": ["Element"],
"meta": {
"allowedIn": ["bpmn:UserTask"]
},
"properties": [
{
"name": "value",
"type": "Boolean",
"isBody": true
}
]
},
{
"name": "SkipExpression",
"extends": ["bpmn:UserTask"],
"properties": [
{
"name": "skipExpression",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "ReasonRequire",
"superClass": ["Element"],
"meta": {
"allowedIn": ["bpmn:UserTask"]
},
"properties": [
{
"name": "value",
"type": "Boolean",
"isBody": true
}
]
} }
], ],
"emumerations": [] "emumerations": []

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="process-panel__container" :style="{ width: `${width}px` }"> <div class="process-panel__container" :style="{ width: `${width}px`, maxHeight: '600px' }">
<el-collapse v-model="activeTab" v-if="isReady"> <el-collapse v-model="activeTab" v-if="isReady">
<el-collapse-item name="base"> <el-collapse-item name="base">
<!-- class="panel-tab__title" --> <!-- class="panel-tab__title" -->

View File

@ -152,6 +152,9 @@ watch(
handleKeyUpdate(props.model.key) handleKeyUpdate(props.model.key)
handleNameUpdate(props.model.name) handleNameUpdate(props.model.name)
} }
},
{
immediate: true
} }
) )

View File

@ -5,6 +5,7 @@
4. 操作按钮 4. 操作按钮
5. 字段权限 5. 字段权限
6. 审批类型 6. 审批类型
7. 是否需要签名
--> -->
<template> <template>
<div> <div>
@ -161,6 +162,16 @@
</el-radio-group> </el-radio-group>
</div> </div>
</div> </div>
<el-divider content-position="left">是否需要签名</el-divider>
<el-form-item prop="signEnable">
<el-switch v-model="signEnable.value" active-text="" inactive-text="" />
</el-form-item>
<el-divider content-position="left">审批意见</el-divider>
<el-form-item prop="reasonRequire">
<el-switch v-model="reasonRequire.value" active-text="" inactive-text="" />
</el-form-item>
</div> </div>
</template> </template>
@ -218,6 +229,12 @@ const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFie
// //
const approveType = ref({ value: ApproveType.USER }) const approveType = ref({ value: ApproveType.USER })
//
const signEnable = ref({ value: false })
//
const reasonRequire = ref({ value: false })
const elExtensionElements = ref() const elExtensionElements = ref()
const otherExtensions = ref() const otherExtensions = ref()
const bpmnElement = ref() const bpmnElement = ref()
@ -311,6 +328,16 @@ const resetCustomConfigList = () => {
}) })
} }
//
signEnable.value =
elExtensionElements.value.values?.filter((ex) => ex.$type === `${prefix}:SignEnable`)?.[0] ||
bpmnInstances().moddle.create(`${prefix}:SignEnable`, { value: false })
//
reasonRequire.value =
elExtensionElements.value.values?.filter((ex) => ex.$type === `${prefix}:ReasonRequire`)?.[0] ||
bpmnInstances().moddle.create(`${prefix}:ReasonRequire`, { value: false })
// 便 // 便
otherExtensions.value = otherExtensions.value =
elExtensionElements.value.values?.filter( elExtensionElements.value.values?.filter(
@ -322,7 +349,9 @@ const resetCustomConfigList = () => {
ex.$type !== `${prefix}:AssignEmptyUserIds` && ex.$type !== `${prefix}:AssignEmptyUserIds` &&
ex.$type !== `${prefix}:ButtonsSetting` && ex.$type !== `${prefix}:ButtonsSetting` &&
ex.$type !== `${prefix}:FieldsPermission` && ex.$type !== `${prefix}:FieldsPermission` &&
ex.$type !== `${prefix}:ApproveType` ex.$type !== `${prefix}:ApproveType` &&
ex.$type !== `${prefix}:SignEnable` &&
ex.$type !== `${prefix}:ReasonRequire`
) ?? [] ) ?? []
// //
@ -373,7 +402,9 @@ const updateElementExtensions = () => {
assignEmptyUserIdsEl.value, assignEmptyUserIdsEl.value,
approveType.value, approveType.value,
...buttonsSettingEl.value, ...buttonsSettingEl.value,
...fieldsPermissionEl.value ...fieldsPermissionEl.value,
signEnable.value,
reasonRequire.value
] ]
}) })
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), { bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {

View File

@ -192,6 +192,16 @@
<!-- 选择弹窗 --> <!-- 选择弹窗 -->
<ProcessExpressionDialog ref="processExpressionDialogRef" @select="selectProcessExpression" /> <ProcessExpressionDialog ref="processExpressionDialogRef" @select="selectProcessExpression" />
</el-form-item> </el-form-item>
<el-form-item label="跳过表达式" prop="skipExpression">
<el-input
type="textarea"
v-model="userTaskForm.skipExpression"
clearable
style="width: 100%"
@change="updateSkipExpression"
/>
</el-form-item>
</el-form> </el-form>
</template> </template>
@ -220,7 +230,8 @@ const props = defineProps({
const prefix = inject('prefix') const prefix = inject('prefix')
const userTaskForm = ref({ const userTaskForm = ref({
candidateStrategy: undefined, // candidateStrategy: undefined, //
candidateParam: [] // candidateParam: [], //
skipExpression: '' //
}) })
const bpmnElement = ref() const bpmnElement = ref()
const bpmnInstances = () => (window as any)?.bpmnInstances const bpmnInstances = () => (window as any)?.bpmnInstances
@ -311,6 +322,13 @@ const resetTaskForm = () => {
(ex) => ex.$type !== `${prefix}:CandidateStrategy` && ex.$type !== `${prefix}:CandidateParam` (ex) => ex.$type !== `${prefix}:CandidateStrategy` && ex.$type !== `${prefix}:CandidateParam`
) ?? [] ) ?? []
//
if (businessObject.skipExpression != undefined) {
userTaskForm.value.skipExpression = businessObject.skipExpression
} else {
userTaskForm.value.skipExpression = ''
}
// extensionElements // extensionElements
return return
if (businessObject.candidateStrategy != undefined) { if (businessObject.candidateStrategy != undefined) {
@ -390,6 +408,18 @@ const updateElementTask = () => {
}) })
} }
const updateSkipExpression = () => {
if (userTaskForm.value.skipExpression && userTaskForm.value.skipExpression !== '') {
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
skipExpression: userTaskForm.value.skipExpression
})
} else {
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
skipExpression: null
})
}
}
// //
const processExpressionDialogRef = ref() const processExpressionDialogRef = ref()
const openProcessExpressionDialog = async () => { const openProcessExpressionDialog = async () => {

View File

@ -1,6 +1,4 @@
@use 'bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css'; @use 'bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css';
@use 'bpmn-js-token-simulation/assets/css/font-awesome.min.css';
@use 'bpmn-js-token-simulation/assets/css/normalize.css';
// token-simulation // token-simulation
.djs-palette { .djs-palette {
@ -97,12 +95,12 @@
box-sizing: border-box; box-sizing: border-box;
} }
} }
svg { // svg {
width: 100%; // width: 100%;
height: 100%; // height: 100%;
min-height: 100%; // min-height: 100%;
overflow: hidden; // overflow: hidden;
} // }
} }
} }

View File

@ -5,18 +5,10 @@ const { t } = useI18n() // 国际化
export function hasPermi(app: App<Element>) { export function hasPermi(app: App<Element>) {
app.directive('hasPermi', (el, binding) => { app.directive('hasPermi', (el, binding) => {
const { wsCache } = useCache()
const { value } = binding const { value } = binding
const all_permission = '*:*:*'
const userInfo = wsCache.get(CACHE_KEY.USER)
const permissions = userInfo?.permissions || []
if (value && value instanceof Array && value.length > 0) { if (value && value instanceof Array && value.length > 0) {
const permissionFlag = value const hasPermissions = hasPermission(value)
const hasPermissions = permissions.some((permission: string) => {
return all_permission === permission || permissionFlag.includes(permission)
})
if (!hasPermissions) { if (!hasPermissions) {
el.parentNode && el.parentNode.removeChild(el) el.parentNode && el.parentNode.removeChild(el)
@ -26,3 +18,14 @@ export function hasPermi(app: App<Element>) {
} }
}) })
} }
export const hasPermission = (permission: string[]) => {
const { wsCache } = useCache()
const all_permission = '*:*:*'
const userInfo = wsCache.get(CACHE_KEY.USER)
const permissions = userInfo?.permissions || []
return permissions.some((p: string) => {
return all_permission === p || permission.includes(p)
})
}

View File

@ -12,6 +12,9 @@ const prefixCls = getPrefixCls('footer')
const appStore = useAppStore() const appStore = useAppStore()
const title = computed(() => appStore.getTitle) const title = computed(() => appStore.getTitle)
//
const currentYear = computed(() => new Date().getFullYear())
</script> </script>
<template> <template>
@ -19,6 +22,6 @@ const title = computed(() => appStore.getTitle)
:class="prefixCls" :class="prefixCls"
class="h-[var(--app-footer-height)] bg-[var(--app-content-bg-color)] text-center leading-[var(--app-footer-height)] text-[var(--el-text-color-placeholder)] dark:bg-[var(--el-bg-color)] overflow-hidden" class="h-[var(--app-footer-height)] bg-[var(--app-content-bg-color)] text-center leading-[var(--app-footer-height)] text-[var(--el-text-color-placeholder)] dark:bg-[var(--el-bg-color)] overflow-hidden"
> >
<span class="text-14px">Copyright ©2022-{{ title }}</span> <span class="text-14px">Copyright ©{{ currentYear }} {{ title }}</span>
</div> </div>
</template> </template>

View File

@ -12,6 +12,10 @@ import { useDesign } from '@/hooks/web/useDesign'
import { useTemplateRefsList } from '@vueuse/core' import { useTemplateRefsList } from '@vueuse/core'
import { ElScrollbar } from 'element-plus' import { ElScrollbar } from 'element-plus'
import { useScrollTo } from '@/hooks/event/useScrollTo' import { useScrollTo } from '@/hooks/event/useScrollTo'
import { useTagsView } from '@/hooks/web/useTagsView'
import { cloneDeep } from 'lodash-es'
defineOptions({ name: 'TagsView' })
const { getPrefixCls } = useDesign() const { getPrefixCls } = useDesign()
@ -19,7 +23,9 @@ const prefixCls = getPrefixCls('tags-view')
const { t } = useI18n() const { t } = useI18n()
const { currentRoute, push, replace } = useRouter() const { currentRoute, push } = useRouter()
const { closeAll, closeLeft, closeRight, closeOther, closeCurrent, refreshPage } = useTagsView()
const permissionStore = usePermissionStore() const permissionStore = usePermissionStore()
@ -31,6 +37,10 @@ const visitedViews = computed(() => tagsViewStore.getVisitedViews)
const affixTagArr = ref<RouteLocationNormalizedLoaded[]>([]) const affixTagArr = ref<RouteLocationNormalizedLoaded[]>([])
const selectedTag = computed(() => tagsViewStore.getSelectedTag)
const setSelectTag = tagsViewStore.setSelectedTag
const appStore = useAppStore() const appStore = useAppStore()
const tagsViewImmerse = computed(() => appStore.getTagsViewImmerse) const tagsViewImmerse = computed(() => appStore.getTagsViewImmerse)
@ -45,66 +55,30 @@ const initTags = () => {
for (const tag of unref(affixTagArr)) { for (const tag of unref(affixTagArr)) {
// Must have tag name // Must have tag name
if (tag.name) { if (tag.name) {
tagsViewStore.addVisitedView(tag) tagsViewStore.addVisitedView(cloneDeep(tag))
} }
} }
} }
const selectedTag = ref<RouteLocationNormalizedLoaded>()
// tag // tag
const addTags = () => { const addTags = () => {
const { name } = unref(currentRoute) const { name } = unref(currentRoute)
if (name) { if (name) {
selectedTag.value = unref(currentRoute) setSelectTag(unref(currentRoute))
tagsViewStore.addView(unref(currentRoute)) tagsViewStore.addView(unref(currentRoute))
} }
return false
} }
// tag // tag
const closeSelectedTag = (view: RouteLocationNormalizedLoaded) => { const closeSelectedTag = (view: RouteLocationNormalizedLoaded) => {
if (view?.meta?.affix) return closeCurrent(view, () => {
tagsViewStore.delView(view) if (isActive(view)) {
if (isActive(view)) { toLastView()
toLastView() }
}
}
//
const closeAllTags = () => {
tagsViewStore.delAllViews()
toLastView()
}
//
const closeOthersTags = () => {
tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
}
//
const refreshSelectedTag = async (view?: RouteLocationNormalizedLoaded) => {
if (!view) return
tagsViewStore.delCachedView()
const { path, query } = view
await nextTick()
replace({
path: '/redirect' + path,
query: query
}) })
} }
// //
const closeLeftTags = () => {
tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
}
//
const closeRightTags = () => {
tagsViewStore.delRightViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
}
//
const toLastView = () => { const toLastView = () => {
const visitedViews = tagsViewStore.getVisitedViews const visitedViews = tagsViewStore.getVisitedViews
const latestView = visitedViews.slice(-1)[0] const latestView = visitedViews.slice(-1)[0]
@ -118,11 +92,38 @@ const toLastView = () => {
addTags() addTags()
return return
} }
// TODO: You can set another route // You can set another route
push('/') push(permissionStore.getAddRouters[0].path)
} }
} }
//
const closeAllTags = () => {
closeAll(() => {
toLastView()
})
}
//
const closeOthersTags = () => {
closeOther()
}
//
const refreshSelectedTag = async (view?: RouteLocationNormalizedLoaded) => {
refreshPage(view)
}
//
const closeLeftTags = () => {
closeLeft()
}
//
const closeRightTags = () => {
closeRight()
}
// tag // tag
const moveToCurrentTag = async () => { const moveToCurrentTag = async () => {
await nextTick() await nextTick()
@ -209,13 +210,14 @@ const isActive = (route: RouteLocationNormalizedLoaded): boolean => {
// //
const itemRefs = useTemplateRefsList<ComponentRef<typeof ContextMenu & ContextMenuExpose>>() const itemRefs = useTemplateRefsList<ComponentRef<typeof ContextMenu & ContextMenuExpose>>()
// //
const visibleChange = (visible: boolean, tagItem: RouteLocationNormalizedLoaded) => { const visibleChange = (visible: boolean, tagItem: RouteLocationNormalizedLoaded) => {
if (visible) { if (visible) {
for (const v of unref(itemRefs)) { for (const v of unref(itemRefs)) {
const elDropdownMenuRef = v.elDropdownMenuRef const elDropdownMenuRef = v.elDropdownMenuRef
if (tagItem.fullPath !== v.tagItem.fullPath) { if (tagItem.fullPath !== v.tagItem.fullPath) {
elDropdownMenuRef?.handleClose() elDropdownMenuRef?.handleClose()
setSelectTag(tagItem)
} }
} }
} }
@ -243,7 +245,17 @@ const move = (to: number) => {
start() start()
} }
onMounted(() => { const canShowIcon = (item: RouteLocationNormalizedLoaded) => {
if (
(item?.matched?.[1]?.meta?.icon && unref(tagsViewIcon)) ||
(item?.meta?.affix && unref(tagsViewIcon) && item?.meta?.icon)
) {
return true
}
return false
}
onBeforeMount(() => {
initTags() initTags()
addTags() addTags()
}) })

View File

@ -197,7 +197,7 @@ export const useRenderLayout = () => {
`${prefixCls}-content-scrollbar`, `${prefixCls}-content-scrollbar`,
{ {
'!h-[calc(100%-var(--tags-view-height))] mt-[calc(var(--tags-view-height))]': '!h-[calc(100%-var(--tags-view-height))] mt-[calc(var(--tags-view-height))]':
fixedHeader.value fixedHeader.value && tagsView.value
} }
]} ]}
> >

View File

@ -140,7 +140,10 @@ export default {
btnQRCode: 'QR code sign in', btnQRCode: 'QR code sign in',
qrcode: 'Scan the QR code to log in', qrcode: 'Scan the QR code to log in',
btnRegister: 'Sign up', btnRegister: 'Sign up',
SmsSendMsg: 'code has been sent' SmsSendMsg: 'code has been sent',
resetPassword: "Reset Password",
resetPasswordSuccess: "Reset Password Success",
invalidTenantName:"Invalid Tenant Name"
}, },
captcha: { captcha: {
verification: 'Please complete security verification', verification: 'Please complete security verification',

View File

@ -141,7 +141,10 @@ export default {
btnQRCode: '二维码登录', btnQRCode: '二维码登录',
qrcode: '扫描二维码登录', qrcode: '扫描二维码登录',
btnRegister: '注册', btnRegister: '注册',
SmsSendMsg: '验证码已发送' SmsSendMsg: '验证码已发送',
resetPassword: "重置密码",
resetPasswordSuccess: "重置密码成功",
invalidTenantName: "无效的租户名称"
}, },
captcha: { captcha: {
verification: '请完成安全验证', verification: '请完成安全验证',

View File

@ -344,7 +344,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
} }
}, },
{ {
path: 'manager/model/update/:id', // TODO @zws1建议在加一个路由。然后标题是“复制流程”这样体验会好点2复制出来的数据在名字前面加“副本 ”,和钉钉保持一致!
path: 'manager/model/:type/:id',
component: () => import('@/views/bpm/model/form/index.vue'), component: () => import('@/views/bpm/model/form/index.vue'),
name: 'BpmModelUpdate', name: 'BpmModelUpdate',
meta: { meta: {

View File

@ -4,16 +4,19 @@ import { getRawRoute } from '@/utils/routerHelper'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { store } from '../index' import { store } from '../index'
import { findIndex } from '@/utils' import { findIndex } from '@/utils'
import { useUserStoreWithOut } from './user'
export interface TagsViewState { export interface TagsViewState {
visitedViews: RouteLocationNormalizedLoaded[] visitedViews: RouteLocationNormalizedLoaded[]
cachedViews: Set<string> cachedViews: Set<string>
selectedTag?: RouteLocationNormalizedLoaded
} }
export const useTagsViewStore = defineStore('tagsView', { export const useTagsViewStore = defineStore('tagsView', {
state: (): TagsViewState => ({ state: (): TagsViewState => ({
visitedViews: [], visitedViews: [],
cachedViews: new Set() cachedViews: new Set(),
selectedTag: undefined
}), }),
getters: { getters: {
getVisitedViews(): RouteLocationNormalizedLoaded[] { getVisitedViews(): RouteLocationNormalizedLoaded[] {
@ -21,6 +24,9 @@ export const useTagsViewStore = defineStore('tagsView', {
}, },
getCachedViews(): string[] { getCachedViews(): string[] {
return Array.from(this.cachedViews) return Array.from(this.cachedViews)
},
getSelectedTag(): RouteLocationNormalizedLoaded | undefined {
return this.selectedTag
} }
}, },
actions: { actions: {
@ -98,8 +104,12 @@ export const useTagsViewStore = defineStore('tagsView', {
}, },
// 删除所有tag // 删除所有tag
delAllVisitedViews() { delAllVisitedViews() {
const userStore = useUserStoreWithOut()
// const affixTags = this.visitedViews.filter((tag) => tag.meta.affix) // const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)
this.visitedViews = [] this.visitedViews = userStore.getUser
? this.visitedViews.filter((tag) => tag?.meta?.affix)
: []
}, },
// 删除其他 // 删除其他
delOthersViews(view: RouteLocationNormalizedLoaded) { delOthersViews(view: RouteLocationNormalizedLoaded) {
@ -145,6 +155,18 @@ export const useTagsViewStore = defineStore('tagsView', {
break break
} }
} }
},
// 设置当前选中的 tag
setSelectedTag(tag: RouteLocationNormalizedLoaded) {
this.selectedTag = tag
},
setTitle(title: string, path?: string) {
for (const v of this.visitedViews) {
if (v.path === (path ?? this.selectedTag?.path)) {
v.meta.title = title
break
}
}
} }
}, },
persist: false persist: false

View File

@ -33,6 +33,10 @@ const download = {
markdown: (data: Blob, fileName: string) => { markdown: (data: Blob, fileName: string) => {
download0(data, fileName, 'text/markdown') download0(data, fileName, 'text/markdown')
}, },
// 下载 Json 方法
json: (data: Blob, fileName: string) => {
download0(data, fileName, 'application/json')
},
// 下载图片(允许跨域) // 下载图片(允许跨域)
image: ({ image: ({
url, url,
@ -65,6 +69,31 @@ const download = {
a.download = 'image.png' a.download = 'image.png'
a.click() a.click()
} }
},
base64ToFile: (base64: any, fileName: string) => {
// 将base64按照 , 进行分割 将前缀 与后续内容分隔开
const data = base64.split(',')
// 利用正则表达式 从前缀中获取图片的类型信息image/png、image/jpeg、image/webp等
const type = data[0].match(/:(.*?);/)[1]
// 从图片的类型信息中 获取具体的文件格式后缀png、jpeg、webp
const suffix = type.split('/')[1]
// 使用atob()对base64数据进行解码 结果是一个文件数据流 以字符串的格式输出
const bstr = window.atob(data[1])
// 获取解码结果字符串的长度
let n = bstr.length
// 根据解码结果字符串的长度创建一个等长的整形数字数组
// 但在创建时 所有元素初始值都为 0
const u8arr = new Uint8Array(n)
// 将整形数组的每个元素填充为解码结果字符串对应位置字符的UTF-16 编码单元
while (n--) {
// charCodeAt():获取给定索引处字符对应的 UTF-16 代码单元
u8arr[n] = bstr.charCodeAt(n)
}
// 将File文件对象返回给方法的调用者
return new File([u8arr], `${fileName}.${suffix}`, {
type: type
})
} }
} }

View File

@ -73,7 +73,7 @@ export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecord
noCache: !route.keepAlive, noCache: !route.keepAlive,
alwaysShow: alwaysShow:
route.children && route.children &&
route.children.length === 1 && route.children.length > 0 &&
(route.alwaysShow !== undefined ? route.alwaysShow : true) (route.alwaysShow !== undefined ? route.alwaysShow : true)
} as any } as any
// 特殊逻辑:如果后端配置的 MenuDO.component 包含 ?,则表示需要传递参数 // 特殊逻辑:如果后端配置的 MenuDO.component 包含 ?,则表示需要传递参数
@ -100,7 +100,6 @@ export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecord
//处理顶级非目录路由 //处理顶级非目录路由
if (!route.children && route.parentId == 0 && route.component) { if (!route.children && route.parentId == 0 && route.component) {
data.component = Layout data.component = Layout
data.meta = {}
data.name = toCamelCase(route.path, true) + 'Parent' data.name = toCamelCase(route.path, true) + 'Parent'
data.redirect = '' data.redirect = ''
meta.alwaysShow = true meta.alwaysShow = true

View File

@ -59,6 +59,8 @@
<RegisterForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" /> <RegisterForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
<!-- 三方登录 --> <!-- 三方登录 -->
<SSOLoginVue class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" /> <SSOLoginVue class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
<!-- 忘记密码 -->
<ForgetPasswordForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
</div> </div>
</Transition> </Transition>
</div> </div>
@ -73,7 +75,7 @@ import { useAppStore } from '@/store/modules/app'
import { ThemeSwitch } from '@/layout/components/ThemeSwitch' import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
import { LocaleDropdown } from '@/layout/components/LocaleDropdown' import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue } from './components' import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue, ForgetPasswordForm } from './components'
defineOptions({ name: 'Login' }) defineOptions({ name: 'Login' })

View File

@ -0,0 +1,278 @@
<template>
<el-form
v-show="getShow"
ref="formSmsResetPassword"
:model="resetPasswordData"
:rules="rules"
class="login-form"
label-position="top"
label-width="120px"
size="large"
>
<el-row style="margin-right: -10px; margin-left: -10px">
<!-- 租户名 -->
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item>
<LoginFormTitle style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item v-if="resetPasswordData.tenantEnable === 'true'" prop="tenantName">
<el-input
v-model="resetPasswordData.tenantName"
:placeholder="t('login.tenantNamePlaceholder')"
:prefix-icon="iconHouse"
type="primary"
link
/>
</el-form-item>
</el-col>
<!-- 手机号 -->
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item prop="mobile">
<el-input
v-model="resetPasswordData.mobile"
:placeholder="t('login.mobileNumberPlaceholder')"
:prefix-icon="iconCellphone"
/>
</el-form-item>
</el-col>
<Verify
ref="verify"
:captchaType="captchaType"
:imgSize="{ width: '400px', height: '200px' }"
mode="pop"
@success="getSmsCode"
/>
<!-- 验证码 -->
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item prop="code">
<el-row :gutter="5" justify="space-between" style="width: 100%">
<el-col :span="24">
<el-input
v-model="resetPasswordData.code"
:placeholder="t('login.codePlaceholder')"
:prefix-icon="iconCircleCheck"
>
<template #append>
<span
v-if="mobileCodeTimer <= 0"
class="getMobileCode"
style="cursor: pointer"
@click="getCode"
>
{{ t('login.getSmsCode') }}
</span>
<span v-if="mobileCodeTimer > 0" class="getMobileCode" style="cursor: pointer">
{{ mobileCodeTimer }}秒后可重新获取
</span>
</template>
</el-input>
<!-- </el-button> -->
</el-col>
</el-row>
</el-form-item>
</el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item prop="password">
<InputPassword
v-model="resetPasswordData.password"
:placeholder="t('login.passwordPlaceholder')"
style="width: 100%"
strength="true"
/>
</el-form-item>
</el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item prop="check_password">
<InputPassword
v-model="resetPasswordData.check_password"
:placeholder="t('login.checkPassword')"
style="width: 100%"
strength="true"
/>
</el-form-item>
</el-col>
<!-- 登录按钮 / 返回按钮 -->
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item>
<XButton
:loading="loginLoading"
:title="t('login.resetPassword')"
class="w-[100%]"
type="primary"
@click="resetPassword()"
/>
</el-form-item>
</el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item>
<XButton
:loading="loginLoading"
:title="t('login.backLogin')"
class="w-[100%]"
@click="handleBackLogin()"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script lang="ts" setup>
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import { useIcon } from '@/hooks/web/useIcon'
import { sendSmsCode, smsResetPassword } from '@/api/login'
import LoginFormTitle from './LoginFormTitle.vue'
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
import { ElLoading } from 'element-plus'
import * as authUtil from '@/utils/auth'
import * as LoginApi from '@/api/login'
defineOptions({ name: 'ForgetPasswordForm' })
const verify = ref()
const { t } = useI18n()
const message = useMessage()
const { currentRoute, push } = useRouter()
const formSmsResetPassword = ref()
const loginLoading = ref(false)
const iconHouse = useIcon({ icon: 'ep:house' })
const iconCellphone = useIcon({ icon: 'ep:cellphone' })
const iconCircleCheck = useIcon({ icon: 'ep:circle-check' })
const { validForm } = useFormValid(formSmsResetPassword)
const { handleBackLogin, getLoginState, setLoginState } = useLoginState()
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.RESET_PASSWORD)
const captchaType = ref('blockPuzzle') // blockPuzzle clickWord
const validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'))
} else if (value !== resetPasswordData.password) {
callback(new Error('两次输入密码不一致!'))
} else {
callback()
}
}
const rules = {
tenantName: [{ required: true, min: 2, max: 20, trigger: 'blur', message: '长度为4到16位' }],
mobile: [{ required: true, min: 11, max: 11, trigger: 'blur', message: '手机号长度为11位' }],
password: [
{
required: true,
min: 4,
max: 16,
validator: validatePass2,
trigger: 'blur',
message: '密码长度为4到16位'
}
],
check_password: [{ required: true, validator: validatePass2, trigger: 'blur' }],
code: [required]
}
const resetPasswordData = reactive({
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
tenantName: '',
username: '',
password: '',
check_password: '',
mobile: '',
code: ''
})
const smsVO = reactive({
tenantName: '',
mobile: '',
captchaVerification: '',
scene: 23
})
const mobileCodeTimer = ref(0)
const redirect = ref<string>('')
//
const getCode = async () => {
//
if (resetPasswordData.captchaEnable === 'false') {
await getSmsCode({})
} else {
//
//
verify.value.show()
}
}
const getSmsCode = async (params) => {
if (resetPasswordData.tenantEnable === 'true') {
await getTenantId()
}
smsVO.captchaVerification = params.captchaVerification
smsVO.mobile = resetPasswordData.mobile
await sendSmsCode(smsVO).then(async () => {
message.success(t('login.SmsSendMsg'))
//
mobileCodeTimer.value = 60
let msgTimer = setInterval(() => {
mobileCodeTimer.value = mobileCodeTimer.value - 1
if (mobileCodeTimer.value <= 0) {
clearInterval(msgTimer)
}
}, 1000)
})
}
watch(
() => currentRoute.value,
(route: RouteLocationNormalizedLoaded) => {
redirect.value = route?.query?.redirect as string
},
{
immediate: true
}
)
const getTenantId = async () => {
if (resetPasswordData.tenantEnable === 'true') {
const res = await LoginApi.getTenantIdByName(resetPasswordData.tenantName)
if (res == null) {
message.error(t('login.invalidTenantName'))
throw t('login.invalidTenantName')
}
authUtil.setTenantId(res)
}
}
//
const resetPassword = async () => {
const data = await validForm()
if (!data) return
await getTenantId()
loginLoading.value = true
await smsResetPassword(resetPasswordData)
.then(async () => {
message.success(t('login.resetPasswordSuccess'))
setLoginState(LoginStateEnum.LOGIN)
})
.catch(() => {})
.finally(() => {
loginLoading.value = false
setTimeout(() => {
const loadingInstance = ElLoading.service()
loadingInstance.close()
}, 400)
})
}
</script>
<style lang="scss" scoped>
:deep(.anticon) {
&:hover {
color: var(--el-color-primary) !important;
}
}
.smsbtn {
margin-top: 33px;
}
</style>

View File

@ -59,7 +59,13 @@
</el-checkbox> </el-checkbox>
</el-col> </el-col>
<el-col :offset="6" :span="12"> <el-col :offset="6" :span="12">
<el-link style="float: right" type="primary">{{ t('login.forgetPassword') }}</el-link> <el-link
style="float: right"
type="primary"
@click="setLoginState(LoginStateEnum.RESET_PASSWORD)"
>
{{ t('login.forgetPassword') }}
</el-link>
</el-col> </el-col>
</el-row> </el-row>
</el-form-item> </el-form-item>

View File

@ -4,5 +4,6 @@ import LoginFormTitle from './LoginFormTitle.vue'
import RegisterForm from './RegisterForm.vue' import RegisterForm from './RegisterForm.vue'
import QrCodeForm from './QrCodeForm.vue' import QrCodeForm from './QrCodeForm.vue'
import SSOLoginVue from './SSOLogin.vue' import SSOLoginVue from './SSOLogin.vue'
import ForgetPasswordForm from './ForgetPasswordForm.vue'
export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm, SSOLoginVue } export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm, SSOLoginVue, ForgetPasswordForm }

View File

@ -163,6 +163,15 @@
> >
修改 修改
</el-button> </el-button>
<el-button
link
type="primary"
@click="openModelForm('copy', scope.row.id)"
v-hasPermi="['bpm:model:update']"
:disabled="!isManagerUser(scope.row)"
>
复制
</el-button>
<el-button <el-button
link link
class="!ml-5px" class="!ml-5px"
@ -194,6 +203,14 @@
> >
{{ scope.row.processDefinition.suspensionState === 1 ? '停用' : '启用' }} {{ scope.row.processDefinition.suspensionState === 1 ? '停用' : '启用' }}
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item
type="danger"
command="handleClean"
v-if="checkPermi(['bpm:model:clean'])"
:disabled="!isManagerUser(scope.row)"
>
清理
</el-dropdown-item>
<el-dropdown-item <el-dropdown-item
type="danger" type="danger"
command="handleDelete" command="handleDelete"
@ -226,13 +243,9 @@
</div> </div>
</template> </template>
</Dialog> </Dialog>
<!-- 表单弹窗添加流程模型 -->
<ModelForm :categoryId="categoryInfo.code" ref="modelFormRef" @success="emit('success')" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import ModelForm from './ModelForm.vue'
import { CategoryApi, CategoryVO } from '@/api/bpm/category' import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import Sortable from 'sortablejs' import Sortable from 'sortablejs'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
@ -245,6 +258,7 @@ import { checkPermi } from '@/utils/permission'
import { useUserStoreWithOut } from '@/store/modules/user' import { useUserStoreWithOut } from '@/store/modules/user'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { useTagsView } from '@/hooks/web/useTagsView'
defineOptions({ name: 'BpmModel' }) defineOptions({ name: 'BpmModel' })
@ -276,6 +290,9 @@ const handleModelCommand = (command: string, row: any) => {
case 'handleChangeState': case 'handleChangeState':
handleChangeState(row) handleChangeState(row)
break break
case 'handleClean':
handleClean(row)
break
default: default:
break break
} }
@ -309,6 +326,19 @@ const handleDelete = async (row: any) => {
} catch {} } catch {}
} }
/** 清理按钮操作 */
const handleClean = async (row: any) => {
try {
//
await message.confirm('是否确认清理流程名字为"' + row.name + '"的数据项?')
//
await ModelApi.cleanModel(row.id)
message.success('清理成功')
//
emit('success')
} catch {}
}
/** 更新状态操作 */ /** 更新状态操作 */
const handleChangeState = async (row: any) => { const handleChangeState = async (row: any) => {
const state = row.processDefinition.suspensionState const state = row.processDefinition.suspensionState
@ -466,15 +496,19 @@ const handleDeleteCategory = async () => {
} }
/** 添加流程模型弹窗 */ /** 添加流程模型弹窗 */
const modelFormRef = ref() const tagsView = useTagsView()
const openModelForm = (type: string, id?: number) => { const openModelForm = async (type: string, id?: number) => {
if (type === 'create') { if (type === 'create') {
push({ name: 'BpmModelCreate' }) await push({ name: 'BpmModelCreate' })
} else { } else {
push({ await push({
name: 'BpmModelUpdate', name: 'BpmModelUpdate',
params: { id } params: { id, type }
}) })
//
if (type === 'copy') {
tagsView.setTitle('复制流程')
}
} }
} }

View File

@ -1,440 +0,0 @@
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle" width="600">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="110px"
>
<el-form-item label="流程标识" prop="key">
<el-input v-model="formData.key" :disabled="!!formData.id" placeholder="请输入流标标识" />
<el-tooltip
v-if="!formData.id"
class="item"
content="新建后,流程标识不可修改!"
effect="light"
placement="top"
>
<i class="el-icon-question" style="padding-left: 5px"></i>
</el-tooltip>
<el-tooltip v-else class="item" content="流程标识不可修改!" effect="light" placement="top">
<i class="el-icon-question" style="padding-left: 5px"></i>
</el-tooltip>
</el-form-item>
<el-form-item label="流程名称" prop="name">
<el-input
v-model="formData.name"
:disabled="!!formData.id"
clearable
placeholder="请输入流程名称"
/>
</el-form-item>
<el-form-item label="流程分类" prop="category">
<el-select
v-model="formData.category"
clearable
placeholder="请选择流程分类"
style="width: 100%"
>
<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="流程图标" prop="icon">
<UploadImg v-model="formData.icon" :limit="1" height="64px" width="64px" />
</el-form-item>
<el-form-item label="流程描述" prop="description">
<el-input v-model="formData.description" clearable type="textarea" />
</el-form-item>
<el-form-item label="流程类型" prop="type">
<el-radio-group v-model="formData.type">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_TYPE)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="表单类型" prop="formType">
<el-radio-group v-model="formData.formType">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="formData.formType === 10" label="流程表单" prop="formId">
<el-select v-model="formData.formId" clearable style="width: 100%">
<el-option v-for="form in formList" :key="form.id" :label="form.name" :value="form.id" />
</el-select>
</el-form-item>
<el-form-item
v-if="formData.formType === 20"
label="表单提交路由"
prop="formCustomCreatePath"
>
<el-input
v-model="formData.formCustomCreatePath"
placeholder="请输入表单提交路由"
style="width: 330px"
/>
<el-tooltip
class="item"
content="自定义表单的提交路径,使用 Vue 的路由地址例如说bpm/oa/leave/create.vue"
effect="light"
placement="top"
>
<i class="el-icon-question" style="padding-left: 5px"></i>
</el-tooltip>
</el-form-item>
<el-form-item v-if="formData.formType === 20" label="表单查看地址" prop="formCustomViewPath">
<el-input
v-model="formData.formCustomViewPath"
placeholder="请输入表单查看的组件地址"
style="width: 330px"
/>
<el-tooltip
class="item"
content="自定义表单的查看组件地址,使用 Vue 的组件地址例如说bpm/oa/leave/detail.vue"
effect="light"
placement="top"
>
<i class="el-icon-question" style="padding-left: 5px"></i>
</el-tooltip>
</el-form-item>
<el-form-item label="是否可见" prop="visible">
<el-radio-group v-model="formData.visible">
<el-radio
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value as string"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="谁可以发起" prop="startUserType">
<el-select
v-model="formData.startUserType"
placeholder="请选择谁可以发起"
@change="handleStartUserTypeChange"
>
<el-option label="全员" :value="0" />
<el-option label="指定人员" :value="1" />
<el-option label="均不可提交" :value="2" />
</el-select>
<div v-if="formData.startUserType === 1" class="mt-2 flex flex-wrap gap-2">
<div
v-for="user in selectedStartUsers"
:key="user.id"
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 }}
<Icon
icon="ep:close"
class="ml-2 cursor-pointer hover:text-red-500"
@click="handleRemoveStartUser(user)"
/>
</div>
<el-button type="primary" link @click="openStartUserSelect">
<Icon icon="ep:plus" />选择人员
</el-button>
</div>
</el-form-item>
<el-form-item label="流程管理员" prop="managerUserType">
<el-select
v-model="formData.managerUserType"
placeholder="请选择流程管理员"
@change="handleManagerUserTypeChange"
>
<el-option label="全员" :value="0" />
<el-option label="指定人员" :value="1" />
<el-option label="均不可提交" :value="2" />
</el-select>
<div v-if="formData.managerUserType === 1" class="mt-2 flex flex-wrap gap-2">
<div
v-for="user in selectedManagerUsers"
:key="user.id"
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 }}
<Icon
icon="ep:close"
class="ml-2 cursor-pointer hover:text-red-500"
@click="handleRemoveManagerUser(user)"
/>
</div>
<el-button type="primary" link @click="openManagerUserSelect">
<Icon icon="ep:plus" />选择人员
</el-button>
</div>
</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>
<UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
</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'
import * as FormApi from '@/api/bpm/form'
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import { BpmModelFormType, BpmModelType } from '@/utils/constants'
import { UserVO } from '@/api/system/user'
import * as UserApi from '@/api/system/user'
import { useUserStoreWithOut } from '@/store/modules/user'
import { FormVO } from '@/api/bpm/form'
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) // 12
const formType = ref('') // create - update -
const formData: any = ref({
id: undefined,
name: '',
key: '',
category: undefined,
icon: undefined,
description: '',
type: BpmModelType.BPMN,
formType: BpmModelFormType.NORMAL,
formId: '',
formCustomCreatePath: '',
formCustomViewPath: '',
visible: true,
startUserType: undefined,
managerUserType: undefined,
startUserIds: [],
managerUserIds: []
})
const formRules = reactive({
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }],
icon: [{ required: true, message: '流程图标不能为空', trigger: 'blur' }],
type: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
formType: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
formId: [{ required: true, message: '流程表单不能为空', trigger: 'blur' }],
formCustomCreatePath: [{ required: true, message: '表单提交路由不能为空', trigger: 'blur' }],
formCustomViewPath: [{ required: true, message: '表单查看地址不能为空', trigger: 'blur' }],
visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
managerUserIds: [{ required: true, message: '流程管理员不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
const formList = ref<FormVO[]>([]) //
const categoryList = ref<CategoryVO[]>([]) //
const userList = ref<UserVO[]>([]) //
const selectedStartUsers = ref<UserVO[]>([]) //
const selectedManagerUsers = ref<UserVO[]>([]) //
const userSelectFormRef = ref() // ref
const currentSelectType = ref<'start' | 'manager'>('start') //
/** 打开弹窗 */
const open = async (type: string, id?: string) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await ModelApi.getModel(id)
} finally {
formLoading.value = false
}
// ID
if (formData.value.startUserIds?.length) {
formData.value.startUserType = 1
selectedStartUsers.value = userList.value.filter((user) =>
formData.value.startUserIds.includes(user.id)
)
}
if (formData.value.managerUserIds?.length) {
formData.value.managerUserType = 1
selectedManagerUsers.value = userList.value.filter((user) =>
formData.value.managerUserIds.includes(user.id)
)
}
} else {
formData.value.managerUserIds.push(userStore.getUser.id)
}
//
formList.value = await FormApi.getFormSimpleList()
//
categoryList.value = await CategoryApi.getCategorySimpleList()
//
userList.value = await UserApi.getSimpleUserList()
if (props.categoryId) {
formData.value.category = props.categoryId
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as unknown as ModelApi.ModelVO
if (formType.value === 'create') {
await ModelApi.createModel(data)
//
await ElMessageBox.alert(
'<strong>新建模型成功!</strong>后续需要执行如下 2 个步骤:' +
'<div>1. 点击【设计流程】按钮,绘制流程图</div>' +
'<div>2. 点击【发布流程】按钮,完成流程的最终发布</div>' +
'另外,每次流程修改后,都需要点击【发布流程】按钮,才能正式生效!!!',
'重要提示',
{
dangerouslyUseHTMLString: true,
type: 'success'
}
)
} else {
await ModelApi.updateModel(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: '',
key: '',
category: undefined,
icon: undefined,
description: '',
type: BpmModelType.BPMN,
formType: BpmModelFormType.NORMAL,
formId: '',
formCustomCreatePath: '',
formCustomViewPath: '',
visible: true,
startUserType: undefined,
managerUserType: undefined,
startUserIds: [],
managerUserIds: []
}
formRef.value?.resetFields()
selectedStartUsers.value = []
selectedManagerUsers.value = []
}
/** 处理发起人类型变化 */
const handleStartUserTypeChange = (value: number) => {
if (value !== 1) {
selectedStartUsers.value = []
formData.value.startUserIds = []
}
}
/** 处理管理员类型变化 */
const handleManagerUserTypeChange = (value: number) => {
if (value !== 1) {
selectedManagerUsers.value = []
formData.value.managerUserIds = []
}
}
/** 打开发起人选择 */
const openStartUserSelect = () => {
currentSelectType.value = 'start'
userSelectFormRef.value.open(0, selectedStartUsers.value)
}
/** 打开管理员选择 */
const openManagerUserSelect = () => {
currentSelectType.value = 'manager'
userSelectFormRef.value.open(0, selectedManagerUsers.value)
}
/** 处理用户选择确认 */
const handleUserSelectConfirm = (_, users: UserVO[]) => {
if (currentSelectType.value === 'start') {
selectedStartUsers.value = users
formData.value.startUserIds = users.map((u) => u.id)
} else {
selectedManagerUsers.value = users
formData.value.managerUserIds = users.map((u) => u.id)
}
}
/** 移除发起人 */
const handleRemoveStartUser = (user: UserVO) => {
selectedStartUsers.value = selectedStartUsers.value.filter((u) => u.id !== user.id)
formData.value.startUserIds = formData.value.startUserIds.filter((id: number) => id !== user.id)
}
/** 移除管理员 */
const handleRemoveManagerUser = (user: UserVO) => {
selectedManagerUsers.value = selectedManagerUsers.value.filter((u) => u.id !== user.id)
formData.value.managerUserIds = formData.value.managerUserIds.filter(
(id: number) => id !== user.id
)
}
</script>
<style lang="scss" scoped>
.bg-gray-100 {
background-color: #f5f7fa;
transition: all 0.3s;
&:hover {
background-color: #e6e8eb;
}
.ep-close {
font-size: 14px;
color: #909399;
transition: color 0.3s;
&:hover {
color: #f56c6c;
}
}
}
</style>

View File

@ -12,10 +12,12 @@
:additionalModel="controlForm.additionalModel" :additionalModel="controlForm.additionalModel"
:model="model" :model="model"
@save="save" @save="save"
:process-id="modelKey"
:process-name="modelName"
/> />
<!-- 流程属性器负责编辑每个流程节点的属性 --> <!-- 流程属性器负责编辑每个流程节点的属性 -->
<MyProcessPenal <MyProcessPenal
v-if="isModelerReady && modeler" v-if="modeler"
key="penal" key="penal"
:bpmnModeler="modeler" :bpmnModeler="modeler"
:prefix="controlForm.prefix" :prefix="controlForm.prefix"
@ -37,8 +39,8 @@ defineOptions({ name: 'BpmModelEditor' })
const props = defineProps<{ const props = defineProps<{
modelId?: string modelId?: string
modelKey?: string modelKey: string
modelName?: string modelName: string
value?: string value?: string
}>() }>()
@ -51,10 +53,13 @@ const formType = ref(20)
provide('formFields', formFields) provide('formFields', formFields)
provide('formType', formType) provide('formType', formType)
const xmlString = ref<string>('') // BPMN XML //
const xmlString = inject('processData') as Ref
//
const modelData = inject('modelData') as Ref
const modeler = shallowRef() // BPMN Modeler const modeler = shallowRef() // BPMN Modeler
const processDesigner = ref() const processDesigner = ref()
const isModelerReady = ref(false)
const controlForm = ref({ const controlForm = ref({
simulation: true, simulation: true,
labelEditing: false, labelEditing: false,
@ -65,154 +70,26 @@ const controlForm = ref({
}) })
const model = ref<ModelApi.ModelVO>() // const model = ref<ModelApi.ModelVO>() //
// bpmnInstances
const initBpmnInstances = () => {
if (!modeler.value) return false
try {
const instances = {
modeler: modeler.value,
modeling: modeler.value.get('modeling'),
moddle: modeler.value.get('moddle'),
eventBus: modeler.value.get('eventBus'),
bpmnFactory: modeler.value.get('bpmnFactory'),
elementFactory: modeler.value.get('elementFactory'),
elementRegistry: modeler.value.get('elementRegistry'),
replace: modeler.value.get('replace'),
selection: modeler.value.get('selection')
}
//
return Object.values(instances).every((instance) => instance)
} catch (error) {
console.error('初始化 bpmnInstances 失败:', error)
return false
}
}
/** 初始化 modeler */ /** 初始化 modeler */
const initModeler = async (item) => { const initModeler = async (item: any) => {
try { //
modeler.value = item model.value = modelData.value
// modeler modeler.value = item
await nextTick()
// modeler
if (initBpmnInstances()) {
isModelerReady.value = true
emit('init-finished')
//
if (props.modelId) {
//
const data = await ModelApi.getModel(props.modelId)
model.value = {
...data,
bpmnXml: undefined // bpmnXml
}
xmlString.value = data.bpmnXml || getDefaultBpmnXml(data.key, data.name)
} else if (props.modelKey && props.modelName) {
//
xmlString.value = props.value || getDefaultBpmnXml(props.modelKey, props.modelName)
model.value = {
key: props.modelKey,
name: props.modelName
} as ModelApi.ModelVO
}
// XML
await nextTick()
try {
await modeler.value.importXML(xmlString.value)
if (processDesigner.value?.refresh) {
processDesigner.value.refresh()
}
} catch (error) {
console.error('导入XML失败:', error)
}
} else {
console.error('modeler 实例未完全初始化')
}
} catch (error) {
console.error('初始化 modeler 失败:', error)
}
}
/** 获取默认的BPMN XML */
const getDefaultBpmnXml = (key: string, name: string) => {
return `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.activiti.org/processdef">
<process id="${key}" name="${name}" isExecutable="true" />
<bpmndi:BPMNDiagram id="BPMNDiagram">
<bpmndi:BPMNPlane id="${key}_di" bpmnElement="${key}" />
</bpmndi:BPMNDiagram>
</definitions>`
} }
/** 添加/修改模型 */ /** 添加/修改模型 */
const save = async (bpmnXml: string) => { const save = async (bpmnXml: string) => {
try { try {
xmlString.value = bpmnXml xmlString.value = bpmnXml
if (props.modelId) { emit('success', bpmnXml)
//
const data = {
...model.value,
bpmnXml: bpmnXml
} as unknown as ModelApi.ModelVO
await ModelApi.updateModelBpmn(data)
emit('success')
} else {
// XML
emit('success', bpmnXml)
}
} catch (error) { } catch (error) {
console.error('保存失败:', error) console.error('保存失败:', error)
message.error('保存失败') message.error('保存失败')
} }
} }
// keyname value
watch(
[() => props.modelKey, () => props.modelName, () => props.value],
async ([newKey, newName, newValue]) => {
if (!props.modelId && isModelerReady.value) {
let shouldRefresh = false
if (newKey && newName) {
const newXml = newValue || getDefaultBpmnXml(newKey, newName)
if (newXml !== xmlString.value) {
xmlString.value = newXml
shouldRefresh = true
}
model.value = {
...model.value,
key: newKey,
name: newName
} as ModelApi.ModelVO
} else if (newValue && newValue !== xmlString.value) {
xmlString.value = newValue
shouldRefresh = true
}
if (shouldRefresh) {
//
await nextTick()
if (processDesigner.value?.refresh) {
try {
await modeler.value?.importXML(xmlString.value)
processDesigner.value.refresh()
} catch (error) {
console.error('导入XML失败:', error)
}
}
}
}
},
{ deep: true }
)
// //
onBeforeUnmount(() => { onBeforeUnmount(() => {
isModelerReady.value = false
modeler.value = null modeler.value = null
// //
const w = window as any const w = window as any
@ -220,55 +97,6 @@ onBeforeUnmount(() => {
w.bpmnInstances = null w.bpmnInstances = null
} }
}) })
/** 获取 XML 字符串 */
const saveXML = async () => {
if (!modeler.value) {
return { xml: xmlString.value }
}
try {
const result = await modeler.value.saveXML({ format: true })
xmlString.value = result.xml
return result
} catch (error) {
console.error('获取XML失败:', error)
return { xml: xmlString.value }
}
}
/** 获取SVG字符串 */
const saveSVG = async () => {
if (!modeler.value) {
return { svg: undefined }
}
try {
return await modeler.value.saveSVG()
} catch (error) {
console.error('获取SVG失败:', error)
return { svg: undefined }
}
}
/** 刷新视图 */
const refresh = async () => {
if (processDesigner.value?.refresh && modeler.value) {
try {
await modeler.value.importXML(xmlString.value)
processDesigner.value.refresh()
} catch (error) {
console.error('刷新视图失败:', error)
}
}
}
//
defineExpose({
modeler,
isModelerReady,
saveXML,
saveSVG,
refresh
})
</script> </script>
<style lang="scss"> <style lang="scss">
.process-panel__container { .process-panel__container {

View File

@ -62,7 +62,7 @@
<el-radio-group v-model="modelData.visible"> <el-radio-group v-model="modelData.visible">
<el-radio <el-radio
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)" v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value" :key="dict.value as string"
:value="dict.value" :value="dict.value"
> >
{{ dict.label }} {{ dict.label }}
@ -77,7 +77,6 @@
> >
<el-option label="全员" :value="0" /> <el-option label="全员" :value="0" />
<el-option label="指定人员" :value="1" /> <el-option label="指定人员" :value="1" />
<el-option label="均不可提交" :value="2" />
</el-select> </el-select>
<div v-if="modelData.startUserType === 1" class="mt-2 flex flex-wrap gap-2"> <div v-if="modelData.startUserType === 1" class="mt-2 flex flex-wrap gap-2">
<div <div
@ -97,21 +96,12 @@
/> />
</div> </div>
<el-button type="primary" link @click="openStartUserSelect"> <el-button type="primary" link @click="openStartUserSelect">
<Icon icon="ep:plus" />选择人员 <Icon icon="ep:plus" /> 选择人员
</el-button> </el-button>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="流程管理员" prop="managerUserType" class="mb-20px"> <el-form-item label="流程管理员" prop="managerUserIds" class="mb-20px">
<el-select <div class="flex flex-wrap gap-2">
v-model="modelData.managerUserType"
placeholder="请选择流程管理员"
@change="handleManagerUserTypeChange"
>
<el-option label="全员" :value="0" />
<el-option label="指定人员" :value="1" />
<el-option label="均不可提交" :value="2" />
</el-select>
<div v-if="modelData.managerUserType === 1" class="mt-2 flex flex-wrap gap-2">
<div <div
v-for="user in selectedManagerUsers" v-for="user in selectedManagerUsers"
:key="user.id" :key="user.id"
@ -142,14 +132,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
import { UserVO } from '@/api/system/user' import { UserVO } from '@/api/system/user'
import { CategoryVO } from '@/api/bpm/category'
const props = defineProps({ const props = defineProps({
modelValue: {
type: Object,
required: true
},
categoryList: { categoryList: {
type: Array, type: Array as PropType<CategoryVO[]>,
required: true required: true
}, },
userList: { userList: {
@ -158,8 +145,6 @@ const props = defineProps({
} }
}) })
const emit = defineEmits(['update:modelValue'])
const formRef = ref() const formRef = ref()
const selectedStartUsers = ref<UserVO[]>([]) const selectedStartUsers = ref<UserVO[]>([])
const selectedManagerUsers = ref<UserVO[]>([]) const selectedManagerUsers = ref<UserVO[]>([])
@ -177,27 +162,30 @@ const rules = {
} }
// //
const modelData = computed({ const modelData = defineModel<any>()
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
// //
watch( watch(
() => props.modelValue, () => modelData.value,
(newVal) => { (newVal) => {
if (newVal.startUserIds?.length) { if (newVal.startUserIds?.length) {
selectedStartUsers.value = props.userList.filter((user: UserVO) => selectedStartUsers.value = props.userList.filter((user: UserVO) =>
newVal.startUserIds.includes(user.id) newVal.startUserIds.includes(user.id)
) as UserVO[] ) as UserVO[]
} else {
selectedStartUsers.value = []
} }
if (newVal.managerUserIds?.length) { if (newVal.managerUserIds?.length) {
selectedManagerUsers.value = props.userList.filter((user: UserVO) => selectedManagerUsers.value = props.userList.filter((user: UserVO) =>
newVal.managerUserIds.includes(user.id) newVal.managerUserIds.includes(user.id)
) as UserVO[] ) as UserVO[]
} else {
selectedManagerUsers.value = []
} }
}, },
{ immediate: true } {
immediate: true
}
) )
/** 打开发起人选择 */ /** 打开发起人选择 */
@ -215,58 +203,42 @@ const openManagerUserSelect = () => {
/** 处理用户选择确认 */ /** 处理用户选择确认 */
const handleUserSelectConfirm = (_, users: UserVO[]) => { const handleUserSelectConfirm = (_, users: UserVO[]) => {
if (currentSelectType.value === 'start') { if (currentSelectType.value === 'start') {
selectedStartUsers.value = users modelData.value = {
emit('update:modelValue', {
...modelData.value, ...modelData.value,
startUserIds: users.map((u) => u.id) startUserIds: users.map((u) => u.id)
}) }
} else { } else {
selectedManagerUsers.value = users modelData.value = {
emit('update:modelValue', {
...modelData.value, ...modelData.value,
managerUserIds: users.map((u) => u.id) managerUserIds: users.map((u) => u.id)
}) }
} }
} }
/** 处理发起人类型变化 */ /** 处理发起人类型变化 */
const handleStartUserTypeChange = (value: number) => { const handleStartUserTypeChange = (value: number) => {
if (value !== 1) { if (value !== 1) {
selectedStartUsers.value = [] modelData.value = {
emit('update:modelValue', {
...modelData.value, ...modelData.value,
startUserIds: [] startUserIds: []
}) }
}
}
/** 处理管理员类型变化 */
const handleManagerUserTypeChange = (value: number) => {
if (value !== 1) {
selectedManagerUsers.value = []
emit('update:modelValue', {
...modelData.value,
managerUserIds: []
})
} }
} }
/** 移除发起人 */ /** 移除发起人 */
const handleRemoveStartUser = (user: UserVO) => { const handleRemoveStartUser = (user: UserVO) => {
selectedStartUsers.value = selectedStartUsers.value.filter((u) => u.id !== user.id) modelData.value = {
emit('update:modelValue', {
...modelData.value, ...modelData.value,
startUserIds: modelData.value.startUserIds.filter((id: number) => id !== user.id) startUserIds: modelData.value.startUserIds.filter((id: number) => id !== user.id)
}) }
} }
/** 移除管理员 */ /** 移除管理员 */
const handleRemoveManagerUser = (user: UserVO) => { const handleRemoveManagerUser = (user: UserVO) => {
selectedManagerUsers.value = selectedManagerUsers.value.filter((u) => u.id !== user.id) modelData.value = {
emit('update:modelValue', {
...modelData.value, ...modelData.value,
managerUserIds: modelData.value.managerUserIds.filter((id: number) => id !== user.id) managerUserIds: modelData.value.managerUserIds.filter((id: number) => id !== user.id)
}) }
} }
/** 表单校验 */ /** 表单校验 */

View File

@ -0,0 +1,17 @@
<template>
<el-form ref="formRef" :model="modelData" label-width="120px" class="mt-20px">
<el-form-item class="mb-20px">
<template #label>
<el-text size="large" tag="b">提交人权限</el-text>
</template>
<el-checkbox v-model="modelData.allowCancelRunningProcess" label="允许撤销审批中的申请" />
<div class="ml-22px">
<el-text type="info"> 第一个审批节点通过后提交人仍可撤销申请 </el-text>
</div>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
const modelData = defineModel<any>()
</script>

View File

@ -70,25 +70,16 @@ import * as FormApi from '@/api/bpm/form'
import { setConfAndFields2 } from '@/utils/formCreate' import { setConfAndFields2 } from '@/utils/formCreate'
const props = defineProps({ const props = defineProps({
modelValue: {
type: Object,
required: true
},
formList: { formList: {
type: Array, type: Array,
required: true required: true
} }
}) })
const emit = defineEmits(['update:modelValue'])
const formRef = ref() const formRef = ref()
// //
const modelData = computed({ const modelData = defineModel<any>()
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
// //
const formPreview = ref({ const formPreview = ref({

View File

@ -6,10 +6,7 @@
:model-id="modelData.id" :model-id="modelData.id"
:model-key="modelData.key" :model-key="modelData.key"
:model-name="modelData.name" :model-name="modelData.name"
:value="currentBpmnXml"
ref="bpmnEditorRef"
@success="handleDesignSuccess" @success="handleDesignSuccess"
@init-finished="handleEditorInit"
/> />
</template> </template>
@ -21,10 +18,7 @@
:model-key="modelData.key" :model-key="modelData.key"
:model-name="modelData.name" :model-name="modelData.name"
:start-user-ids="modelData.startUserIds" :start-user-ids="modelData.startUserIds"
:value="currentSimpleModel"
ref="simpleEditorRef"
@success="handleDesignSuccess" @success="handleDesignSuccess"
@init-finished="handleEditorInit"
/> />
</template> </template>
</template> </template>
@ -34,137 +28,16 @@ import { BpmModelType } from '@/utils/constants'
import BpmModelEditor from '../editor/index.vue' import BpmModelEditor from '../editor/index.vue'
import SimpleModelDesign from '../../simple/SimpleModelDesign.vue' import SimpleModelDesign from '../../simple/SimpleModelDesign.vue'
const props = defineProps({
modelValue: {
type: Object,
required: true
}
})
const emit = defineEmits(['update:modelValue', 'success'])
const bpmnEditorRef = ref()
const simpleEditorRef = ref()
const isEditorInitialized = ref(false)
// //
const modelData = computed({ const modelData = defineModel<any>()
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
// XML const processData = inject('processData') as Ref
const currentBpmnXml = ref('')
const currentSimpleModel = ref('')
// XML
const initOrUpdateXmlData = () => {
if (modelData.value) {
if (modelData.value.type === BpmModelType.BPMN) {
currentBpmnXml.value = modelData.value.bpmnXml || ''
} else {
currentSimpleModel.value = modelData.value.simpleModel || ''
}
}
}
// modelValue
watch(
() => props.modelValue,
(newVal) => {
if (newVal) {
if (newVal.type === BpmModelType.BPMN) {
if (newVal.bpmnXml && newVal.bpmnXml !== currentBpmnXml.value) {
currentBpmnXml.value = newVal.bpmnXml
//
if (isEditorInitialized.value && bpmnEditorRef.value?.refresh) {
nextTick(() => {
bpmnEditorRef.value.refresh()
})
}
}
} else {
if (newVal.simpleModel && newVal.simpleModel !== currentSimpleModel.value) {
currentSimpleModel.value = newVal.simpleModel
//
if (isEditorInitialized.value && simpleEditorRef.value?.refresh) {
nextTick(() => {
simpleEditorRef.value.refresh()
})
}
}
}
}
},
{ immediate: true, deep: true }
)
/** 编辑器初始化完成的回调 */
const handleEditorInit = async () => {
isEditorInitialized.value = true
// tick
await nextTick()
//
if (modelData.value.type === BpmModelType.BPMN) {
if (modelData.value.bpmnXml) {
currentBpmnXml.value = modelData.value.bpmnXml
if (bpmnEditorRef.value?.refresh) {
await nextTick()
bpmnEditorRef.value.refresh()
}
}
} else {
if (modelData.value.simpleModel) {
currentSimpleModel.value = modelData.value.simpleModel
if (simpleEditorRef.value?.refresh) {
await nextTick()
simpleEditorRef.value.refresh()
}
}
}
}
/** 获取当前流程数据 */
const getProcessData = async () => {
try {
if (modelData.value.type === BpmModelType.BPMN) {
if (!bpmnEditorRef.value || !isEditorInitialized.value) {
return currentBpmnXml.value || undefined
}
const { xml } = await bpmnEditorRef.value.saveXML()
if (xml) {
currentBpmnXml.value = xml
return xml
}
} else {
if (!simpleEditorRef.value || !isEditorInitialized.value) {
return currentSimpleModel.value || undefined
}
const flowData = await simpleEditorRef.value.getCurrentFlowData()
if (flowData) {
currentSimpleModel.value = flowData
return flowData
}
}
return modelData.value.type === BpmModelType.BPMN
? currentBpmnXml.value
: currentSimpleModel.value
} catch (error) {
console.error('获取流程数据失败:', error)
return modelData.value.type === BpmModelType.BPMN
? currentBpmnXml.value
: currentSimpleModel.value
}
}
/** 表单校验 */ /** 表单校验 */
const validate = async () => { const validate = async () => {
try { try {
// //
const processData = await getProcessData() if (!processData.value) {
if (!processData) {
throw new Error('请设计流程') throw new Error('请设计流程')
} }
return true return true
@ -172,27 +45,19 @@ const validate = async () => {
throw error throw error
} }
} }
/** 处理设计器保存成功 */ /** 处理设计器保存成功 */
const handleDesignSuccess = async (data?: any) => { const handleDesignSuccess = async (data?: any) => {
if (data) { if (data) {
if (modelData.value.type === BpmModelType.BPMN) {
currentBpmnXml.value = data
} else {
currentSimpleModel.value = data
}
// //
const newModelData = { const newModelData = {
...modelData.value, ...modelData.value,
bpmnXml: modelData.value.type === BpmModelType.BPMN ? data : null, bpmnXml: modelData.value.type === BpmModelType.BPMN ? data : null,
simpleModel: modelData.value.type === BpmModelType.BPMN ? null : data simpleModel: modelData.value.type === BpmModelType.BPMN ? null : data
} }
// 使emit // 使emit
await nextTick() await nextTick()
emit('update:modelValue', newModelData) //
emit('success', data) modelData.value = newModelData
} }
} }
@ -200,36 +65,7 @@ const handleDesignSuccess = async (data?: any) => {
const showDesigner = computed(() => { const showDesigner = computed(() => {
return Boolean(modelData.value?.key && modelData.value?.name) return Boolean(modelData.value?.key && modelData.value?.name)
}) })
//
onMounted(() => {
initOrUpdateXmlData()
})
//
onBeforeUnmount(async () => {
try {
//
const data = await getProcessData()
if (data) {
//
const newModelData = {
...modelData.value,
bpmnXml: modelData.value.type === BpmModelType.BPMN ? data : null,
simpleModel: modelData.value.type === BpmModelType.BPMN ? null : data
}
// 使emit
await nextTick()
emit('update:modelValue', newModelData)
}
} catch (error) {
console.error('保存数据失败:', error)
}
})
defineExpose({ defineExpose({
validate, validate
getProcessData
}) })
</script> </script>

View File

@ -67,12 +67,12 @@
</div> </div>
<!-- 第三步流程设计 --> <!-- 第三步流程设计 -->
<ProcessDesign <ProcessDesign v-if="currentStep === 2" v-model="formData" ref="processDesignRef" />
v-if="currentStep === 2"
v-model="formData" <!-- 第四步更多设置 -->
ref="processDesignRef" <div v-if="currentStep === 3" class="mx-auto w-700px">
@success="handleDesignSuccess" <ExtraSettings v-model="formData" ref="extraSettingsRef" />
/> </div>
</div> </div>
</div> </div>
</ContentWrap> </ContentWrap>
@ -83,7 +83,7 @@ import { useRoute, useRouter } from 'vue-router'
import { useMessage } from '@/hooks/web/useMessage' import { useMessage } from '@/hooks/web/useMessage'
import * as ModelApi from '@/api/bpm/model' import * as ModelApi from '@/api/bpm/model'
import * as FormApi from '@/api/bpm/form' import * as FormApi from '@/api/bpm/form'
import { CategoryApi } from '@/api/bpm/category' import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
import { useUserStoreWithOut } from '@/store/modules/user' import { useUserStoreWithOut } from '@/store/modules/user'
import { BpmModelFormType, BpmModelType } from '@/utils/constants' import { BpmModelFormType, BpmModelType } from '@/utils/constants'
@ -91,6 +91,7 @@ import BasicInfo from './BasicInfo.vue'
import FormDesign from './FormDesign.vue' import FormDesign from './FormDesign.vue'
import ProcessDesign from './ProcessDesign.vue' import ProcessDesign from './ProcessDesign.vue'
import { useTagsViewStore } from '@/store/modules/tagsView' import { useTagsViewStore } from '@/store/modules/tagsView'
import ExtraSettings from './ExtraSettings.vue'
const router = useRouter() const router = useRouter()
const { delView } = useTagsViewStore() // const { delView } = useTagsViewStore() //
@ -118,11 +119,13 @@ const validateProcess = async () => {
await processDesignRef.value?.validate() await processDesignRef.value?.validate()
} }
const currentStep = ref(0) // const currentStep = ref(-1) // -1
const steps = [ const steps = [
{ title: '基本信息', validator: validateBasic }, { title: '基本信息', validator: validateBasic },
{ title: '表单设计', validator: validateForm }, { title: '表单设计', validator: validateForm },
{ title: '流程设计', validator: validateProcess } { title: '流程设计', validator: validateProcess },
{ title: '更多设置', validator: null }
] ]
// //
@ -140,14 +143,20 @@ const formData: any = ref({
formCustomViewPath: '', formCustomViewPath: '',
visible: true, visible: true,
startUserType: undefined, startUserType: undefined,
managerUserType: undefined,
startUserIds: [], startUserIds: [],
managerUserIds: [] managerUserIds: [],
allowCancelRunningProcess: true
}) })
//
const processData = ref<any>()
provide('processData', processData)
provide('modelData', formData)
// //
const formList = ref([]) const formList = ref([])
const categoryList = ref([]) const categoryList = ref<CategoryVO[]>([])
const userList = ref<UserApi.UserVO[]>([]) const userList = ref<UserApi.UserVO[]>([])
/** 初始化数据 */ /** 初始化数据 */
@ -156,8 +165,16 @@ const initData = async () => {
if (modelId) { if (modelId) {
// //
formData.value = await ModelApi.getModel(modelId) formData.value = await ModelApi.getModel(modelId)
formData.value.startUserType = formData.value.startUserIds?.length > 0 ? 1 : 0
//
if (route.params.type === 'copy') {
delete formData.value.id
formData.value.name += '副本'
formData.value.key += '_copy'
}
} else { } else {
// //
formData.value.startUserType = 0 //
formData.value.managerUserIds.push(userStore.getUser.id) formData.value.managerUserIds.push(userStore.getUser.id)
} }
@ -167,59 +184,54 @@ const initData = async () => {
categoryList.value = await CategoryApi.getCategorySimpleList() categoryList.value = await CategoryApi.getCategorySimpleList()
// //
userList.value = await UserApi.getSimpleUserList() userList.value = await UserApi.getSimpleUserList()
// currentStep
currentStep.value = 0
} }
/** 根据类型切换流程数据 */
watch(
async () => formData.value.type,
() => {
if (formData.value.type === BpmModelType.BPMN) {
processData.value = formData.value.bpmnXml
} else if (formData.value.type === BpmModelType.SIMPLE) {
processData.value = formData.value.simpleModel
}
console.log('加载流程数据', processData.value)
},
{
immediate: true
}
)
/** 校验所有步骤数据是否完整 */ /** 校验所有步骤数据是否完整 */
const validateAllSteps = async () => { const validateAllSteps = async () => {
try { try {
// //
await basicInfoRef.value?.validate() try {
if (!formData.value.key || !formData.value.name || !formData.value.category) { await validateBasic()
} catch (error) {
currentStep.value = 0 currentStep.value = 0
throw new Error('请完善基本信息') throw new Error('请完善基本信息')
} }
// //
await formDesignRef.value?.validate() try {
if (formData.value.formType === 10 && !formData.value.formId) { await validateForm()
currentStep.value = 1 } catch (error) {
throw new Error('请选择流程表单')
}
if (
formData.value.formType === 20 &&
(!formData.value.formCustomCreatePath || !formData.value.formCustomViewPath)
) {
currentStep.value = 1 currentStep.value = 1
throw new Error('请完善自定义表单信息') throw new Error('请完善自定义表单信息')
} }
// //
//
if (!formData.value.bpmnXml && !formData.value.simpleModel) {
//
if (currentStep.value !== 2) {
await steps[currentStep.value].validator()
//
currentStep.value = 2
//
await nextTick()
}
// //
await processDesignRef.value?.validate() try {
const processData = await processDesignRef.value?.getProcessData() await validateProcess()
if (!processData) { } catch (error) {
throw new Error('请设计流程') currentStep.value = 2
} throw new Error('请设计流程')
//
if (formData.value.type === BpmModelType.BPMN) {
formData.value.bpmnXml = processData
formData.value.simpleModel = null
} else {
formData.value.bpmnXml = null
formData.value.simpleModel = processData
}
} }
return true return true
@ -239,20 +251,6 @@ const handleSave = async () => {
...formData.value ...formData.value
} }
//
if (currentStep.value === 2) {
const processData = await processDesignRef.value?.getProcessData()
if (processData) {
if (formData.value.type === BpmModelType.BPMN) {
modelData.bpmnXml = processData
modelData.simpleModel = null
} else {
modelData.bpmnXml = null
modelData.simpleModel = processData
}
}
}
if (formData.value.id) { if (formData.value.id) {
// //
await ModelApi.updateModel(modelData) await ModelApi.updateModel(modelData)
@ -308,20 +306,6 @@ const handleDeploy = async () => {
...formData.value ...formData.value
} }
//
if (currentStep.value === 2) {
const processData = await processDesignRef.value?.getProcessData()
if (processData) {
if (formData.value.type === BpmModelType.BPMN) {
modelData.bpmnXml = processData
modelData.simpleModel = null
} else {
modelData.bpmnXml = null
modelData.simpleModel = processData
}
}
}
// //
if (formData.value.id) { if (formData.value.id) {
await ModelApi.updateModel(modelData) await ModelApi.updateModel(modelData)
@ -344,60 +328,25 @@ const handleDeploy = async () => {
/** 步骤切换处理 */ /** 步骤切换处理 */
const handleStepClick = async (index: number) => { const handleStepClick = async (index: number) => {
try { try {
// keyname console.log('index', index)
if (index === 2) { if (index !== 0) {
if (!formData.value.key || !formData.value.name) { await validateBasic()
message.warning('请先填写流程标识和流程名称')
return
}
} }
if (index !== 1) {
// await validateForm()
if (currentStep.value === 2) { }
const processData = await processDesignRef.value?.getProcessData() if (index !== 2) {
if (processData) { await validateProcess()
if (formData.value.type === BpmModelType.BPMN) {
formData.value.bpmnXml = processData
formData.value.simpleModel = null
} else {
formData.value.bpmnXml = null
formData.value.simpleModel = processData
}
}
} else {
//
if (index > currentStep.value) {
if (typeof steps[currentStep.value].validator === 'function') {
await steps[currentStep.value].validator()
}
}
} }
// //
currentStep.value = index currentStep.value = index
//
if (index === 2) {
await nextTick()
//
await new Promise(resolve => setTimeout(resolve, 200))
if (processDesignRef.value?.refresh) {
await processDesignRef.value.refresh()
}
}
} catch (error) { } catch (error) {
console.error('步骤切换失败:', error) console.error('步骤切换失败:', error)
message.warning('请先完善当前步骤必填信息') message.warning('请先完善当前步骤必填信息')
} }
} }
/** 处理设计器保存成功 */
const handleDesignSuccess = (bpmnXml?: string) => {
if (bpmnXml) {
formData.value.bpmnXml = bpmnXml
}
}
/** 返回列表页 */ /** 返回列表页 */
const handleBack = () => { const handleBack = () => {
// //

View File

@ -85,8 +85,6 @@
</div> </div>
</ContentWrap> </ContentWrap>
<!-- 表单弹窗添加/修改流程 -->
<ModelForm ref="formRef" @success="getList" />
<!-- 表单弹窗添加分类 --> <!-- 表单弹窗添加分类 -->
<CategoryForm ref="categoryFormRef" @success="getList" /> <CategoryForm ref="categoryFormRef" @success="getList" />
<!-- 弹窗表单详情 --> <!-- 弹窗表单详情 -->
@ -99,7 +97,6 @@
import draggable from 'vuedraggable' import draggable from 'vuedraggable'
import { CategoryApi } from '@/api/bpm/category' import { CategoryApi } from '@/api/bpm/category'
import * as ModelApi from '@/api/bpm/model' import * as ModelApi from '@/api/bpm/model'
import ModelForm from './ModelForm.vue'
import CategoryForm from '../category/CategoryForm.vue' import CategoryForm from '../category/CategoryForm.vue'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import CategoryDraggableModel from './CategoryDraggableModel.vue' import CategoryDraggableModel from './CategoryDraggableModel.vue'
@ -123,7 +120,6 @@ const handleQuery = () => {
} }
/** 添加/修改操作 */ /** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => { const openForm = (type: string, id?: number) => {
if (type === 'create') { if (type === 'create') {
push({ name: 'BpmModelCreate' }) push({ name: 'BpmModelCreate' })
@ -206,7 +202,7 @@ const getList = async () => {
} }
/** 初始化 **/ /** 初始化 **/
onMounted(() => { onActivated(() => {
getList() getList()
}) })
</script> </script>

View File

@ -44,8 +44,26 @@
:rows="4" :rows="4"
/> />
</el-form-item> </el-form-item>
<el-form-item
v-if="runningTask.signEnable"
label="签名"
prop="signPicUrl"
ref="approveSignFormRef"
>
<el-button @click="signRef.open()"></el-button>
<el-image
class="w-90px h-40px ml-5px"
v-if="approveReasonForm.signPicUrl"
:src="approveReasonForm.signPicUrl"
:preview-src-list="[approveReasonForm.signPicUrl]"
/>
</el-form-item>
<el-form-item> <el-form-item>
<el-button :disabled="formLoading" type="success" @click="handleAudit(true, approveFormRef)"> <el-button
:disabled="formLoading"
type="success"
@click="handleAudit(true, approveFormRef)"
>
{{ getButtonDisplayName(OperationButtonType.APPROVE) }} {{ getButtonDisplayName(OperationButtonType.APPROVE) }}
</el-button> </el-button>
<el-button @click="closePropover('approve', approveFormRef)"> 取消 </el-button> <el-button @click="closePropover('approve', approveFormRef)"> 取消 </el-button>
@ -86,7 +104,11 @@
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button :disabled="formLoading" type="danger" @click="handleAudit(false,rejectFormRef)"> <el-button
:disabled="formLoading"
type="danger"
@click="handleAudit(false, rejectFormRef)"
>
{{ getButtonDisplayName(OperationButtonType.REJECT) }} {{ getButtonDisplayName(OperationButtonType.REJECT) }}
</el-button> </el-button>
<el-button @click="closePropover('reject', rejectFormRef)"> 取消 </el-button> <el-button @click="closePropover('reject', rejectFormRef)"> 取消 </el-button>
@ -471,6 +493,9 @@
<Icon :size="14" icon="ep:refresh" />&nbsp; 再次提交 <Icon :size="14" icon="ep:refresh" />&nbsp; 再次提交
</div> </div>
</div> </div>
<!-- 签名弹窗 -->
<SignDialog ref="signRef" @success="handleSignFinish" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useUserStoreWithOut } from '@/store/modules/user' import { useUserStoreWithOut } from '@/store/modules/user'
@ -479,11 +504,13 @@ import * as TaskApi from '@/api/bpm/task'
import * as ProcessInstanceApi from '@/api/bpm/processInstance' import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
import { import {
OperationButtonType, OPERATION_BUTTON_NAME,
OPERATION_BUTTON_NAME OperationButtonType
} from '@/components/SimpleProcessDesignerV2/src/consts' } from '@/components/SimpleProcessDesignerV2/src/consts'
import { BpmProcessInstanceStatus, BpmModelFormType } from '@/utils/constants' import { BpmModelFormType, BpmProcessInstanceStatus } from '@/utils/constants'
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
import SignDialog from './SignDialog.vue'
defineOptions({ name: 'ProcessInstanceBtnContainer' }) defineOptions({ name: 'ProcessInstanceBtnContainer' })
const router = useRouter() // const router = useRouter() //
@ -492,12 +519,12 @@ const message = useMessage() // 消息弹窗
const userId = useUserStoreWithOut().getUser.id // const userId = useUserStoreWithOut().getUser.id //
const emit = defineEmits(['success']) // success const emit = defineEmits(['success']) // success
const props = defineProps< { const props = defineProps<{
processInstance: any, // processInstance: any //
processDefinition: any, // processDefinition: any //
userOptions: UserApi.UserVO[], userOptions: UserApi.UserVO[]
normalForm: any, // formCreate normalForm: any // formCreate
normalFormApi: any, // formCreate Api normalFormApi: any // formCreate Api
writableFields: string[] // writableFields: string[] //
}>() }>()
@ -521,20 +548,29 @@ const approveForm = ref<any>({}) // 审批通过时,额外的补充信息
const approveFormFApi = ref<any>({}) // approveForms fAPi const approveFormFApi = ref<any>({}) // approveForms fAPi
// //
const reasonRequire = ref()
const approveFormRef = ref<FormInstance>() const approveFormRef = ref<FormInstance>()
const signRef = ref()
const approveSignFormRef = ref()
const approveReasonForm = reactive({ const approveReasonForm = reactive({
reason: '' reason: '',
signPicUrl: ''
}) })
const approveReasonRule = reactive<FormRules<typeof approveReasonForm>>({ const approveReasonRule = computed(() => {
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }], return {
reason: [{ required: reasonRequire.value, message: '审批意见不能为空', trigger: 'blur' }],
signPicUrl: [{ required: true, message: '签名不能为空', trigger: 'change' }]
}
}) })
// //
const rejectFormRef = ref<FormInstance>() const rejectFormRef = ref<FormInstance>()
const rejectReasonForm = reactive({ const rejectReasonForm = reactive({
reason: '' reason: ''
}) })
const rejectReasonRule = reactive<FormRules<typeof rejectReasonForm>>({ const rejectReasonRule = computed(() => {
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }], return {
reason: [{ required: reasonRequire.value, message: '审批意见不能为空', trigger: 'blur' }]
}
}) })
// //
@ -555,7 +591,7 @@ const transferForm = reactive({
}) })
const transferFormRule = reactive<FormRules<typeof transferForm>>({ const transferFormRule = reactive<FormRules<typeof transferForm>>({
assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }], assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }],
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }], reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }]
}) })
// //
@ -566,7 +602,7 @@ const delegateForm = reactive({
}) })
const delegateFormRule = reactive<FormRules<typeof delegateForm>>({ const delegateFormRule = reactive<FormRules<typeof delegateForm>>({
delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }], delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }],
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }], reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }]
}) })
// //
@ -577,7 +613,7 @@ const addSignForm = reactive({
}) })
const addSignFormRule = reactive<FormRules<typeof addSignForm>>({ const addSignFormRule = reactive<FormRules<typeof addSignForm>>({
addSignUserIds: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }], addSignUserIds: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }],
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }], reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }]
}) })
// //
@ -588,7 +624,7 @@ const deleteSignForm = reactive({
}) })
const deleteSignFormRule = reactive<FormRules<typeof deleteSignForm>>({ const deleteSignFormRule = reactive<FormRules<typeof deleteSignForm>>({
deleteSignTaskId: [{ required: true, message: '减签人员不能为空', trigger: 'change' }], deleteSignTaskId: [{ required: true, message: '减签人员不能为空', trigger: 'change' }],
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }], reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }]
}) })
// 退 // 退
@ -608,7 +644,7 @@ const cancelForm = reactive({
cancelReason: '' cancelReason: ''
}) })
const cancelFormRule = reactive<FormRules<typeof cancelForm>>({ const cancelFormRule = reactive<FormRules<typeof cancelForm>>({
cancelReason: [{ required: true, message: '取消理由不能为空', trigger: 'blur' }], cancelReason: [{ required: true, message: '取消理由不能为空', trigger: 'blur' }]
}) })
/** 监听 approveFormFApis实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */ /** 监听 approveFormFApis实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */
@ -627,11 +663,11 @@ watch(
const openPopover = async (type: string) => { const openPopover = async (type: string) => {
if (type === 'approve') { if (type === 'approve') {
// //
const valid = await validateNormalForm(); const valid = await validateNormalForm()
if (!valid) { if (!valid) {
message.warning('表单校验不通过,请先完善表单!!') message.warning('表单校验不通过,请先完善表单!!')
return; return
} }
} }
if (type === 'return') { if (type === 'return') {
// 退 // 退
@ -664,14 +700,18 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
if (!formRef) return if (!formRef) return
await formRef.validate() await formRef.validate()
if (pass) { if (pass) {
// , // ,
const variables = getUpdatedProcessInstanceVaiables(); const variables = getUpdatedProcessInstanceVariables()
// //
const data = { const data = {
id: runningTask.value.id, id: runningTask.value.id,
reason: approveReasonForm.reason, reason: approveReasonForm.reason,
variables // , variables // ,
} }
//
if (runningTask.value.signEnable) {
data.signPicUrl = approveReasonForm.signPicUrl
}
// approveForm + data // approveForm + data
// TODO // TODO
const formCreateApi = approveFormFApi.value const formCreateApi = approveFormFApi.value
@ -684,10 +724,10 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
popOverVisible.value.approve = false popOverVisible.value.approve = false
message.success('审批通过成功') message.success('审批通过成功')
} else { } else {
// //
const data = { const data = {
id: runningTask.value.id, id: runningTask.value.id,
reason: rejectReasonForm.reason, reason: rejectReasonForm.reason
} }
await TaskApi.rejectTask(data) await TaskApi.rejectTask(data)
popOverVisible.value.reject = false popOverVisible.value.reject = false
@ -713,7 +753,7 @@ const handleCopy = async () => {
const data = { const data = {
id: runningTask.value.id, id: runningTask.value.id,
reason: copyForm.copyReason, reason: copyForm.copyReason,
copyUserIds:copyForm.copyUserIds copyUserIds: copyForm.copyUserIds
} }
await TaskApi.copyTask(data) await TaskApi.copyTask(data)
copyFormRef.value.resetFields() copyFormRef.value.resetFields()
@ -752,7 +792,6 @@ const handleTransfer = async () => {
const handleDelegate = async () => { const handleDelegate = async () => {
formLoading.value = true formLoading.value = true
try { try {
// 1.1 // 1.1
if (!delegateFormRef.value) return if (!delegateFormRef.value) return
await delegateFormRef.value.validate() await delegateFormRef.value.validate()
@ -932,6 +971,7 @@ const loadTodoTask = (task: any) => {
approveForm.value = {} approveForm.value = {}
approveFormFApi.value = {} approveFormFApi.value = {}
runningTask.value = task runningTask.value = task
reasonRequire.value = task?.reasonRequire ?? false
// approve . // approve .
if (task && task.formId && task.formConf) { if (task && task.formId && task.formConf) {
const tempApproveForm = {} const tempApproveForm = {}
@ -949,23 +989,29 @@ const validateNormalForm = async () => {
try { try {
await props.normalFormApi?.validate() await props.normalFormApi?.validate()
} catch { } catch {
valid = false; valid = false
} }
return valid; return valid
} else { } else {
return true; return true
} }
} }
/** 从可以编辑的流程表单字段,获取需要修改的流程实例的变量 */ /** 从可以编辑的流程表单字段,获取需要修改的流程实例的变量 */
const getUpdatedProcessInstanceVaiables = ()=> { const getUpdatedProcessInstanceVariables = () => {
const variables = {} const variables = {}
props.writableFields.forEach( (field) => { props.writableFields.forEach((field) => {
const fieldValue = props.normalFormApi.getValue(field) variables[field] = props.normalFormApi.getValue(field)
variables[field] = fieldValue;
}) })
return variables return variables
} }
/** 处理签名完成 */
const handleSignFinish = (url: string) => {
approveReasonForm.signPicUrl = url
approveSignFormRef.value.validate('change')
}
defineExpose({ loadTodoTask }) defineExpose({ loadTodoTask })
</script> </script>

View File

@ -128,7 +128,7 @@ const setSimpleModelNodeTaskStatus = (
simpleModel.type === NodeType.CONDITION_BRANCH_NODE || simpleModel.type === NodeType.CONDITION_BRANCH_NODE ||
simpleModel.type === NodeType.PARALLEL_BRANCH_NODE || simpleModel.type === NodeType.PARALLEL_BRANCH_NODE ||
simpleModel.type === NodeType.INCLUSIVE_BRANCH_NODE || simpleModel.type === NodeType.INCLUSIVE_BRANCH_NODE ||
simpleModel.type === NodeType.ROUTE_BRANCH_NODE simpleModel.type === NodeType.ROUTER_BRANCH_NODE
) { ) {
// //
if (finishedActivityIds.includes(simpleModel.id)) { if (finishedActivityIds.includes(simpleModel.id)) {

View File

@ -123,6 +123,17 @@
> >
审批意见{{ task.reason }} 审批意见{{ task.reason }}
</div> </div>
<div
v-if="task.signPicUrl && activity.nodeType === NodeType.USER_TASK_NODE"
class="text-#a5a5a5 text-13px mt-1 w-full bg-#f8f8fa p2 rounded-md"
>
签名
<el-image
class="w-90px h-40px ml-5px"
:src="task.signPicUrl"
:preview-src-list="[task.signPicUrl]"
/>
</div>
</teleport> </teleport>
</div> </div>
<!-- 情况二遍历每个审批节点下的候选的task 任务例如说1依次审批2未来的审批任务等 --> <!-- 情况二遍历每个审批节点下的候选的task 任务例如说1依次审批2未来的审批任务等 -->

View File

@ -0,0 +1,50 @@
<template>
<el-dialog v-model="signDialogVisible" title="签名" width="935">
<div class="position-relative">
<Vue3Signature class="b b-solid b-gray" ref="signature" w="900px" h="400px" />
<el-button
class="pos-absolute bottom-20px right-10px"
type="primary"
text
size="small"
@click="signature.clear()"
>
<Icon icon="ep:delete" class="mr-5px" />
清除
</el-button>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="signDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submit"> </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import Vue3Signature from 'vue3-signature'
import * as FileApi from '@/api/infra/file'
import download from '@/utils/download'
const message = useMessage() //
const signDialogVisible = ref(false)
const signature = ref()
const open = async () => {
signDialogVisible.value = true
}
defineExpose({ open })
const emits = defineEmits(['success'])
const submit = async () => {
message.success('签名上传中请稍等。。。')
const res = await FileApi.updateFile({
file: download.base64ToFile(signature.value.save('image/png'), '签名')
})
emits('success', res.data)
signDialogVisible.value = false
}
</script>
<style scoped></style>

View File

@ -4,9 +4,7 @@
:model-id="modelId" :model-id="modelId"
:model-key="modelKey" :model-key="modelKey"
:model-name="modelName" :model-name="modelName"
:value="currentValue"
@success="handleSuccess" @success="handleSuccess"
@init-finished="handleInit"
:start-user-ids="startUserIds" :start-user-ids="startUserIds"
ref="designerRef" ref="designerRef"
/> />
@ -19,136 +17,22 @@ defineOptions({
name: 'SimpleModelDesign' name: 'SimpleModelDesign'
}) })
const props = defineProps<{ defineProps<{
modelId?: string modelId?: string
modelKey?: string modelKey?: string
modelName?: string modelName?: string
value?: any
startUserIds?: number[] startUserIds?: number[]
}>() }>()
const emit = defineEmits(['success', 'init-finished']) const emit = defineEmits(['success'])
const designerRef = ref() const designerRef = ref()
const isInitialized = ref(false)
const currentValue = ref('')
//
const initOrUpdateValue = async () => {
if (props.value) {
currentValue.value = props.value
//
if (isInitialized.value && designerRef.value) {
try {
await designerRef.value.loadProcessData(props.value)
await nextTick()
if (designerRef.value.refresh) {
await designerRef.value.refresh()
}
} catch (error) {
console.error('加载流程数据失败:', error)
}
}
}
}
//
watch(
[() => props.modelKey, () => props.modelName, () => props.value],
async ([newKey, newName, newValue], [oldKey, oldName, oldValue]) => {
if (designerRef.value && isInitialized.value) {
try {
if (newKey && newName && (newKey !== oldKey || newName !== oldName)) {
await designerRef.value.updateModel(newKey, newName)
}
if (newValue && newValue !== oldValue) {
currentValue.value = newValue
await designerRef.value.loadProcessData(newValue)
await nextTick()
if (designerRef.value.refresh) {
await designerRef.value.refresh()
}
}
} catch (error) {
console.error('更新流程数据失败:', error)
}
}
},
{ deep: true, immediate: true }
)
//
const handleInit = async () => {
try {
isInitialized.value = true
emit('init-finished')
// tick
await nextTick()
//
if (props.modelKey && props.modelName) {
await designerRef.value.updateModel(props.modelKey, props.modelName)
}
if (props.value) {
currentValue.value = props.value
await designerRef.value.loadProcessData(props.value)
//
await nextTick()
if (designerRef.value.refresh) {
await designerRef.value.refresh()
}
}
} catch (error) {
console.error('初始化流程数据失败:', error)
}
}
// //
const handleSuccess = (data?: any) => { const handleSuccess = (data?: any) => {
console.warn('handleSuccess', data) console.info('handleSuccess', data)
if (data && data !== currentValue.value) { if (data) {
currentValue.value = data
emit('success', data) emit('success', data)
} }
} }
/** 获取当前流程数据 */
const getCurrentFlowData = async () => {
try {
if (designerRef.value) {
const data = await designerRef.value.getCurrentFlowData()
if (data) {
currentValue.value = data
}
return data
}
return currentValue.value || undefined
} catch (error) {
console.error('获取流程数据失败:', error)
return currentValue.value || undefined
}
}
//
onMounted(() => {
initOrUpdateValue()
})
//
onBeforeUnmount(async () => {
try {
const data = await getCurrentFlowData()
if (data) {
emit('success', data)
}
} catch (error) {
console.error('保存数据失败:', error)
}
})
defineExpose({
getCurrentFlowData,
refresh: () => designerRef.value?.refresh?.()
})
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@ -135,7 +135,8 @@ const makeTemplate = () => {
/** 复制 **/ /** 复制 **/
const copy = async (text: string) => { const copy = async (text: string) => {
const { copy, copied, isSupported } = useClipboard({ source: text }) const textToCopy = JSON.stringify(text, null, 2)
const { copy, copied, isSupported } = useClipboard({ source: textToCopy })
if (!isSupported) { if (!isSupported) {
message.error(t('common.copyError')) message.error(t('common.copyError'))
} else { } else {
@ -149,17 +150,18 @@ const copy = async (text: string) => {
/** /**
* 代码高亮 * 代码高亮
*/ */
const highlightedCode = (code) => { const highlightedCode = (code: string) => {
// //
let language = 'json' let language = 'json'
if (formType.value === 2) { if (formType.value === 2) {
language = 'xml' language = 'xml'
} }
// debugger
if (!isString(code)) { if (!isString(code)) {
code = JSON.stringify(code) code = JSON.stringify(code, null, 2)
} }
// //
const result = hljs.highlight(language, code, true) const result = hljs.highlight(code, { language: language, ignoreIllegals: true })
return result.value || '&nbsp;' return result.value || '&nbsp;'
} }

View File

@ -95,6 +95,9 @@
/> />
<el-table-column label="操作" align="center"> <el-table-column label="操作" align="center">
<template #default="scope"> <template #default="scope">
<el-button link type="primary" @click="copyToClipboard(scope.row.url)">
复制链接
</el-button>
<el-button <el-button
link link
type="danger" type="danger"
@ -172,6 +175,13 @@ const openForm = () => {
formRef.value.open() formRef.value.open()
} }
/** 复制到剪贴板方法 */
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text).then(() => {
message.success('复制成功')
})
}
/** 删除按钮操作 */ /** 删除按钮操作 */
const handleDelete = async (id: number) => { const handleDelete = async (id: number) => {
try { try {

View File

@ -16,6 +16,7 @@
<template #default="{ height, width }"> <template #default="{ height, width }">
<!-- Virtualized Table 虚拟化表格高性能解决表格在大数据量下的卡顿问题 --> <!-- Virtualized Table 虚拟化表格高性能解决表格在大数据量下的卡顿问题 -->
<el-table-v2 <el-table-v2
v-loading="loading"
:columns="columns" :columns="columns"
:data="list" :data="list"
:width="width" :width="width"
@ -31,7 +32,7 @@
<AreaForm ref="formRef" /> <AreaForm ref="formRef" />
</template> </template>
<script setup lang="tsx"> <script setup lang="tsx">
import type { Column } from 'element-plus' import { Column } from 'element-plus'
import AreaForm from './AreaForm.vue' import AreaForm from './AreaForm.vue'
import * as AreaApi from '@/api/system/area' import * as AreaApi from '@/api/system/area'
@ -40,7 +41,7 @@ defineOptions({ name: 'SystemArea' })
// column // column
const columns: Column[] = [ const columns: Column[] = [
{ {
dataKey: 'id', // {id:9527, name:'Mike'} id dataKey: 'id', //
title: '编号', // title: '编号', //
width: 400, // width: 400, //
fixed: true, // fixed: true, //
@ -52,14 +53,17 @@ const columns: Column[] = [
width: 200 width: 200
} }
] ]
// const loading = ref(true) //
const list = ref([]) const list = ref([]) //
/** /** 获得数据列表 */
* 获得数据列表
*/
const getList = async () => { const getList = async () => {
list.value = await AreaApi.getAreaTree() loading.value = true
try {
list.value = await AreaApi.getAreaTree()
} finally {
loading.value = false
}
} }
/** 添加/修改操作 */ /** 添加/修改操作 */

View File

@ -53,10 +53,6 @@
<Icon class="mr-5px" icon="ep:plus" /> <Icon class="mr-5px" icon="ep:plus" />
新增 新增
</el-button> </el-button>
<el-button plain type="danger" @click="toggleExpandAll">
<Icon class="mr-5px" icon="ep:sort" />
展开/折叠
</el-button>
<el-button plain @click="refreshMenu"> <el-button plain @click="refreshMenu">
<Icon class="mr-5px" icon="ep:refresh" /> <Icon class="mr-5px" icon="ep:refresh" />
刷新菜单缓存 刷新菜单缓存
@ -67,65 +63,22 @@
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<el-table <div style="height: 700px">
v-if="refreshTable" <!-- AutoResizer 自动调节大小 -->
v-loading="loading" <el-auto-resizer>
:data="list" <template #default="{ height, width }">
:default-expand-all="isExpandAll" <!-- Virtualized Table 虚拟化表格高性能解决表格在大数据量下的卡顿问题 -->
row-key="id" <el-table-v2
> v-loading="loading"
<el-table-column :show-overflow-tooltip="true" label="菜单名称" prop="name" width="250" /> :columns="columns"
<el-table-column align="center" label="图标" prop="icon" width="100"> :data="list"
<template #default="scope"> :width="width"
<Icon :icon="scope.row.icon" /> :height="height"
</template> expand-column-key="name"
</el-table-column>
<el-table-column label="排序" prop="sort" width="60" />
<el-table-column :show-overflow-tooltip="true" label="权限标识" prop="permission" />
<el-table-column :show-overflow-tooltip="true" label="组件路径" prop="component" />
<el-table-column :show-overflow-tooltip="true" label="组件名称" prop="componentName" />
<el-table-column label="状态" prop="status">
<template #default="scope">
<el-switch
class="ml-4px"
v-model="scope.row.status"
v-hasPermi="['system:menu:update']"
:active-value="CommonStatusEnum.ENABLE"
:inactive-value="CommonStatusEnum.DISABLE"
:loading="menuStatusUpdating[scope.row.id]"
@change="(val) => handleStatusChanged(scope.row, val as number)"
/> />
</template> </template>
</el-table-column> </el-auto-resizer>
<el-table-column align="center" label="操作"> </div>
<template #default="scope">
<el-button
v-hasPermi="['system:menu:update']"
link
type="primary"
@click="openForm('update', scope.row.id)"
>
修改
</el-button>
<el-button
v-hasPermi="['system:menu:create']"
link
type="primary"
@click="openForm('create', undefined, scope.row.id)"
>
新增
</el-button>
<el-button
v-hasPermi="['system:menu:delete']"
link
type="danger"
@click="handleDelete(scope.row.id)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</ContentWrap> </ContentWrap>
<!-- 表单弹窗添加/修改 --> <!-- 表单弹窗添加/修改 -->
@ -138,6 +91,10 @@ import * as MenuApi from '@/api/system/menu'
import { MenuVO } from '@/api/system/menu' import { MenuVO } from '@/api/system/menu'
import MenuForm from './MenuForm.vue' import MenuForm from './MenuForm.vue'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache' import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
import { h } from 'vue'
import { Column, ElButton } from 'element-plus'
import { Icon } from '@/components/Icon'
import { hasPermission } from '@/directives/permission/hasPermi'
import { CommonStatusEnum } from '@/utils/constants' import { CommonStatusEnum } from '@/utils/constants'
defineOptions({ name: 'SystemMenu' }) defineOptions({ name: 'SystemMenu' })
@ -146,6 +103,101 @@ const { wsCache } = useCache()
const { t } = useI18n() // const { t } = useI18n() //
const message = useMessage() // const message = useMessage() //
// column
const columns: Column[] = [
{
dataKey: 'name',
title: '菜单名称',
width: 250
},
{
dataKey: 'icon',
title: '图标',
width: 150,
cellRenderer: ({ rowData }) => {
return h(Icon, {
icon: rowData.icon
})
}
},
{
dataKey: 'sort',
title: '排序',
width: 100
},
{
dataKey: 'permission',
title: '权限标识',
width: 240
},
{
dataKey: 'component',
title: '组件路径',
width: 240
},
{
dataKey: 'componentName',
title: '组件名称',
width: 240
},
{
dataKey: 'status',
title: '状态',
width: 160,
cellRenderer: ({ rowData }) => {
return h(ElSwitch, {
modelValue: rowData.status,
activeValue: CommonStatusEnum.ENABLE,
inactiveValue: CommonStatusEnum.DISABLE,
loading: menuStatusUpdating.value[rowData.id],
disabled: !hasPermission(['system:menu:update']),
onChange: (val) => handleStatusChanged(rowData, val as number)
})
}
},
{
dataKey: 'operation',
title: '操作',
width: 200,
cellRenderer: ({ rowData }) => {
return h(
'div',
[
hasPermission(['system:menu:update']) &&
h(
ElButton,
{
link: true,
type: 'primary',
onClick: () => openForm('update', rowData.id)
},
'修改'
),
hasPermission(['system:menu:create']) &&
h(
ElButton,
{
link: true,
type: 'primary',
onClick: () => openForm('create', undefined, rowData.id)
},
'新增'
),
hasPermission(['system:menu:delete']) &&
h(
ElButton,
{
link: true,
type: 'danger',
onClick: () => handleDelete(rowData.id)
},
'删除'
)
].filter(Boolean)
)
}
}
]
const loading = ref(true) // const loading = ref(true) //
const list = ref<any>([]) // const list = ref<any>([]) //
const queryParams = reactive({ const queryParams = reactive({
@ -153,8 +205,6 @@ const queryParams = reactive({
status: undefined status: undefined
}) })
const queryFormRef = ref() // const queryFormRef = ref() //
const isExpandAll = ref(false) //
const refreshTable = ref(true) //
/** 查询列表 */ /** 查询列表 */
const getList = async () => { const getList = async () => {
@ -184,15 +234,6 @@ const openForm = (type: string, id?: number, parentId?: number) => {
formRef.value.open(type, id, parentId) formRef.value.open(type, id, parentId)
} }
/** 展开/折叠操作 */
const toggleExpandAll = () => {
refreshTable.value = false
isExpandAll.value = !isExpandAll.value
nextTick(() => {
refreshTable.value = true
})
}
/** 刷新菜单缓存按钮操作 */ /** 刷新菜单缓存按钮操作 */
const refreshMenu = async () => { const refreshMenu = async () => {
try { try {