Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cafde3877 | ||
|
|
10a698b9ad | ||
|
|
bffa42aec6 | ||
|
|
a39698d6ab | ||
|
|
c6f70cce00 | ||
|
|
f4a83c091d | ||
|
|
b0d4e39e68 | ||
|
|
d40f1c3150 | ||
|
|
60af2c6e7e | ||
|
|
e0880db65c | ||
|
|
e32fc81df6 | ||
|
|
59842b09c9 | ||
|
|
9c2619851a | ||
|
|
25f5b104ce | ||
|
|
5d2605fcae | ||
|
|
5e801a8e63 | ||
|
|
62ad543163 | ||
|
|
503a35f44e | ||
|
|
bd48210d7f | ||
|
|
79a53412d6 | ||
|
|
f35f62d2f8 | ||
|
|
475385bd43 | ||
|
|
cea84173da | ||
|
|
00dfe00d46 | ||
|
|
5f5ae354d2 | ||
|
|
410e257a47 | ||
|
|
e726c1dcba | ||
|
|
107b1d3778 | ||
|
|
473d681c11 | ||
|
|
52c393a04b | ||
|
|
e9a4efbf6a | ||
|
|
092a8acde4 | ||
|
|
a88c86d44c | ||
|
|
d2954776a5 | ||
|
|
7db5ac81ae | ||
|
|
e3db7d3014 | ||
|
|
b7329b0c44 | ||
|
|
a61a405346 | ||
|
|
6c504adcbc | ||
|
|
8e8475ffdc | ||
|
|
ab83cee50f | ||
|
|
99a44e8c01 | ||
|
|
0bbf4c9143 | ||
|
|
9e26e902a7 | ||
|
|
8ff63622ec | ||
|
|
4319d86978 | ||
|
|
7f50584a02 | ||
|
|
7043dea354 | ||
|
|
1cb06911c7 | ||
|
|
3ae486c39f | ||
|
|
c514c799ff | ||
|
|
58455cd0a4 | ||
|
|
bf437421bb | ||
|
|
09e406618c | ||
|
|
5832fd687f | ||
|
|
a96873ef5d | ||
|
|
951ce1cf50 | ||
|
|
9495c2c63a | ||
|
|
c435e2c9e8 | ||
|
|
b60693036d | ||
|
|
c50f59d181 | ||
|
|
39106843f3 | ||
|
|
15b0414604 | ||
|
|
1b8430e2fc | ||
|
|
051fc134cb | ||
|
|
c9c03bed13 | ||
|
|
d710c9d27e | ||
|
|
0215e8d4a8 | ||
|
|
0f58e968b1 | ||
|
|
3dbe74c1e9 | ||
|
|
187fa08524 | ||
|
|
0677173856 | ||
|
|
e6765b7e61 | ||
|
|
3d591912c2 | ||
|
|
5efdd93789 | ||
|
|
22eb2ccdbf | ||
|
|
c3fb2a3ca1 | ||
|
|
b047fd9929 | ||
|
|
aeb096a311 | ||
|
|
42de3d9127 | ||
|
|
2659165220 | ||
|
|
fef86bcc56 | ||
|
|
70e6b54642 | ||
|
|
d4432acec3 | ||
|
|
8330f61eef | ||
|
|
9694ad0fa3 | ||
|
|
5551c594ab | ||
|
|
391a1481c6 | ||
|
|
82ee62cb14 | ||
|
|
506d87ec71 | ||
|
|
ced9ad6bba | ||
|
|
34081915d4 | ||
|
|
a2ef21b27f | ||
|
|
fea238ef15 | ||
|
|
7349a0d080 | ||
|
|
39da8118b8 | ||
|
|
625426566c | ||
|
|
ae0cc94da0 | ||
|
|
99790bae63 | ||
|
|
6b1c0e7369 | ||
|
|
cde758d324 | ||
|
|
43ebd50a42 | ||
|
|
f484f95de3 | ||
|
|
b82959118e | ||
|
|
121a6d85ff | ||
|
|
2486f0d3e0 | ||
|
|
c8447fe4f7 | ||
|
|
e8e357b8a2 | ||
|
|
5c0ee866a0 | ||
|
|
1af93e502d | ||
|
|
4e0e8dd220 | ||
|
|
9308eceb81 | ||
|
|
38578c3e50 | ||
|
|
69ccd83af3 | ||
|
|
37964e740f | ||
|
|
8e5271a6d6 | ||
|
|
d52e779131 | ||
|
|
66d8cf1233 | ||
|
|
84552a6253 | ||
|
|
9febee7abe | ||
|
|
e44f48c0c4 | ||
|
|
2f7200abdb | ||
|
|
9849a040c0 | ||
|
|
753e44ccd0 | ||
|
|
e4fab335fd | ||
|
|
c391ba7689 | ||
|
|
2b38c02165 | ||
|
|
d5d667353a | ||
|
|
026b2f2ba5 | ||
|
|
ece0ccd6df | ||
|
|
395c5862fe | ||
|
|
c62833fcf6 | ||
|
|
954f2b6353 | ||
|
|
5676ac125d | ||
|
|
7e715dd03b | ||
|
|
6fba4b7228 | ||
|
|
d32a9a37bf | ||
|
|
45cf482c74 | ||
|
|
f1ac8ba622 | ||
|
|
89c3af5207 | ||
|
|
b117431318 | ||
|
|
8df285aefb | ||
|
|
e76eda8af3 | ||
|
|
86bb36fc2f | ||
|
|
0fa31be851 | ||
|
|
aba4705b8e | ||
|
|
4fe158b2ec | ||
|
|
99fe894eda | ||
|
|
42c1618202 | ||
|
|
3d8f1290c3 | ||
|
|
baa4116697 | ||
|
|
b3a97b0889 | ||
|
|
d896996f27 | ||
|
|
dc5aa6d8df | ||
|
|
401481662f | ||
|
|
04f4f630ad | ||
|
|
dd72d35fe4 | ||
|
|
96d1fcedde | ||
|
|
a1933a781e | ||
|
|
777564673e | ||
|
|
077dfdcddd | ||
|
|
4741f3737f | ||
|
|
77938c38ca | ||
|
|
d11a82a521 | ||
|
|
0c433552f9 | ||
|
|
5fa2a78e84 | ||
|
|
35a0e69cd4 | ||
|
|
2144034e1f | ||
|
|
53e6d6aa61 | ||
|
|
7303bcd91a | ||
|
|
5e31eb8ebc | ||
|
|
90fa5ffd81 | ||
|
|
099b81cb2d | ||
|
|
e5f6020582 | ||
|
|
a2759680f8 | ||
|
|
72dcb0c1f7 | ||
|
|
528ebf3ea3 | ||
|
|
3f9408bfbe | ||
|
|
65eee157b9 | ||
|
|
7683aa2e7d | ||
|
|
0da3a9bf7b | ||
|
|
eca1c83c56 | ||
|
|
42a3d2efab | ||
|
|
0168f9ff9d | ||
|
|
1ebd37a0e9 | ||
|
|
739a1c29a2 | ||
|
|
0a15eca573 | ||
|
|
3118d2abd9 | ||
|
|
e9950929de | ||
|
|
63c9b8d163 | ||
|
|
be60018908 | ||
|
|
d7e6523dad | ||
|
|
49fc9b5412 | ||
|
|
5198b2ee01 | ||
|
|
4aa44abf25 | ||
|
|
2a9290ce86 | ||
|
|
65924bb921 | ||
|
|
791d59020a | ||
|
|
09da6579b6 | ||
|
|
ce60f630c4 | ||
|
|
a9b5a7f006 | ||
|
|
ea133da1d8 | ||
|
|
101ed368c3 | ||
|
|
48e7e7e5c6 | ||
|
|
452d6024b0 | ||
|
|
7b7ce96c66 | ||
|
|
e508da7ff5 | ||
|
|
6b6d2ff806 | ||
|
|
5fce1afe29 | ||
|
|
69f76705fb | ||
|
|
166e95298b | ||
|
|
39486d6696 | ||
|
|
98774ecc6d | ||
|
|
8bbc5e22d1 | ||
|
|
c3dd741d97 | ||
|
|
f470e0d65b | ||
|
|
700efd7ee2 | ||
|
|
739e43a31e | ||
|
|
7faea8c341 | ||
|
|
3607b1d468 | ||
|
|
2436075337 | ||
|
|
b9326463c2 | ||
|
|
74b62bf05e | ||
|
|
d815c60340 | ||
|
|
02fa679d62 |
No files matched your search
+4
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yudao-ui-admin-vue3",
|
"name": "yudao-ui-admin-vue3",
|
||||||
"version": "2.4.0-snapshot",
|
"version": "2.4.1-snapshot",
|
||||||
"description": "基于vue3、vite4、element-plus、typesScript",
|
"description": "基于vue3、vite4、element-plus、typesScript",
|
||||||
"author": "xingyu",
|
"author": "xingyu",
|
||||||
"private": false,
|
"private": false,
|
||||||
@@ -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",
|
||||||
@@ -73,6 +73,7 @@
|
|||||||
"vue-i18n": "9.10.2",
|
"vue-i18n": "9.10.2",
|
||||||
"vue-router": "4.4.5",
|
"vue-router": "4.4.5",
|
||||||
"vue-types": "^5.1.1",
|
"vue-types": "^5.1.1",
|
||||||
|
"vue3-signature": "^0.2.4",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
"web-storage-cache": "^1.1.1",
|
"web-storage-cache": "^1.1.1",
|
||||||
"xml-js": "^1.6.11"
|
"xml-js": "^1.6.11"
|
||||||
|
|||||||
Generated
+39
-74
@@ -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:
|
||||||
|
|||||||
@@ -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 })
|
||||||
|
}
|
||||||
@@ -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 })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取表单字段权限
|
// 获取表单字段权限
|
||||||
|
|||||||
@@ -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/reset-password', data })
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1735905505218" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4277" width="200" height="200" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M561.778 454.929h198.117c0.549 0 0.994 0.444 0.994 1.001v97.553a0.998 0.998 0 0 1-0.994 1.001H463.224a1.005 1.005 0 0 1-1.002-1V207.04c0-0.552 0.444-1 1.002-1h97.552c0.553 0 1.002 0.455 1.002 1v247.89zM512 952.706c-247.424 0-448-200.576-448-448 0-247.423 200.576-448 448-448s448 200.577 448 448c0 247.424-200.576 448-448 448z m0-99.555c192.44 0 348.444-156.004 348.444-348.445 0-192.44-156.003-348.444-348.444-348.444-192.44 0-348.444 156.004-348.444 348.444 0 192.441 156.003 348.445 348.444 348.445z" fill="#3296FA" p-id="4278"></path></svg>
|
||||||
|
After Width: | Height: | Size: 876 B |
@@ -51,7 +51,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ComponentStyle, usePropertyForm } from '@/components/DiyEditor/util'
|
import { ComponentStyle } from '@/components/DiyEditor/util'
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 组件容器属性:目前右边部分
|
* 组件容器属性:目前右边部分
|
||||||
@@ -61,7 +62,7 @@ defineOptions({ name: 'ComponentContainer' })
|
|||||||
|
|
||||||
const props = defineProps<{ modelValue: ComponentStyle }>()
|
const props = defineProps<{ modelValue: ComponentStyle }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
|
|
||||||
const treeData = [
|
const treeData = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -93,14 +93,14 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CarouselProperty } from './config'
|
import { CarouselProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
|
|
||||||
// 轮播图属性面板
|
// 轮播图属性面板
|
||||||
defineOptions({ name: 'CarouselProperty' })
|
defineOptions({ name: 'CarouselProperty' })
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: CarouselProperty }>()
|
const props = defineProps<{ modelValue: CarouselProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
@@ -68,15 +68,20 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</ComponentContainerProperty>
|
</ComponentContainerProperty>
|
||||||
<!-- 优惠券选择 -->
|
<!-- 优惠券选择 -->
|
||||||
<CouponSelect ref="couponSelectDialog" v-model:multiple-selection="couponList" />
|
<CouponSelect
|
||||||
|
ref="couponSelectDialog"
|
||||||
|
v-model:multiple-selection="couponList"
|
||||||
|
:take-type="CouponTemplateTakeTypeEnum.USER.type"
|
||||||
|
@change="handleCouponSelect"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CouponCardProperty } from './config'
|
import { CouponCardProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
import * as CouponTemplateApi from '@/api/mall/promotion/coupon/couponTemplate'
|
import * as CouponTemplateApi from '@/api/mall/promotion/coupon/couponTemplate'
|
||||||
import { floatToFixed2 } from '@/utils'
|
import { floatToFixed2 } from '@/utils'
|
||||||
import { PromotionDiscountTypeEnum } from '@/utils/constants'
|
import { CouponTemplateTakeTypeEnum, PromotionDiscountTypeEnum } from '@/utils/constants'
|
||||||
import CouponSelect from '@/views/mall/promotion/coupon/components/CouponSelect.vue'
|
import CouponSelect from '@/views/mall/promotion/coupon/components/CouponSelect.vue'
|
||||||
|
|
||||||
// 优惠券卡片属性面板
|
// 优惠券卡片属性面板
|
||||||
@@ -84,7 +89,7 @@ defineOptions({ name: 'CouponCardProperty' })
|
|||||||
|
|
||||||
const props = defineProps<{ modelValue: CouponCardProperty }>()
|
const props = defineProps<{ modelValue: CouponCardProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
|
|
||||||
// 优惠券列表
|
// 优惠券列表
|
||||||
const couponList = ref<CouponTemplateApi.CouponTemplateVO[]>([])
|
const couponList = ref<CouponTemplateApi.CouponTemplateVO[]>([])
|
||||||
@@ -93,10 +98,20 @@ const couponSelectDialog = ref()
|
|||||||
const handleAddCoupon = () => {
|
const handleAddCoupon = () => {
|
||||||
couponSelectDialog.value.open()
|
couponSelectDialog.value.open()
|
||||||
}
|
}
|
||||||
|
const handleCouponSelect = () => {
|
||||||
|
formData.value.couponIds = couponList.value.map((coupon) => coupon.id)
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => couponList.value,
|
() => formData.value.couponIds,
|
||||||
() => {
|
async () => {
|
||||||
formData.value.couponIds = couponList.value.map((coupon) => coupon.id)
|
if (formData.value.couponIds?.length > 0) {
|
||||||
|
couponList.value = await CouponTemplateApi.getCouponTemplateList(formData.value.couponIds)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
deep: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -45,12 +45,12 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DividerProperty } from './config'
|
import { DividerProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
// 导航栏属性面板
|
// 导航栏属性面板
|
||||||
defineOptions({ name: 'DividerProperty' })
|
defineOptions({ name: 'DividerProperty' })
|
||||||
const props = defineProps<{ modelValue: DividerProperty }>()
|
const props = defineProps<{ modelValue: DividerProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
|
|
||||||
//线类型
|
//线类型
|
||||||
const BORDER_TYPES = [
|
const BORDER_TYPES = [
|
||||||
|
|||||||
@@ -31,14 +31,14 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { FloatingActionButtonProperty } from './config'
|
import { FloatingActionButtonProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
|
|
||||||
// 悬浮按钮属性面板
|
// 悬浮按钮属性面板
|
||||||
defineOptions({ name: 'FloatingActionButtonProperty' })
|
defineOptions({ name: 'FloatingActionButtonProperty' })
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: FloatingActionButtonProperty }>()
|
const props = defineProps<{ modelValue: FloatingActionButtonProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
import { HotZoneProperty } from '@/components/DiyEditor/components/mobile/HotZone/config'
|
import { HotZoneProperty } from '@/components/DiyEditor/components/mobile/HotZone/config'
|
||||||
import HotZoneEditDialog from './components/HotZoneEditDialog/index.vue'
|
import HotZoneEditDialog from './components/HotZoneEditDialog/index.vue'
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ defineOptions({ name: 'HotZoneProperty' })
|
|||||||
|
|
||||||
const props = defineProps<{ modelValue: HotZoneProperty }>()
|
const props = defineProps<{ modelValue: HotZoneProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
|
|
||||||
// 热区编辑对话框
|
// 热区编辑对话框
|
||||||
const editDialogRef = ref()
|
const editDialogRef = ref()
|
||||||
|
|||||||
@@ -21,14 +21,14 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ImageBarProperty } from './config'
|
import { ImageBarProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
|
|
||||||
// 图片展示属性面板
|
// 图片展示属性面板
|
||||||
defineOptions({ name: 'ImageBarProperty' })
|
defineOptions({ name: 'ImageBarProperty' })
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: ImageBarProperty }>()
|
const props = defineProps<{ modelValue: ImageBarProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
import { MagicCubeProperty } from '@/components/DiyEditor/components/mobile/MagicCube/config'
|
import { MagicCubeProperty } from '@/components/DiyEditor/components/mobile/MagicCube/config'
|
||||||
|
|
||||||
/** 广告魔方属性面板 */
|
/** 广告魔方属性面板 */
|
||||||
@@ -64,7 +64,7 @@ defineOptions({ name: 'MagicCubeProperty' })
|
|||||||
|
|
||||||
const props = defineProps<{ modelValue: MagicCubeProperty }>()
|
const props = defineProps<{ modelValue: MagicCubeProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
|
|
||||||
// 选中的热区
|
// 选中的热区
|
||||||
const selectedHotAreaIndex = ref(-1)
|
const selectedHotAreaIndex = ref(-1)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
import {
|
import {
|
||||||
EMPTY_MENU_GRID_ITEM_PROPERTY,
|
EMPTY_MENU_GRID_ITEM_PROPERTY,
|
||||||
MenuGridProperty
|
MenuGridProperty
|
||||||
@@ -59,7 +59,7 @@ defineOptions({ name: 'MenuGridProperty' })
|
|||||||
|
|
||||||
const props = defineProps<{ modelValue: MenuGridProperty }>()
|
const props = defineProps<{ modelValue: MenuGridProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
import {
|
import {
|
||||||
EMPTY_MENU_LIST_ITEM_PROPERTY,
|
EMPTY_MENU_LIST_ITEM_PROPERTY,
|
||||||
MenuListProperty
|
MenuListProperty
|
||||||
@@ -39,7 +39,7 @@ defineOptions({ name: 'MenuListProperty' })
|
|||||||
|
|
||||||
const props = defineProps<{ modelValue: MenuListProperty }>()
|
const props = defineProps<{ modelValue: MenuListProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
import {
|
import {
|
||||||
EMPTY_MENU_SWIPER_ITEM_PROPERTY,
|
EMPTY_MENU_SWIPER_ITEM_PROPERTY,
|
||||||
MenuSwiperProperty
|
MenuSwiperProperty
|
||||||
@@ -70,7 +70,7 @@ defineOptions({ name: 'MenuSwiperProperty' })
|
|||||||
|
|
||||||
const props = defineProps<{ modelValue: MenuSwiperProperty }>()
|
const props = defineProps<{ modelValue: MenuSwiperProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
+12
-7
@@ -64,17 +64,22 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { NavigationBarCellProperty } from '../config'
|
import { NavigationBarCellProperty } from '../config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
// 导航栏属性面板
|
// 导航栏属性面板
|
||||||
defineOptions({ name: 'NavigationBarCellProperty' })
|
defineOptions({ name: 'NavigationBarCellProperty' })
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(
|
||||||
modelValue: NavigationBarCellProperty[]
|
defineProps<{
|
||||||
isMp: boolean
|
modelValue: NavigationBarCellProperty[]
|
||||||
}>()
|
isMp: boolean
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
modelValue: () => [],
|
||||||
|
isMp: true
|
||||||
|
}
|
||||||
|
)
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData: cellList } = usePropertyForm(props.modelValue, emit)
|
const cellList = useVModel(props, 'modelValue', emit)
|
||||||
if (!cellList.value) cellList.value = []
|
|
||||||
|
|
||||||
// 单元格数量:小程序6个(右侧胶囊按钮占了2个),其它平台8个
|
// 单元格数量:小程序6个(右侧胶囊按钮占了2个),其它平台8个
|
||||||
const cellCount = computed(() => (props.isMp ? 6 : 8))
|
const cellCount = computed(() => (props.isMp ? 6 : 8))
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div v-for="(cell, cellIndex) in cellList" :key="cellIndex" :style="getCellStyle(cell)">
|
<div v-for="(cell, cellIndex) in cellList" :key="cellIndex" :style="getCellStyle(cell)">
|
||||||
<span v-if="cell.type === 'text'">{{ cell.text }}</span>
|
<span v-if="cell.type === 'text'">{{ cell.text }}</span>
|
||||||
<img v-else-if="cell.type === 'image'" :src="cell.imgUrl" alt="" class="h-full w-full" />
|
<img v-else-if="cell.type === 'image'" :src="cell.imgUrl" alt="" class="h-full w-full" />
|
||||||
<SearchBar v-else :property="getSearchProp" />
|
<SearchBar v-else :property="getSearchProp(cell)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<img
|
<img
|
||||||
@@ -51,14 +51,14 @@ const getCellStyle = (cell: NavigationBarCellProperty) => {
|
|||||||
} as StyleValue
|
} as StyleValue
|
||||||
}
|
}
|
||||||
// 获得搜索框属性
|
// 获得搜索框属性
|
||||||
const getSearchProp = (cell: NavigationBarCellProperty) => {
|
const getSearchProp = computed(() => (cell: NavigationBarCellProperty) => {
|
||||||
return {
|
return {
|
||||||
height: 30,
|
height: 30,
|
||||||
showScan: false,
|
showScan: false,
|
||||||
placeholder: cell.placeholder,
|
placeholder: cell.placeholder,
|
||||||
borderRadius: cell.borderRadius
|
borderRadius: cell.borderRadius
|
||||||
} as SearchProperty
|
} as SearchProperty
|
||||||
}
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.navigation-bar {
|
.navigation-bar {
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NavigationBarProperty } from './config'
|
import { NavigationBarProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
import NavigationBarCellProperty from '@/components/DiyEditor/components/mobile/NavigationBar/components/CellProperty.vue'
|
import NavigationBarCellProperty from '@/components/DiyEditor/components/mobile/NavigationBar/components/CellProperty.vue'
|
||||||
// 导航栏属性面板
|
// 导航栏属性面板
|
||||||
defineOptions({ name: 'NavigationBarProperty' })
|
defineOptions({ name: 'NavigationBarProperty' })
|
||||||
@@ -77,7 +77,7 @@ const rules = {
|
|||||||
|
|
||||||
const props = defineProps<{ modelValue: NavigationBarProperty }>()
|
const props = defineProps<{ modelValue: NavigationBarProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
if (!formData.value._local) {
|
if (!formData.value._local) {
|
||||||
formData.value._local = { previewMp: true, previewOther: false }
|
formData.value._local = { previewMp: true, previewOther: false }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NoticeBarProperty } from './config'
|
import { NoticeBarProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
// 通知栏属性面板
|
// 通知栏属性面板
|
||||||
defineOptions({ name: 'NoticeBarProperty' })
|
defineOptions({ name: 'NoticeBarProperty' })
|
||||||
// 表单校验
|
// 表单校验
|
||||||
@@ -40,7 +40,7 @@ const rules = {
|
|||||||
|
|
||||||
const props = defineProps<{ modelValue: NoticeBarProperty }>()
|
const props = defineProps<{ modelValue: NoticeBarProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PageConfigProperty } from './config'
|
import { PageConfigProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
// 导航栏属性面板
|
// 导航栏属性面板
|
||||||
defineOptions({ name: 'PageConfigProperty' })
|
defineOptions({ name: 'PageConfigProperty' })
|
||||||
// 表单校验
|
// 表单校验
|
||||||
@@ -28,7 +28,7 @@ const rules = {}
|
|||||||
|
|
||||||
const props = defineProps<{ modelValue: PageConfigProperty }>()
|
const props = defineProps<{ modelValue: PageConfigProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
@@ -25,14 +25,14 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PopoverProperty } from './config'
|
import { PopoverProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
|
|
||||||
// 弹窗广告属性面板
|
// 弹窗广告属性面板
|
||||||
defineOptions({ name: 'PopoverProperty' })
|
defineOptions({ name: 'PopoverProperty' })
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: PopoverProperty }>()
|
const props = defineProps<{ modelValue: PopoverProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
@@ -135,7 +135,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ProductCardProperty } from './config'
|
import { ProductCardProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
import SpuShowcase from '@/views/mall/product/spu/components/SpuShowcase.vue'
|
import SpuShowcase from '@/views/mall/product/spu/components/SpuShowcase.vue'
|
||||||
|
|
||||||
// 商品卡片属性面板
|
// 商品卡片属性面板
|
||||||
@@ -143,7 +143,7 @@ defineOptions({ name: 'ProductCardProperty' })
|
|||||||
|
|
||||||
const props = defineProps<{ modelValue: ProductCardProperty }>()
|
const props = defineProps<{ modelValue: ProductCardProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ProductListProperty } from './config'
|
import { ProductListProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
import SpuShowcase from '@/views/mall/product/spu/components/SpuShowcase.vue'
|
import SpuShowcase from '@/views/mall/product/spu/components/SpuShowcase.vue'
|
||||||
|
|
||||||
// 商品栏属性面板
|
// 商品栏属性面板
|
||||||
@@ -93,7 +93,7 @@ defineOptions({ name: 'ProductListProperty' })
|
|||||||
|
|
||||||
const props = defineProps<{ modelValue: ProductListProperty }>()
|
const props = defineProps<{ modelValue: ProductListProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PromotionArticleProperty } from './config'
|
import { PromotionArticleProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
import * as ArticleApi from '@/api/mall/promotion/article/index'
|
import * as ArticleApi from '@/api/mall/promotion/article/index'
|
||||||
|
|
||||||
// 营销文章属性面板
|
// 营销文章属性面板
|
||||||
@@ -33,7 +33,7 @@ defineOptions({ name: 'PromotionArticleProperty' })
|
|||||||
|
|
||||||
const props = defineProps<{ modelValue: PromotionArticleProperty }>()
|
const props = defineProps<{ modelValue: PromotionArticleProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
// 文章列表
|
// 文章列表
|
||||||
const articles = ref<ArticleApi.ArticleVO>([])
|
const articles = ref<ArticleApi.ArticleVO>([])
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PromotionCombinationProperty } from './config'
|
import { PromotionCombinationProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
|
import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
|
||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
import CombinationShowcase from '@/views/mall/promotion/combination/components/CombinationShowcase.vue'
|
import CombinationShowcase from '@/views/mall/promotion/combination/components/CombinationShowcase.vue'
|
||||||
@@ -150,7 +150,7 @@ defineOptions({ name: 'PromotionCombinationProperty' })
|
|||||||
|
|
||||||
const props = defineProps<{ modelValue: PromotionCombinationProperty }>()
|
const props = defineProps<{ modelValue: PromotionCombinationProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
// 活动列表
|
// 活动列表
|
||||||
const activityList = ref<CombinationActivityApi.CombinationActivityVO[]>([])
|
const activityList = ref<CombinationActivityApi.CombinationActivityVO[]>([])
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|||||||
@@ -140,7 +140,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { PromotionPointProperty } from './config'
|
import { PromotionPointProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
import PointShowcase from '@/views/mall/promotion/point/components/PointShowcase.vue'
|
import PointShowcase from '@/views/mall/promotion/point/components/PointShowcase.vue'
|
||||||
|
|
||||||
// 秒杀属性面板
|
// 秒杀属性面板
|
||||||
@@ -148,7 +148,7 @@ defineOptions({ name: 'PromotionPointProperty' })
|
|||||||
|
|
||||||
const props = defineProps<{ modelValue: PromotionPointProperty }>()
|
const props = defineProps<{ modelValue: PromotionPointProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped></style>
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PromotionSeckillProperty } from './config'
|
import { PromotionSeckillProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
|
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
|
||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
import SeckillShowcase from '@/views/mall/promotion/seckill/components/SeckillShowcase.vue'
|
import SeckillShowcase from '@/views/mall/promotion/seckill/components/SeckillShowcase.vue'
|
||||||
@@ -150,7 +150,7 @@ defineOptions({ name: 'PromotionSeckillProperty' })
|
|||||||
|
|
||||||
const props = defineProps<{ modelValue: PromotionSeckillProperty }>()
|
const props = defineProps<{ modelValue: PromotionSeckillProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
// 活动列表
|
// 活动列表
|
||||||
const activityList = ref<SeckillActivityApi.SeckillActivityVO[]>([])
|
const activityList = ref<SeckillActivityApi.SeckillActivityVO[]>([])
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
import { SearchProperty } from '@/components/DiyEditor/components/mobile/SearchBar/config'
|
import { SearchProperty } from '@/components/DiyEditor/components/mobile/SearchBar/config'
|
||||||
|
|
||||||
/** 搜索框属性面板 */
|
/** 搜索框属性面板 */
|
||||||
@@ -67,7 +67,7 @@ defineOptions({ name: 'SearchProperty' })
|
|||||||
|
|
||||||
const props = defineProps<{ modelValue: SearchProperty }>()
|
const props = defineProps<{ modelValue: SearchProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
@@ -80,13 +80,13 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { TabBarProperty, component, THEME_LIST } from './config'
|
import { TabBarProperty, component, THEME_LIST } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
// 底部导航栏
|
// 底部导航栏
|
||||||
defineOptions({ name: 'TabBarProperty' })
|
defineOptions({ name: 'TabBarProperty' })
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: TabBarProperty }>()
|
const props = defineProps<{ modelValue: TabBarProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
|
|
||||||
// 将数据库的值更新到右侧属性栏
|
// 将数据库的值更新到右侧属性栏
|
||||||
component.property.items = formData.value.items
|
component.property.items = formData.value.items
|
||||||
|
|||||||
@@ -101,13 +101,13 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { TitleBarProperty } from './config'
|
import { TitleBarProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
// 导航栏属性面板
|
// 导航栏属性面板
|
||||||
defineOptions({ name: 'TitleBarProperty' })
|
defineOptions({ name: 'TitleBarProperty' })
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: TitleBarProperty }>()
|
const props = defineProps<{ modelValue: TitleBarProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
|
|
||||||
// 表单校验
|
// 表单校验
|
||||||
const rules = {}
|
const rules = {}
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { UserCardProperty } from './config'
|
import { UserCardProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
|
|
||||||
// 用户卡片属性面板
|
// 用户卡片属性面板
|
||||||
defineOptions({ name: 'UserCardProperty' })
|
defineOptions({ name: 'UserCardProperty' })
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: UserCardProperty }>()
|
const props = defineProps<{ modelValue: UserCardProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
@@ -4,14 +4,14 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { UserCouponProperty } from './config'
|
import { UserCouponProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
|
|
||||||
// 用户卡券属性面板
|
// 用户卡券属性面板
|
||||||
defineOptions({ name: 'UserCouponProperty' })
|
defineOptions({ name: 'UserCouponProperty' })
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: UserCouponProperty }>()
|
const props = defineProps<{ modelValue: UserCouponProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
@@ -4,14 +4,14 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { UserOrderProperty } from './config'
|
import { UserOrderProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
|
|
||||||
// 用户订单属性面板
|
// 用户订单属性面板
|
||||||
defineOptions({ name: 'UserOrderProperty' })
|
defineOptions({ name: 'UserOrderProperty' })
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: UserOrderProperty }>()
|
const props = defineProps<{ modelValue: UserOrderProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
@@ -4,14 +4,14 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { UserWalletProperty } from './config'
|
import { UserWalletProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
|
|
||||||
// 用户资产属性面板
|
// 用户资产属性面板
|
||||||
defineOptions({ name: 'UserWalletProperty' })
|
defineOptions({ name: 'UserWalletProperty' })
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: UserWalletProperty }>()
|
const props = defineProps<{ modelValue: UserWalletProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
@@ -42,14 +42,14 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { VideoPlayerProperty } from './config'
|
import { VideoPlayerProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
|
|
||||||
// 视频播放属性面板
|
// 视频播放属性面板
|
||||||
defineOptions({ name: 'VideoPlayerProperty' })
|
defineOptions({ name: 'VideoPlayerProperty' })
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: VideoPlayerProperty }>()
|
const props = defineProps<{ modelValue: VideoPlayerProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
<el-tag
|
<el-tag
|
||||||
v-if="showPageConfig"
|
v-if="showPageConfig"
|
||||||
:effect="selectedComponent?.uid === pageConfigComponent.uid ? 'dark' : 'plain'"
|
:effect="selectedComponent?.uid === pageConfigComponent.uid ? 'dark' : 'plain'"
|
||||||
:type="selectedComponent?.uid === pageConfigComponent.uid ? '' : 'info'"
|
:type="selectedComponent?.uid === pageConfigComponent.uid ? 'primary' : 'info'"
|
||||||
size="large"
|
size="large"
|
||||||
@click="handleComponentSelected(pageConfigComponent)"
|
@click="handleComponentSelected(pageConfigComponent)"
|
||||||
>
|
>
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
<el-tag
|
<el-tag
|
||||||
v-if="component.position === 'fixed'"
|
v-if="component.position === 'fixed'"
|
||||||
:effect="selectedComponent?.uid === component.uid ? 'dark' : 'plain'"
|
:effect="selectedComponent?.uid === component.uid ? 'dark' : 'plain'"
|
||||||
:type="selectedComponent?.uid === component.uid ? '' : 'info'"
|
:type="selectedComponent?.uid === component.uid ? 'primary' : 'info'"
|
||||||
closable
|
closable
|
||||||
size="large"
|
size="large"
|
||||||
@click="handleComponentSelected(component)"
|
@click="handleComponentSelected(component)"
|
||||||
@@ -191,7 +191,7 @@ import { cloneDeep, includes } from 'lodash-es'
|
|||||||
import { component as PAGE_CONFIG_COMPONENT } from '@/components/DiyEditor/components/mobile/PageConfig/config'
|
import { component as PAGE_CONFIG_COMPONENT } from '@/components/DiyEditor/components/mobile/PageConfig/config'
|
||||||
import { component as NAVIGATION_BAR_COMPONENT } from './components/mobile/NavigationBar/config'
|
import { component as NAVIGATION_BAR_COMPONENT } from './components/mobile/NavigationBar/config'
|
||||||
import { component as TAB_BAR_COMPONENT } from './components/mobile/TabBar/config'
|
import { component as TAB_BAR_COMPONENT } from './components/mobile/TabBar/config'
|
||||||
import { isString } from '@/utils/is'
|
import { isEmpty, isString } from '@/utils/is'
|
||||||
import { DiyComponent, DiyComponentLibrary, PageConfig } from '@/components/DiyEditor/util'
|
import { DiyComponent, DiyComponentLibrary, PageConfig } from '@/components/DiyEditor/util'
|
||||||
import { componentConfigs } from '@/components/DiyEditor/components/mobile'
|
import { componentConfigs } from '@/components/DiyEditor/components/mobile'
|
||||||
import { array, oneOfType } from 'vue-types'
|
import { array, oneOfType } from 'vue-types'
|
||||||
@@ -238,24 +238,42 @@ const props = defineProps({
|
|||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
() => {
|
() => {
|
||||||
const modelValue = isString(props.modelValue)
|
const modelValue =
|
||||||
? (JSON.parse(props.modelValue) as PageConfig)
|
isString(props.modelValue) && !isEmpty(props.modelValue)
|
||||||
: props.modelValue
|
? (JSON.parse(props.modelValue) as PageConfig)
|
||||||
pageConfigComponent.value.property = modelValue?.page || PAGE_CONFIG_COMPONENT.property
|
: props.modelValue
|
||||||
|
pageConfigComponent.value.property =
|
||||||
|
(typeof modelValue !== 'string' && modelValue?.page) || PAGE_CONFIG_COMPONENT.property
|
||||||
navigationBarComponent.value.property =
|
navigationBarComponent.value.property =
|
||||||
modelValue?.navigationBar || NAVIGATION_BAR_COMPONENT.property
|
(typeof modelValue !== 'string' && modelValue?.navigationBar) ||
|
||||||
tabBarComponent.value.property = modelValue?.tabBar || TAB_BAR_COMPONENT.property
|
NAVIGATION_BAR_COMPONENT.property
|
||||||
|
tabBarComponent.value.property =
|
||||||
|
(typeof modelValue !== 'string' && modelValue?.tabBar) || TAB_BAR_COMPONENT.property
|
||||||
// 查找对应的页面组件
|
// 查找对应的页面组件
|
||||||
pageComponents.value = (modelValue?.components || []).map((item) => {
|
pageComponents.value = ((typeof modelValue !== 'string' && modelValue?.components) || []).map(
|
||||||
const component = componentConfigs[item.id]
|
(item) => {
|
||||||
return { ...component, property: item.property }
|
const component = componentConfigs[item.id]
|
||||||
})
|
return { ...component, property: item.property }
|
||||||
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true
|
immediate: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/** 选择组件修改其属性后更新它的配置 */
|
||||||
|
watch(
|
||||||
|
selectedComponent,
|
||||||
|
(val: any) => {
|
||||||
|
if (!val || selectedComponentIndex.value === -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pageComponents.value[selectedComponentIndex.value] = selectedComponent.value!
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
// 保存
|
// 保存
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
// 发送保存通知
|
// 发送保存通知
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { ref, Ref } from 'vue'
|
|
||||||
import { PageConfigProperty } from '@/components/DiyEditor/components/mobile/PageConfig/config'
|
import { PageConfigProperty } from '@/components/DiyEditor/components/mobile/PageConfig/config'
|
||||||
import { NavigationBarProperty } from '@/components/DiyEditor/components/mobile/NavigationBar/config'
|
import { NavigationBarProperty } from '@/components/DiyEditor/components/mobile/NavigationBar/config'
|
||||||
import { TabBarProperty } from '@/components/DiyEditor/components/mobile/TabBar/config'
|
import { TabBarProperty } from '@/components/DiyEditor/components/mobile/TabBar/config'
|
||||||
@@ -78,34 +77,6 @@ export interface PageConfig {
|
|||||||
// 页面组件,只保留组件ID,组件属性
|
// 页面组件,只保留组件ID,组件属性
|
||||||
export interface PageComponent extends Pick<DiyComponent<any>, 'id' | 'property'> {}
|
export interface PageComponent extends Pick<DiyComponent<any>, 'id' | 'property'> {}
|
||||||
|
|
||||||
// 属性表单监听
|
|
||||||
export function usePropertyForm<T>(modelValue: T, emit: Function): { formData: Ref<T> } {
|
|
||||||
const formData = ref<T>()
|
|
||||||
// 监听属性数据变动
|
|
||||||
watch(
|
|
||||||
() => modelValue,
|
|
||||||
() => {
|
|
||||||
formData.value = modelValue
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deep: true,
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
// 监听表单数据变动
|
|
||||||
watch(
|
|
||||||
() => formData.value,
|
|
||||||
() => {
|
|
||||||
emit('update:modelValue', formData.value)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deep: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return { formData } as { formData: Ref<T> }
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面组件库
|
// 页面组件库
|
||||||
export const PAGE_LIBS = [
|
export const PAGE_LIBS = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,9 +13,16 @@
|
|||||||
class="mb-4px flex flex-col gap-4px border border-gray-2 border-rounded rounded border-solid p-8px"
|
class="mb-4px flex flex-col gap-4px border border-gray-2 border-rounded rounded border-solid p-8px"
|
||||||
>
|
>
|
||||||
<!-- 操作按钮区 -->
|
<!-- 操作按钮区 -->
|
||||||
<div class="m--8px m-b-4px flex flex-row items-center justify-between p-8px" style="background-color: var(--app-content-bg-color);">
|
<div
|
||||||
|
class="m--8px m-b-4px flex flex-row items-center justify-between p-8px"
|
||||||
|
style="background-color: var(--app-content-bg-color)"
|
||||||
|
>
|
||||||
<el-tooltip content="拖动排序">
|
<el-tooltip content="拖动排序">
|
||||||
<Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" style="color: #8a909c;" />
|
<Icon
|
||||||
|
icon="ic:round-drag-indicator"
|
||||||
|
class="drag-icon cursor-move"
|
||||||
|
style="color: #8a909c"
|
||||||
|
/>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip content="删除">
|
<el-tooltip content="删除">
|
||||||
<Icon
|
<Icon
|
||||||
@@ -47,7 +54,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// 拖拽组件
|
// 拖拽组件
|
||||||
import VueDraggable from 'vuedraggable'
|
import VueDraggable from 'vuedraggable'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { useVModel } from '@vueuse/core'
|
||||||
import { any, array } from 'vue-types'
|
import { any, array } from 'vue-types'
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
import { cloneDeep } from 'lodash-es'
|
import { cloneDeep } from 'lodash-es'
|
||||||
@@ -66,7 +73,7 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
// 定义事件
|
// 定义事件
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
|
|
||||||
// 处理添加
|
// 处理添加
|
||||||
const handleAdd = () => formData.value.push(cloneDeep(props.emptyItem || {}))
|
const handleAdd = () => formData.value.push(cloneDeep(props.emptyItem || {}))
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-input v-model="valueRef" v-bind="$attrs">
|
<el-input v-model="modelValue" v-bind="$attrs">
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-color-picker v-model="colorRef" :predefine="PREDEFINE_COLORS" />
|
<el-color-picker v-model="color" :predefine="PREDEFINE_COLORS" />
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</template>
|
</template>
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
import { PREDEFINE_COLORS } from '@/utils/color'
|
import { PREDEFINE_COLORS } from '@/utils/color'
|
||||||
|
import { useVModels } from '@vueuse/core'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 带颜色选择器输入框
|
* 带颜色选择器输入框
|
||||||
@@ -19,33 +20,8 @@ const props = defineProps({
|
|||||||
modelValue: propTypes.string.def('').isRequired,
|
modelValue: propTypes.string.def('').isRequired,
|
||||||
color: propTypes.string.def('').isRequired
|
color: propTypes.string.def('').isRequired
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.modelValue,
|
|
||||||
(val: string) => {
|
|
||||||
if (val === unref(valueRef)) return
|
|
||||||
valueRef.value = val
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'update:color'])
|
const emit = defineEmits(['update:modelValue', 'update:color'])
|
||||||
|
const { modelValue, color } = useVModels(props, emit)
|
||||||
// 输入框的值
|
|
||||||
const valueRef = ref(props.modelValue)
|
|
||||||
watch(
|
|
||||||
() => valueRef.value,
|
|
||||||
(val: string) => {
|
|
||||||
emit('update:modelValue', val)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
// 颜色
|
|
||||||
const colorRef = ref(props.color)
|
|
||||||
watch(
|
|
||||||
() => colorRef.value,
|
|
||||||
(val: string) => {
|
|
||||||
emit('update:color', val)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
:deep(.el-input-group__append) {
|
:deep(.el-input-group__append) {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,24 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="handler-item-text">包容分支</div>
|
<div class="handler-item-text">包容分支</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="handler-item" @click="addNode(NodeType.DELAY_TIMER_NODE)">
|
||||||
|
<div class="handler-item-icon delay">
|
||||||
|
<span class="iconfont icon-size icon-delay"></span>
|
||||||
|
</div>
|
||||||
|
<div class="handler-item-text">延迟器</div>
|
||||||
|
</div>
|
||||||
|
<div class="handler-item" @click="addNode(NodeType.ROUTER_BRANCH_NODE)">
|
||||||
|
<div class="handler-item-icon router">
|
||||||
|
<span class="iconfont icon-size icon-router"></span>
|
||||||
|
</div>
|
||||||
|
<div class="handler-item-text">路由分支</div>
|
||||||
|
</div>
|
||||||
|
<div class="handler-item" @click="addNode(NodeType.TRIGGER_NODE)">
|
||||||
|
<div class="handler-item-icon trigger">
|
||||||
|
<span class="iconfont icon-size icon-trigger"></span>
|
||||||
|
</div>
|
||||||
|
<div class="handler-item-text">触发器</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<div class="add-icon"><Icon icon="ep:plus" /></div>
|
<div class="add-icon"><Icon icon="ep:plus" /></div>
|
||||||
@@ -53,12 +71,14 @@ import {
|
|||||||
ApproveMethodType,
|
ApproveMethodType,
|
||||||
AssignEmptyHandlerType,
|
AssignEmptyHandlerType,
|
||||||
AssignStartUserHandlerType,
|
AssignStartUserHandlerType,
|
||||||
|
ConditionType,
|
||||||
NODE_DEFAULT_NAME,
|
NODE_DEFAULT_NAME,
|
||||||
NodeType,
|
NodeType,
|
||||||
RejectHandlerType,
|
RejectHandlerType,
|
||||||
SimpleFlowNode
|
SimpleFlowNode,
|
||||||
|
DEFAULT_CONDITION_GROUP_VALUE
|
||||||
} from './consts'
|
} from './consts'
|
||||||
import { generateUUID } from '@/utils'
|
import {generateUUID} from '@/utils'
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'NodeHandler'
|
name: 'NodeHandler'
|
||||||
@@ -113,7 +133,16 @@ const addNode = (type: number) => {
|
|||||||
type: AssignEmptyHandlerType.APPROVE
|
type: AssignEmptyHandlerType.APPROVE
|
||||||
},
|
},
|
||||||
assignStartUserHandlerType: AssignStartUserHandlerType.START_USER_AUDIT,
|
assignStartUserHandlerType: AssignStartUserHandlerType.START_USER_AUDIT,
|
||||||
childNode: props.childNode
|
childNode: props.childNode,
|
||||||
|
taskCreateListener: {
|
||||||
|
enable: false
|
||||||
|
},
|
||||||
|
taskAssignListener: {
|
||||||
|
enable: false
|
||||||
|
},
|
||||||
|
taskCompleteListener: {
|
||||||
|
enable: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
emits('update:childNode', data)
|
emits('update:childNode', data)
|
||||||
}
|
}
|
||||||
@@ -140,8 +169,11 @@ const addNode = (type: number) => {
|
|||||||
showText: '',
|
showText: '',
|
||||||
type: NodeType.CONDITION_NODE,
|
type: NodeType.CONDITION_NODE,
|
||||||
childNode: undefined,
|
childNode: undefined,
|
||||||
conditionType: 1,
|
conditionSetting: {
|
||||||
defaultFlow: false
|
defaultFlow: false,
|
||||||
|
conditionType: ConditionType.RULE,
|
||||||
|
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'Flow_' + generateUUID(),
|
id: 'Flow_' + generateUUID(),
|
||||||
@@ -149,8 +181,9 @@ const addNode = (type: number) => {
|
|||||||
showText: '未满足其它条件时,将进入此分支',
|
showText: '未满足其它条件时,将进入此分支',
|
||||||
type: NodeType.CONDITION_NODE,
|
type: NodeType.CONDITION_NODE,
|
||||||
childNode: undefined,
|
childNode: undefined,
|
||||||
conditionType: undefined,
|
conditionSetting: {
|
||||||
defaultFlow: true
|
defaultFlow: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -194,7 +227,11 @@ const addNode = (type: number) => {
|
|||||||
showText: '',
|
showText: '',
|
||||||
type: NodeType.CONDITION_NODE,
|
type: NodeType.CONDITION_NODE,
|
||||||
childNode: undefined,
|
childNode: undefined,
|
||||||
defaultFlow: false
|
conditionSetting: {
|
||||||
|
defaultFlow: false,
|
||||||
|
conditionType: ConditionType.RULE,
|
||||||
|
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'Flow_' + generateUUID(),
|
id: 'Flow_' + generateUUID(),
|
||||||
@@ -202,12 +239,44 @@ const addNode = (type: number) => {
|
|||||||
showText: '未满足其它条件时,将进入此分支',
|
showText: '未满足其它条件时,将进入此分支',
|
||||||
type: NodeType.CONDITION_NODE,
|
type: NodeType.CONDITION_NODE,
|
||||||
childNode: undefined,
|
childNode: undefined,
|
||||||
defaultFlow: true
|
conditionSetting: {
|
||||||
|
defaultFlow: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
emits('update:childNode', data)
|
emits('update:childNode', data)
|
||||||
}
|
}
|
||||||
|
if (type === NodeType.DELAY_TIMER_NODE) {
|
||||||
|
const data: SimpleFlowNode = {
|
||||||
|
id: 'Activity_' + generateUUID(),
|
||||||
|
name: NODE_DEFAULT_NAME.get(NodeType.DELAY_TIMER_NODE) as string,
|
||||||
|
showText: '',
|
||||||
|
type: NodeType.DELAY_TIMER_NODE,
|
||||||
|
childNode: props.childNode
|
||||||
|
}
|
||||||
|
emits('update:childNode', data)
|
||||||
|
}
|
||||||
|
if (type === NodeType.ROUTER_BRANCH_NODE) {
|
||||||
|
const data: SimpleFlowNode = {
|
||||||
|
id: 'GateWay_' + generateUUID(),
|
||||||
|
name: NODE_DEFAULT_NAME.get(NodeType.ROUTER_BRANCH_NODE) as string,
|
||||||
|
showText: '',
|
||||||
|
type: NodeType.ROUTER_BRANCH_NODE,
|
||||||
|
childNode: props.childNode
|
||||||
|
}
|
||||||
|
emits('update:childNode', data)
|
||||||
|
}
|
||||||
|
if (type === NodeType.TRIGGER_NODE) {
|
||||||
|
const data: SimpleFlowNode = {
|
||||||
|
id: 'Activity_' + generateUUID(),
|
||||||
|
name: NODE_DEFAULT_NAME.get(NodeType.TRIGGER_NODE) as string,
|
||||||
|
showText: '',
|
||||||
|
type: NodeType.TRIGGER_NODE,
|
||||||
|
childNode: props.childNode
|
||||||
|
}
|
||||||
|
emits('update:childNode', data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,24 @@
|
|||||||
@update:model-value="handleModelValueUpdate"
|
@update:model-value="handleModelValueUpdate"
|
||||||
@find:parent-node="findFromParentNode"
|
@find:parent-node="findFromParentNode"
|
||||||
/>
|
/>
|
||||||
|
<!-- 延迟器节点 -->
|
||||||
|
<DelayTimerNode
|
||||||
|
v-if="currentNode && currentNode.type === NodeType.DELAY_TIMER_NODE"
|
||||||
|
:flow-node="currentNode"
|
||||||
|
@update:flow-node="handleModelValueUpdate"
|
||||||
|
/>
|
||||||
|
<!-- 路由分支节点 -->
|
||||||
|
<RouterNode
|
||||||
|
v-if="currentNode && currentNode.type === NodeType.ROUTER_BRANCH_NODE"
|
||||||
|
:flow-node="currentNode"
|
||||||
|
@update:flow-node="handleModelValueUpdate"
|
||||||
|
/>
|
||||||
|
<!-- 触发器节点 -->
|
||||||
|
<TriggerNode
|
||||||
|
v-if="currentNode && currentNode.type === NodeType.TRIGGER_NODE"
|
||||||
|
:flow-node="currentNode"
|
||||||
|
@update:flow-node="handleModelValueUpdate"
|
||||||
|
/>
|
||||||
<!-- 递归显示孩子节点 -->
|
<!-- 递归显示孩子节点 -->
|
||||||
<ProcessNodeTree
|
<ProcessNodeTree
|
||||||
v-if="currentNode && currentNode.childNode"
|
v-if="currentNode && currentNode.childNode"
|
||||||
@@ -60,6 +78,9 @@ import CopyTaskNode from './nodes/CopyTaskNode.vue'
|
|||||||
import ExclusiveNode from './nodes/ExclusiveNode.vue'
|
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 RouterNode from './nodes/RouterNode.vue'
|
||||||
|
import TriggerNode from './nodes/TriggerNode.vue'
|
||||||
import { SimpleFlowNode, NodeType } from './consts'
|
import { SimpleFlowNode, NodeType } from './consts'
|
||||||
import { useWatchNode } from './node'
|
import { useWatchNode } from './node'
|
||||||
defineOptions({
|
defineOptions({
|
||||||
|
|||||||
@@ -56,16 +56,13 @@ const props = defineProps({
|
|||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
// 可发起流程的人员编号
|
// 可发起流程的人员编号
|
||||||
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,9 +82,11 @@ 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)
|
||||||
const errorDialogVisible = ref(false)
|
const errorDialogVisible = ref(false)
|
||||||
let errorNodes: SimpleFlowNode[] = []
|
let errorNodes: SimpleFlowNode[] = []
|
||||||
|
|
||||||
@@ -112,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)
|
||||||
@@ -246,18 +185,9 @@ 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()
|
||||||
}
|
}
|
||||||
@@ -268,38 +198,5 @@ onMounted(async () => {
|
|||||||
|
|
||||||
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>
|
||||||
@@ -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>
|
||||||
@@ -23,6 +23,16 @@ export enum NodeType {
|
|||||||
*/
|
*/
|
||||||
COPY_TASK_NODE = 12,
|
COPY_TASK_NODE = 12,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 延迟器节点
|
||||||
|
*/
|
||||||
|
DELAY_TIMER_NODE = 14,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发器节点
|
||||||
|
*/
|
||||||
|
TRIGGER_NODE = 15,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 条件节点
|
* 条件节点
|
||||||
*/
|
*/
|
||||||
@@ -39,7 +49,11 @@ export enum NodeType {
|
|||||||
/**
|
/**
|
||||||
* 包容分支节点 (对应包容网关)
|
* 包容分支节点 (对应包容网关)
|
||||||
*/
|
*/
|
||||||
INCLUSIVE_BRANCH_NODE = 53
|
INCLUSIVE_BRANCH_NODE = 53,
|
||||||
|
/**
|
||||||
|
* 路由分支节点
|
||||||
|
*/
|
||||||
|
ROUTER_BRANCH_NODE = 54
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NodeId {
|
export enum NodeId {
|
||||||
@@ -88,16 +102,27 @@ export interface SimpleFlowNode {
|
|||||||
assignEmptyHandler?: AssignEmptyHandler
|
assignEmptyHandler?: AssignEmptyHandler
|
||||||
// 审批节点的审批人与发起人相同时,对应的处理类型
|
// 审批节点的审批人与发起人相同时,对应的处理类型
|
||||||
assignStartUserHandlerType?: number
|
assignStartUserHandlerType?: number
|
||||||
// 条件类型
|
// 创建任务监听器
|
||||||
conditionType?: ConditionType
|
taskCreateListener?: ListenerHandler
|
||||||
// 条件表达式
|
// 创建任务监听器
|
||||||
conditionExpression?: string
|
taskAssignListener?: ListenerHandler
|
||||||
// 条件组
|
// 创建任务监听器
|
||||||
conditionGroups?: ConditionGroup
|
taskCompleteListener?: ListenerHandler
|
||||||
// 是否默认的条件
|
// 条件设置
|
||||||
defaultFlow?: boolean
|
conditionSetting?: ConditionSetting
|
||||||
// 活动的状态,用于前端节点状态展示
|
// 活动的状态,用于前端节点状态展示
|
||||||
activityStatus?: TaskStatusEnum
|
activityStatus?: TaskStatusEnum
|
||||||
|
// 延迟设置
|
||||||
|
delaySetting?: DelaySetting
|
||||||
|
// 路由分支
|
||||||
|
routerGroups?: RouterSetting[]
|
||||||
|
defaultFlowId?: string
|
||||||
|
// 签名
|
||||||
|
signEnable?: boolean
|
||||||
|
// 审批意见
|
||||||
|
reasonRequire?: boolean
|
||||||
|
// 触发器设置
|
||||||
|
triggerSetting?: TriggerSetting
|
||||||
}
|
}
|
||||||
// 候选人策略枚举 ( 用于审批节点。抄送节点 )
|
// 候选人策略枚举 ( 用于审批节点。抄送节点 )
|
||||||
export enum CandidateStrategy {
|
export enum CandidateStrategy {
|
||||||
@@ -215,6 +240,41 @@ export type AssignEmptyHandler = {
|
|||||||
userIds?: number[]
|
userIds?: number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听器的结构定义
|
||||||
|
*/
|
||||||
|
export type ListenerHandler = {
|
||||||
|
enable: boolean
|
||||||
|
path?: string
|
||||||
|
header?: HttpRequestParam[]
|
||||||
|
body?: HttpRequestParam[]
|
||||||
|
}
|
||||||
|
export type HttpRequestParam = {
|
||||||
|
key: string
|
||||||
|
type: number
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
export enum BpmHttpRequestParamTypeEnum {
|
||||||
|
/**
|
||||||
|
* 固定值
|
||||||
|
*/
|
||||||
|
FIXED_VALUE = 1,
|
||||||
|
/**
|
||||||
|
* 表单
|
||||||
|
*/
|
||||||
|
FROM_FORM = 2
|
||||||
|
}
|
||||||
|
export const BPM_HTTP_REQUEST_PARAM_TYPES = [
|
||||||
|
{
|
||||||
|
value: 1,
|
||||||
|
label: '固定值'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 2,
|
||||||
|
label: '表单'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
// 审批拒绝类型枚举
|
// 审批拒绝类型枚举
|
||||||
export enum RejectHandlerType {
|
export enum RejectHandlerType {
|
||||||
/**
|
/**
|
||||||
@@ -308,6 +368,20 @@ export enum TimeUnitType {
|
|||||||
DAY = 3
|
DAY = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件节点设置结构定义,用于条件节点
|
||||||
|
*/
|
||||||
|
export type ConditionSetting = {
|
||||||
|
// 条件类型
|
||||||
|
conditionType?: ConditionType
|
||||||
|
// 条件表达式
|
||||||
|
conditionExpression?: string
|
||||||
|
// 条件组
|
||||||
|
conditionGroups?: ConditionGroup
|
||||||
|
// 是否默认的条件
|
||||||
|
defaultFlow?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
// 条件配置类型 ( 用于条件节点配置 )
|
// 条件配置类型 ( 用于条件节点配置 )
|
||||||
export enum ConditionType {
|
export enum ConditionType {
|
||||||
/**
|
/**
|
||||||
@@ -382,8 +456,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
|
||||||
@@ -398,6 +470,24 @@ export type ConditionGroup = {
|
|||||||
// 条件数组
|
// 条件数组
|
||||||
conditions: Condition[]
|
conditions: Condition[]
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 条件组默认值
|
||||||
|
*/
|
||||||
|
export const DEFAULT_CONDITION_GROUP_VALUE = {
|
||||||
|
and: true,
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
and: true,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
opCode: '==',
|
||||||
|
leftSide: '',
|
||||||
|
rightSide: ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 条件结构定义
|
* 条件结构定义
|
||||||
@@ -413,12 +503,18 @@ NODE_DEFAULT_TEXT.set(NodeType.USER_TASK_NODE, '请配置审批人')
|
|||||||
NODE_DEFAULT_TEXT.set(NodeType.COPY_TASK_NODE, '请配置抄送人')
|
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.ROUTER_BRANCH_NODE, '请设置路由节点')
|
||||||
|
NODE_DEFAULT_TEXT.set(NodeType.TRIGGER_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, '审批人')
|
||||||
NODE_DEFAULT_NAME.set(NodeType.COPY_TASK_NODE, '抄送人')
|
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.ROUTER_BRANCH_NODE, '路由分支')
|
||||||
|
NODE_DEFAULT_NAME.set(NodeType.TRIGGER_NODE, '触发器')
|
||||||
|
|
||||||
// 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序
|
// 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序
|
||||||
export const CANDIDATE_STRATEGY: DictDataVO[] = [
|
export const CANDIDATE_STRATEGY: DictDataVO[] = [
|
||||||
@@ -451,8 +547,8 @@ export const APPROVE_METHODS: DictDataVO[] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
export const CONDITION_CONFIG_TYPES: DictDataVO[] = [
|
export const CONDITION_CONFIG_TYPES: DictDataVO[] = [
|
||||||
{ label: '条件表达式', value: ConditionType.EXPRESSION },
|
{ label: '条件规则', value: ConditionType.RULE },
|
||||||
{ label: '条件规则', value: ConditionType.RULE }
|
{ label: '条件表达式', value: ConditionType.EXPRESSION }
|
||||||
]
|
]
|
||||||
|
|
||||||
// 时间单位类型
|
// 时间单位类型
|
||||||
@@ -566,5 +662,101 @@ export enum ProcessVariableEnum {
|
|||||||
/**
|
/**
|
||||||
* 发起用户 ID
|
* 发起用户 ID
|
||||||
*/
|
*/
|
||||||
START_USER_ID = 'PROCESS_START_USER_ID'
|
START_USER_ID = 'PROCESS_START_USER_ID',
|
||||||
|
/**
|
||||||
|
* 发起时间
|
||||||
|
*/
|
||||||
|
START_TIME = 'PROCESS_START_TIME',
|
||||||
|
/**
|
||||||
|
* 流程定义名称
|
||||||
|
*/
|
||||||
|
PROCESS_DEFINITION_NAME = 'PROCESS_DEFINITION_NAME'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 延迟设置
|
||||||
|
*/
|
||||||
|
export type DelaySetting = {
|
||||||
|
// 延迟类型
|
||||||
|
delayType: number
|
||||||
|
// 延迟时间表达式
|
||||||
|
delayTime: string
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 延迟类型
|
||||||
|
*/
|
||||||
|
export enum DelayTypeEnum {
|
||||||
|
/**
|
||||||
|
* 固定时长
|
||||||
|
*/
|
||||||
|
FIXED_TIME_DURATION = 1,
|
||||||
|
/**
|
||||||
|
* 固定日期时间
|
||||||
|
*/
|
||||||
|
FIXED_DATE_TIME = 2
|
||||||
|
}
|
||||||
|
export const DELAY_TYPE = [
|
||||||
|
{ label: '固定时长', value: DelayTypeEnum.FIXED_TIME_DURATION },
|
||||||
|
{ label: '固定日期', value: DelayTypeEnum.FIXED_DATE_TIME }
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 路由分支结构定义
|
||||||
|
*/
|
||||||
|
export type RouterSetting = {
|
||||||
|
nodeId: string
|
||||||
|
conditionType: ConditionType
|
||||||
|
conditionExpression: string
|
||||||
|
conditionGroups: ConditionGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 触发器相关定义 ====================
|
||||||
|
/**
|
||||||
|
* 触发器节点结构定义
|
||||||
|
*/
|
||||||
|
export type TriggerSetting = {
|
||||||
|
type: TriggerTypeEnum
|
||||||
|
httpRequestSetting?: HttpRequestTriggerSetting
|
||||||
|
normalFormSetting?: NormalFormTriggerSetting
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发器类型枚举
|
||||||
|
*/
|
||||||
|
export enum TriggerTypeEnum {
|
||||||
|
/**
|
||||||
|
* 发送 HTTP 请求触发器
|
||||||
|
*/
|
||||||
|
HTTP_REQUEST = 1,
|
||||||
|
/**
|
||||||
|
* 更新流程表单触发器
|
||||||
|
*/
|
||||||
|
UPDATE_NORMAL_FORM = 2 // TODO @jason:FORM_UPDATE?
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP 请求触发器结构定义
|
||||||
|
*/
|
||||||
|
export type HttpRequestTriggerSetting = {
|
||||||
|
// 请求 URL
|
||||||
|
url: string
|
||||||
|
// 请求头参数设置
|
||||||
|
header?: HttpRequestParam[]
|
||||||
|
// 请求体参数设置
|
||||||
|
body?: HttpRequestParam[]
|
||||||
|
// 请求响应设置
|
||||||
|
response?: Record<string, string>[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程表单触发器配置结构定义
|
||||||
|
*/
|
||||||
|
export type NormalFormTriggerSetting = {
|
||||||
|
// 更新表单字段
|
||||||
|
updateFormFields?: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TRIGGER_TYPES: DictDataVO[] = [
|
||||||
|
{ label: 'HTTP 请求', value: TriggerTypeEnum.HTTP_REQUEST },
|
||||||
|
{ label: '修改表单数据', value: TriggerTypeEnum.UPDATE_NORMAL_FORM }
|
||||||
|
]
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { cloneDeep } from 'lodash-es'
|
|
||||||
import { TaskStatusEnum } from '@/api/bpm/task'
|
import { TaskStatusEnum } from '@/api/bpm/task'
|
||||||
import * as RoleApi from '@/api/system/role'
|
import * as RoleApi from '@/api/system/role'
|
||||||
import * as DeptApi from '@/api/system/dept'
|
import * as DeptApi from '@/api/system/dept'
|
||||||
@@ -14,9 +13,12 @@ import {
|
|||||||
NODE_DEFAULT_NAME,
|
NODE_DEFAULT_NAME,
|
||||||
AssignStartUserHandlerType,
|
AssignStartUserHandlerType,
|
||||||
AssignEmptyHandlerType,
|
AssignEmptyHandlerType,
|
||||||
FieldPermissionType
|
FieldPermissionType,
|
||||||
|
HttpRequestParam,
|
||||||
|
ProcessVariableEnum
|
||||||
} from './consts'
|
} from './consts'
|
||||||
import { parseFormFields } from '@/components/FormCreate/src/utils/index'
|
import { parseFormFields } from '@/components/FormCreate/src/utils'
|
||||||
|
|
||||||
export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> {
|
export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> {
|
||||||
const node = ref<SimpleFlowNode>(props.flowNode)
|
const node = ref<SimpleFlowNode>(props.flowNode)
|
||||||
watch(
|
watch(
|
||||||
@@ -46,9 +48,9 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType)
|
|||||||
// 字段权限配置. 需要有 field, title, permissioin 属性
|
// 字段权限配置. 需要有 field, title, permissioin 属性
|
||||||
const fieldsPermissionConfig = ref<Array<Record<string, any>>>([])
|
const fieldsPermissionConfig = ref<Array<Record<string, any>>>([])
|
||||||
|
|
||||||
const formType = inject<Ref<number>>('formType') // 表单类型
|
const formType = inject<Ref<number | undefined>>('formType', ref()) // 表单类型
|
||||||
|
|
||||||
const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
|
const formFields = inject<Ref<string[]>>('formFields', ref([])) // 流程表单字段
|
||||||
|
|
||||||
const getNodeConfigFormFields = (nodeFormFields?: Array<Record<string, string>>) => {
|
const getNodeConfigFormFields = (nodeFormFields?: Array<Record<string, string>>) => {
|
||||||
nodeFormFields = toRaw(nodeFormFields)
|
nodeFormFields = toRaw(nodeFormFields)
|
||||||
@@ -104,16 +106,32 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType)
|
|||||||
getNodeConfigFormFields
|
getNodeConfigFormFields
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 获取表单的字段
|
* @description 获取流程表单的字段
|
||||||
*/
|
*/
|
||||||
export function useFormFields() {
|
export function useFormFields() {
|
||||||
const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
|
const formFields = inject<Ref<string[]>>('formFields', ref([])) // 流程表单字段
|
||||||
return parseFormCreateFields(unref(formFields))
|
return parseFormCreateFields(unref(formFields))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO @芋艿:后续需要把各种类似 useFormFieldsPermission 的逻辑,抽成一个通用方法。
|
||||||
|
/**
|
||||||
|
* @description 获取流程表单的字段和发起人字段
|
||||||
|
*/
|
||||||
|
export function useFormFieldsAndStartUser() {
|
||||||
|
const injectFormFields = inject<Ref<string[]>>('formFields', ref([])) // 流程表单字段
|
||||||
|
const formFields = parseFormCreateFields(unref(injectFormFields))
|
||||||
|
// 添加发起人
|
||||||
|
formFields.unshift({
|
||||||
|
field: ProcessVariableEnum.START_USER_ID,
|
||||||
|
title: '发起人',
|
||||||
|
required: true
|
||||||
|
})
|
||||||
|
return formFields
|
||||||
|
}
|
||||||
|
|
||||||
export type UserTaskFormType = {
|
export type UserTaskFormType = {
|
||||||
//candidateParamArray: any[]
|
|
||||||
candidateStrategy: CandidateStrategy
|
candidateStrategy: CandidateStrategy
|
||||||
approveMethod: ApproveMethodType
|
approveMethod: ApproveMethodType
|
||||||
roleIds?: number[] // 角色
|
roleIds?: number[] // 角色
|
||||||
@@ -136,10 +154,29 @@ export type UserTaskFormType = {
|
|||||||
timeDuration?: number
|
timeDuration?: number
|
||||||
maxRemindCount?: number
|
maxRemindCount?: number
|
||||||
buttonsSetting: any[]
|
buttonsSetting: any[]
|
||||||
|
taskCreateListenerEnable?: boolean
|
||||||
|
taskCreateListenerPath?: string
|
||||||
|
taskCreateListener?: {
|
||||||
|
header: HttpRequestParam[]
|
||||||
|
body: HttpRequestParam[]
|
||||||
|
}
|
||||||
|
taskAssignListenerEnable?: boolean
|
||||||
|
taskAssignListenerPath?: string
|
||||||
|
taskAssignListener?: {
|
||||||
|
header: HttpRequestParam[]
|
||||||
|
body: HttpRequestParam[]
|
||||||
|
}
|
||||||
|
taskCompleteListenerEnable?: boolean
|
||||||
|
taskCompleteListenerPath?: string
|
||||||
|
taskCompleteListener?: {
|
||||||
|
header: HttpRequestParam[]
|
||||||
|
body: HttpRequestParam[]
|
||||||
|
}
|
||||||
|
signEnable: boolean
|
||||||
|
reasonRequire: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CopyTaskFormType = {
|
export type CopyTaskFormType = {
|
||||||
// candidateParamArray: any[]
|
|
||||||
candidateStrategy: CandidateStrategy
|
candidateStrategy: CandidateStrategy
|
||||||
roleIds?: number[] // 角色
|
roleIds?: number[] // 角色
|
||||||
deptIds?: number[] // 部门
|
deptIds?: number[] // 部门
|
||||||
@@ -156,13 +193,13 @@ export type CopyTaskFormType = {
|
|||||||
* @description 节点表单数据。 用于审批节点、抄送节点
|
* @description 节点表单数据。 用于审批节点、抄送节点
|
||||||
*/
|
*/
|
||||||
export function useNodeForm(nodeType: NodeType) {
|
export function useNodeForm(nodeType: NodeType) {
|
||||||
const roleOptions = inject<Ref<RoleApi.RoleVO[]>>('roleList') // 角色列表
|
const roleOptions = inject<Ref<RoleApi.RoleVO[]>>('roleList', ref([])) // 角色列表
|
||||||
const postOptions = inject<Ref<PostApi.PostVO[]>>('postList') // 岗位列表
|
const postOptions = inject<Ref<PostApi.PostVO[]>>('postList', ref([])) // 岗位列表
|
||||||
const userOptions = inject<Ref<UserApi.UserVO[]>>('userList') // 用户列表
|
const userOptions = inject<Ref<UserApi.UserVO[]>>('userList', ref([])) // 用户列表
|
||||||
const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList') // 部门列表
|
const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList', ref([])) // 部门列表
|
||||||
const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList') // 用户组列表
|
const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList', ref([])) // 用户组列表
|
||||||
const deptTreeOptions = inject('deptTree') // 部门树
|
const deptTreeOptions = inject('deptTree', ref()) // 部门树
|
||||||
const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
|
const formFields = inject<Ref<string[]>>('formFields', ref([])) // 流程表单字段
|
||||||
const configForm = ref<UserTaskFormType | CopyTaskFormType>()
|
const configForm = ref<UserTaskFormType | CopyTaskFormType>()
|
||||||
if (nodeType === NodeType.USER_TASK_NODE) {
|
if (nodeType === NodeType.USER_TASK_NODE) {
|
||||||
configForm.value = {
|
configForm.value = {
|
||||||
|
|||||||
@@ -26,121 +26,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div>
|
<div>
|
||||||
<div class="mb-3 font-size-16px" v-if="currentNode.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" :rules="formRules" label-position="top">
|
<Condition ref="conditionRef" v-model="condition" />
|
||||||
<el-form-item label="配置方式" prop="conditionType">
|
|
||||||
<el-radio-group v-model="currentNode.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.conditionType === 1"
|
|
||||||
label="条件表达式"
|
|
||||||
prop="conditionExpression"
|
|
||||||
>
|
|
||||||
<el-input
|
|
||||||
type="textarea"
|
|
||||||
v-model="currentNode.conditionExpression"
|
|
||||||
clearable
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item v-if="currentNode.conditionType === 2" 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>
|
||||||
@@ -155,33 +45,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
SimpleFlowNode,
|
SimpleFlowNode,
|
||||||
CONDITION_CONFIG_TYPES,
|
|
||||||
ConditionType,
|
ConditionType,
|
||||||
COMPARISON_OPERATORS,
|
COMPARISON_OPERATORS,
|
||||||
ConditionGroup,
|
|
||||||
Condition,
|
|
||||||
ConditionRule,
|
|
||||||
ProcessVariableEnum
|
|
||||||
} from '../consts'
|
} from '../consts'
|
||||||
import { getDefaultConditionNodeName } from '../utils'
|
import { getDefaultConditionNodeName } from '../utils'
|
||||||
import { useFormFields } from '../node'
|
import { useFormFieldsAndStartUser } 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,
|
||||||
@@ -193,12 +66,10 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
const settingVisible = ref(false)
|
const settingVisible = ref(false)
|
||||||
|
const currentNode = ref<SimpleFlowNode>(props.conditionNode)
|
||||||
|
const condition = ref<any>()
|
||||||
const open = () => {
|
const open = () => {
|
||||||
if (currentNode.value.conditionType === ConditionType.RULE) {
|
condition.value = currentNode.value.conditionSetting
|
||||||
if (currentNode.value.conditionGroups) {
|
|
||||||
conditionGroups.value = currentNode.value.conditionGroups
|
|
||||||
}
|
|
||||||
}
|
|
||||||
settingVisible.value = true
|
settingVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,10 +90,10 @@ const blurEvent = () => {
|
|||||||
showInput.value = false
|
showInput.value = false
|
||||||
currentNode.value.name =
|
currentNode.value.name =
|
||||||
currentNode.value.name ||
|
currentNode.value.name ||
|
||||||
getDefaultConditionNodeName(props.nodeIndex, currentNode.value?.defaultFlow)
|
getDefaultConditionNodeName(props.nodeIndex, currentNode.value?.conditionSetting?.defaultFlow)
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentNode = ref<SimpleFlowNode>(props.conditionNode)
|
|
||||||
|
|
||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
@@ -239,31 +110,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.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
|
||||||
if (currentNode.value.conditionType === ConditionType.EXPRESSION) {
|
currentNode.value.conditionSetting!.conditionType = condition.value?.conditionType
|
||||||
currentNode.value.conditionGroups = undefined
|
if (currentNode.value.conditionSetting?.conditionType === ConditionType.EXPRESSION) {
|
||||||
|
currentNode.value.conditionSetting.conditionGroups = undefined
|
||||||
|
currentNode.value.conditionSetting.conditionExpression = condition.value?.conditionExpression
|
||||||
}
|
}
|
||||||
if (currentNode.value.conditionType === ConditionType.RULE) {
|
if (currentNode.value.conditionSetting!.conditionType === ConditionType.RULE) {
|
||||||
currentNode.value.conditionExpression = undefined
|
currentNode.value.conditionSetting!.conditionExpression = undefined
|
||||||
currentNode.value.conditionGroups = conditionGroups.value
|
currentNode.value.conditionSetting!.conditionGroups = condition.value?.conditionGroups
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
settingVisible.value = false
|
settingVisible.value = false
|
||||||
@@ -271,16 +138,16 @@ const saveConfig = async () => {
|
|||||||
}
|
}
|
||||||
const getShowText = (): string => {
|
const getShowText = (): string => {
|
||||||
let showText = ''
|
let showText = ''
|
||||||
if (currentNode.value.conditionType === ConditionType.EXPRESSION) {
|
if (condition.value?.conditionType === ConditionType.EXPRESSION) {
|
||||||
if (currentNode.value.conditionExpression) {
|
if (condition.value.conditionExpression) {
|
||||||
showText = `表达式:${currentNode.value.conditionExpression}`
|
showText = `表达式:${condition.value.conditionExpression}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (currentNode.value.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
|
||||||
@@ -303,85 +170,17 @@ 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 fieldOptions = useFormFieldsAndStartUser()
|
||||||
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 fieldOptions = computed(() => {
|
|
||||||
const fieldsCopy = fieldsInfo.slice()
|
|
||||||
// 固定添加发起人 ID 字段
|
|
||||||
fieldsCopy.unshift({
|
|
||||||
field: ProcessVariableEnum.START_USER_ID,
|
|
||||||
title: '发起人',
|
|
||||||
required: true
|
|
||||||
})
|
|
||||||
return fieldsCopy
|
|
||||||
})
|
|
||||||
|
|
||||||
/** 获取字段名称 */
|
/** 获取字段名称 */
|
||||||
const getFieldTitle = (field: string) => {
|
const getFieldTitle = (field: string) => {
|
||||||
const item = fieldOptions.value.find((item) => item.field === field)
|
const item = fieldOptions.find((item) => item.field === field)
|
||||||
return item?.title
|
return item?.title
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer
|
||||||
|
:append-to-body="true"
|
||||||
|
v-model="settingVisible"
|
||||||
|
:show-close="false"
|
||||||
|
:size="550"
|
||||||
|
:before-close="saveConfig"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="config-header">
|
||||||
|
<input
|
||||||
|
v-if="showInput"
|
||||||
|
type="text"
|
||||||
|
class="config-editable-input"
|
||||||
|
@blur="blurEvent()"
|
||||||
|
v-mountedFocus
|
||||||
|
v-model="nodeName"
|
||||||
|
:placeholder="nodeName"
|
||||||
|
/>
|
||||||
|
<div v-else class="node-name">
|
||||||
|
{{ nodeName }} <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
|
||||||
|
</div>
|
||||||
|
<div class="divide-line"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div>
|
||||||
|
<el-form ref="formRef" :model="configForm" label-position="top" :rules="formRules">
|
||||||
|
<el-form-item label="延迟时间" prop="delayType">
|
||||||
|
<el-radio-group v-model="configForm.delayType">
|
||||||
|
<el-radio-button
|
||||||
|
v-for="item in DELAY_TYPE"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="configForm.delayType === DelayTypeEnum.FIXED_TIME_DURATION">
|
||||||
|
<el-form-item prop="timeDuration">
|
||||||
|
<el-input-number
|
||||||
|
class="mr-2"
|
||||||
|
:style="{ width: '100px' }"
|
||||||
|
v-model="configForm.timeDuration"
|
||||||
|
:min="1"
|
||||||
|
controls-position="right"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-select v-model="configForm.timeUnit" class="mr-2" :style="{ width: '100px' }">
|
||||||
|
<el-option
|
||||||
|
v-for="item in TIME_UNIT_TYPES"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-text>后进入下一节点</el-text>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="configForm.delayType === DelayTypeEnum.FIXED_DATE_TIME" prop="dateTime">
|
||||||
|
<el-date-picker
|
||||||
|
class="mr-2"
|
||||||
|
v-model="configForm.dateTime"
|
||||||
|
type="datetime"
|
||||||
|
placeholder="请选择日期和时间"
|
||||||
|
value-format="YYYY-MM-DDTHH:mm:ss"
|
||||||
|
/>
|
||||||
|
<el-text>后进入下一节点</el-text>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-divider />
|
||||||
|
<div>
|
||||||
|
<el-button type="primary" @click="saveConfig">确 定</el-button>
|
||||||
|
<el-button @click="closeDrawer">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
SimpleFlowNode,
|
||||||
|
NodeType,
|
||||||
|
TIME_UNIT_TYPES,
|
||||||
|
TimeUnitType,
|
||||||
|
DelayTypeEnum,
|
||||||
|
DELAY_TYPE
|
||||||
|
} from '../consts'
|
||||||
|
import { useWatchNode, useDrawer, useNodeName } from '../node'
|
||||||
|
import { convertTimeUnit } from '../utils'
|
||||||
|
defineOptions({
|
||||||
|
name: 'DelayTimerNodeConfig'
|
||||||
|
})
|
||||||
|
const props = defineProps({
|
||||||
|
flowNode: {
|
||||||
|
type: Object as () => SimpleFlowNode,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 抽屉配置
|
||||||
|
const { settingVisible, closeDrawer, openDrawer } = useDrawer()
|
||||||
|
// 当前节点
|
||||||
|
const currentNode = useWatchNode(props)
|
||||||
|
// 节点名称
|
||||||
|
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.DELAY_TIMER_NODE)
|
||||||
|
// 抄送人表单配置
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
// 表单校验规则
|
||||||
|
const formRules = reactive({
|
||||||
|
delayType: [{ required: true, message: '延迟时间不能为空', trigger: 'change' }],
|
||||||
|
timeDuration: [{ required: true, message: '延迟时间不能为空', trigger: 'change' }],
|
||||||
|
dateTime: [{ required: true, message: '延迟时间不能为空', trigger: 'change' }]
|
||||||
|
})
|
||||||
|
// 配置表单数据
|
||||||
|
const configForm = ref({
|
||||||
|
delayType: DelayTypeEnum.FIXED_TIME_DURATION,
|
||||||
|
timeDuration: 1,
|
||||||
|
timeUnit: TimeUnitType.HOUR,
|
||||||
|
dateTime: ''
|
||||||
|
})
|
||||||
|
// 保存配置
|
||||||
|
const saveConfig = async () => {
|
||||||
|
if (!formRef) return false
|
||||||
|
const valid = await formRef.value.validate()
|
||||||
|
if (!valid) return false
|
||||||
|
const showText = getShowText()
|
||||||
|
if (!showText) return false
|
||||||
|
currentNode.value.name = nodeName.value!
|
||||||
|
currentNode.value.showText = showText
|
||||||
|
if (configForm.value.delayType === DelayTypeEnum.FIXED_TIME_DURATION) {
|
||||||
|
currentNode.value.delaySetting = {
|
||||||
|
delayType: configForm.value.delayType,
|
||||||
|
delayTime: getIsoTimeDuration()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (configForm.value.delayType === DelayTypeEnum.FIXED_DATE_TIME) {
|
||||||
|
currentNode.value.delaySetting = {
|
||||||
|
delayType: configForm.value.delayType,
|
||||||
|
delayTime: configForm.value.dateTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settingVisible.value = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const getShowText = (): string => {
|
||||||
|
let showText = ''
|
||||||
|
if (configForm.value.delayType === DelayTypeEnum.FIXED_TIME_DURATION) {
|
||||||
|
showText = `延迟${configForm.value.timeDuration}${TIME_UNIT_TYPES.find((item) => item.value === configForm.value.timeUnit).label}`
|
||||||
|
}
|
||||||
|
if (configForm.value.delayType === DelayTypeEnum.FIXED_DATE_TIME) {
|
||||||
|
showText = `延迟至${configForm.value.dateTime.replace('T', ' ')}`
|
||||||
|
}
|
||||||
|
return showText
|
||||||
|
}
|
||||||
|
const getIsoTimeDuration = () => {
|
||||||
|
let strTimeDuration = 'PT'
|
||||||
|
if (configForm.value.timeUnit === TimeUnitType.MINUTE) {
|
||||||
|
strTimeDuration += configForm.value.timeDuration + 'M'
|
||||||
|
}
|
||||||
|
if (configForm.value.timeUnit === TimeUnitType.HOUR) {
|
||||||
|
strTimeDuration += configForm.value.timeDuration + 'H'
|
||||||
|
}
|
||||||
|
if (configForm.value.timeUnit === TimeUnitType.DAY) {
|
||||||
|
strTimeDuration += configForm.value.timeDuration + 'D'
|
||||||
|
}
|
||||||
|
return strTimeDuration
|
||||||
|
}
|
||||||
|
// 显示延迟器节点配置, 由父组件传过来
|
||||||
|
const showDelayTimerNodeConfig = (node: SimpleFlowNode) => {
|
||||||
|
nodeName.value = node.name
|
||||||
|
if (node.delaySetting) {
|
||||||
|
configForm.value.delayType = node.delaySetting.delayType
|
||||||
|
// 固定时长
|
||||||
|
if (configForm.value.delayType === DelayTypeEnum.FIXED_TIME_DURATION) {
|
||||||
|
const strTimeDuration = node.delaySetting.delayTime
|
||||||
|
let parseTime = strTimeDuration.slice(2, strTimeDuration.length - 1)
|
||||||
|
let parseTimeUnit = strTimeDuration.slice(strTimeDuration.length - 1)
|
||||||
|
configForm.value.timeDuration = parseInt(parseTime)
|
||||||
|
configForm.value.timeUnit = convertTimeUnit(parseTimeUnit)
|
||||||
|
}
|
||||||
|
// 固定日期时间
|
||||||
|
if (configForm.value.delayType === DelayTypeEnum.FIXED_DATE_TIME) {
|
||||||
|
configForm.value.dateTime = node.delaySetting.delayTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ openDrawer, showDelayTimerNodeConfig }) // 暴露方法给父组件
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer
|
||||||
|
:append-to-body="true"
|
||||||
|
v-model="settingVisible"
|
||||||
|
:show-close="false"
|
||||||
|
:size="630"
|
||||||
|
:before-close="saveConfig"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="config-header">
|
||||||
|
<input
|
||||||
|
v-if="showInput"
|
||||||
|
type="text"
|
||||||
|
class="config-editable-input"
|
||||||
|
@blur="blurEvent()"
|
||||||
|
v-mountedFocus
|
||||||
|
v-model="nodeName"
|
||||||
|
:placeholder="nodeName"
|
||||||
|
/>
|
||||||
|
<div v-else class="node-name">
|
||||||
|
{{ nodeName }} <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
|
||||||
|
</div>
|
||||||
|
<div class="divide-line"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div>
|
||||||
|
<el-form label-position="top">
|
||||||
|
<el-card class="mb-15px" v-for="(item, index) in routerGroups" :key="index">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex flex-items-center">
|
||||||
|
<el-text size="large">路由{{ index + 1 }}</el-text>
|
||||||
|
<el-select class="ml-15px" v-model="item.nodeId" style="width: 180px">
|
||||||
|
<el-option
|
||||||
|
v-for="node in nodeOptions"
|
||||||
|
:key="node.value"
|
||||||
|
:label="node.label"
|
||||||
|
:value="node.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-button class="mla" type="danger" link @click="deleteRouterGroup(index)">
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<Condition
|
||||||
|
:ref="($event) => (conditionRef[index] = $event)"
|
||||||
|
v-model="routerGroups[index]"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<el-button class="w-1/1" type="primary" :icon="Plus" @click="addRouterGroup">
|
||||||
|
新增路由分支
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-divider />
|
||||||
|
<div>
|
||||||
|
<el-button type="primary" @click="saveConfig">确 定</el-button>
|
||||||
|
<el-button @click="closeDrawer">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Plus } from '@element-plus/icons-vue'
|
||||||
|
import { SimpleFlowNode, NodeType, ConditionType, RouterSetting } from '../consts'
|
||||||
|
import { useWatchNode, useDrawer, useNodeName } from '../node'
|
||||||
|
import Condition from './components/Condition.vue'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'RouterNodeConfig'
|
||||||
|
})
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const props = defineProps({
|
||||||
|
flowNode: {
|
||||||
|
type: Object as () => SimpleFlowNode,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const processNodeTree = inject<Ref<SimpleFlowNode>>('processNodeTree')
|
||||||
|
// 抽屉配置
|
||||||
|
const { settingVisible, closeDrawer, openDrawer } = useDrawer()
|
||||||
|
// 当前节点
|
||||||
|
const currentNode = useWatchNode(props)
|
||||||
|
// 节点名称
|
||||||
|
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.ROUTER_BRANCH_NODE)
|
||||||
|
const routerGroups = ref<RouterSetting[]>([])
|
||||||
|
const nodeOptions = ref<any>([])
|
||||||
|
const conditionRef = ref([])
|
||||||
|
|
||||||
|
/** 保存配置 */
|
||||||
|
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()
|
||||||
|
if (!showText) return false
|
||||||
|
currentNode.value.name = nodeName.value!
|
||||||
|
currentNode.value.showText = showText
|
||||||
|
currentNode.value.routerGroups = routerGroups.value
|
||||||
|
settingVisible.value = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// 显示路由分支节点配置, 由父组件传过来
|
||||||
|
const showRouteNodeConfig = (node: SimpleFlowNode) => {
|
||||||
|
getRouterNode(processNodeTree?.value)
|
||||||
|
routerGroups.value = []
|
||||||
|
nodeName.value = node.name
|
||||||
|
if (node.routerGroups) {
|
||||||
|
routerGroups.value = node.routerGroups
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getShowText = () => {
|
||||||
|
if (!routerGroups.value || !Array.isArray(routerGroups.value) || routerGroups.value.length <= 0) {
|
||||||
|
message.warning('请配置路由!')
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
for (const route of routerGroups.value) {
|
||||||
|
if (!route.nodeId || !route.conditionType) {
|
||||||
|
message.warning('请完善路由配置项!')
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (route.conditionType === ConditionType.EXPRESSION && !route.conditionExpression) {
|
||||||
|
message.warning('请完善路由配置项!')
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (route.conditionType === ConditionType.RULE) {
|
||||||
|
for (const condition of route.conditionGroups.conditions) {
|
||||||
|
for (const rule of condition.rules) {
|
||||||
|
if (!rule.leftSide || !rule.rightSide) {
|
||||||
|
message.warning('请完善路由配置项!')
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `${routerGroups.value.length}条路由分支`
|
||||||
|
}
|
||||||
|
|
||||||
|
const addRouterGroup = () => {
|
||||||
|
routerGroups.value.push({
|
||||||
|
nodeId: '',
|
||||||
|
conditionType: ConditionType.RULE,
|
||||||
|
conditionExpression: '',
|
||||||
|
conditionGroups: {
|
||||||
|
and: true,
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
and: true,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
opCode: '==',
|
||||||
|
leftSide: '',
|
||||||
|
rightSide: ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteRouterGroup = (index: number) => {
|
||||||
|
routerGroups.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归获取所有节点
|
||||||
|
const getRouterNode = (node) => {
|
||||||
|
// TODO 最好还需要满足以下要求
|
||||||
|
// 并行分支、包容分支内部节点不能跳转到外部节点
|
||||||
|
// 条件分支节点可以向上跳转到外部节点
|
||||||
|
while (true) {
|
||||||
|
if (!node) break
|
||||||
|
if (node.type !== NodeType.ROUTER_BRANCH_NODE && node.type !== NodeType.CONDITION_NODE) {
|
||||||
|
nodeOptions.value.push({
|
||||||
|
label: node.name,
|
||||||
|
value: node.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (!node.childNode || node.type === NodeType.END_EVENT_NODE) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (node.conditionNodes && node.conditionNodes.length) {
|
||||||
|
node.conditionNodes.forEach((item) => {
|
||||||
|
getRouterNode(item)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
node = node.childNode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ openDrawer, showRouteNodeConfig }) // 暴露方法给父组件
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer
|
||||||
|
:append-to-body="true"
|
||||||
|
v-model="settingVisible"
|
||||||
|
:show-close="false"
|
||||||
|
:size="550"
|
||||||
|
:before-close="saveConfig"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="config-header">
|
||||||
|
<input
|
||||||
|
v-if="showInput"
|
||||||
|
type="text"
|
||||||
|
class="config-editable-input"
|
||||||
|
@blur="blurEvent()"
|
||||||
|
v-mountedFocus
|
||||||
|
v-model="nodeName"
|
||||||
|
:placeholder="nodeName"
|
||||||
|
/>
|
||||||
|
<div v-else class="node-name">
|
||||||
|
{{ nodeName }} <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
|
||||||
|
</div>
|
||||||
|
<div class="divide-line"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div>
|
||||||
|
<el-form ref="formRef" :model="configForm" label-position="top" :rules="formRules">
|
||||||
|
<el-form-item label="触发器类型" prop="type">
|
||||||
|
<el-select v-model="configForm.type">
|
||||||
|
<el-option
|
||||||
|
v-for="(item, index) in TRIGGER_TYPES"
|
||||||
|
:key="index"
|
||||||
|
:value="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<!-- HTTP 请求触发器 -->
|
||||||
|
<div
|
||||||
|
v-if="configForm.type === TriggerTypeEnum.HTTP_REQUEST && configForm.httpRequestSetting"
|
||||||
|
>
|
||||||
|
<el-form-item>
|
||||||
|
<el-alert
|
||||||
|
title="仅支持 POST 请求,以请求体方式接收参数"
|
||||||
|
type="warning"
|
||||||
|
show-icon
|
||||||
|
:closable="false"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<!-- 请求地址-->
|
||||||
|
<el-form-item label="请求地址" prop="httpRequestSetting.url">
|
||||||
|
<el-input v-model="configForm.httpRequestSetting.url" />
|
||||||
|
</el-form-item>
|
||||||
|
<!-- 请求头,请求体设置-->
|
||||||
|
<HttpRequestParamSetting
|
||||||
|
:header="configForm.httpRequestSetting.header"
|
||||||
|
:body="configForm.httpRequestSetting.body"
|
||||||
|
:bind="'httpRequestSetting'"
|
||||||
|
/>
|
||||||
|
<!-- 返回值设置-->
|
||||||
|
<el-form-item label="返回值">
|
||||||
|
<el-alert
|
||||||
|
title="通过请求返回值, 可以修改流程表单的值"
|
||||||
|
type="warning"
|
||||||
|
show-icon
|
||||||
|
:closable="false"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<div
|
||||||
|
class="flex pt-2"
|
||||||
|
v-for="(item, index) in configForm.httpRequestSetting.response"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<div class="mr-2">
|
||||||
|
<el-form-item
|
||||||
|
:prop="`httpRequestSetting.response.${index}.key`"
|
||||||
|
:rules="{
|
||||||
|
required: true,
|
||||||
|
message: '表单字段不能为空',
|
||||||
|
trigger: 'blur'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-select class="w-160px!" v-model="item.key" placeholder="请选择表单字段">
|
||||||
|
<el-option
|
||||||
|
v-for="(field, fIdx) in formFields"
|
||||||
|
:key="fIdx"
|
||||||
|
:label="field.title"
|
||||||
|
:value="field.field"
|
||||||
|
:disabled="!field.required"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="mr-2">
|
||||||
|
<el-form-item
|
||||||
|
:prop="`httpRequestSetting.response.${index}.value`"
|
||||||
|
:rules="{
|
||||||
|
required: true,
|
||||||
|
message: '请求返回字段不能为空',
|
||||||
|
trigger: 'blur'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-input class="w-160px" v-model="item.value" placeholder="请求返回字段" />
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="mr-1 pt-1 cursor-pointer">
|
||||||
|
<Icon
|
||||||
|
icon="ep:delete"
|
||||||
|
:size="18"
|
||||||
|
@click="deleteHttpResponseSetting(configForm.httpRequestSetting.response!, index)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
text
|
||||||
|
@click="addHttpResponseSetting(configForm.httpRequestSetting.response!)"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" />添加一行
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
configForm.type === TriggerTypeEnum.UPDATE_NORMAL_FORM && configForm.normalFormSetting
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<el-divider content-position="left">修改表单设置</el-divider>
|
||||||
|
<div
|
||||||
|
class="flex items-center"
|
||||||
|
v-for="key in Object.keys(configForm.normalFormSetting.updateFormFields!)"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
<div class="mr-2 flex items-center">
|
||||||
|
<el-form-item>
|
||||||
|
<el-select
|
||||||
|
class="w-160px!"
|
||||||
|
:model-value="key"
|
||||||
|
@update:model-value="(newKey) => updateFormFieldKey(key, newKey)"
|
||||||
|
placeholder="请选择表单字段"
|
||||||
|
:disabled="key !== ''"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="(field, fIdx) in optionalUpdateFormFields"
|
||||||
|
:key="fIdx"
|
||||||
|
:label="field.title"
|
||||||
|
:value="field.field"
|
||||||
|
:disabled="field.disabled"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="mx-2"><el-form-item>的值设置为</el-form-item></div>
|
||||||
|
<div class="mr-2">
|
||||||
|
<el-form-item
|
||||||
|
:prop="`normalFormSetting.updateFormFields.${key}`"
|
||||||
|
:rules="{
|
||||||
|
required: true,
|
||||||
|
message: '值不能为空',
|
||||||
|
trigger: 'blur'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
class="w-160px"
|
||||||
|
v-model="configForm.normalFormSetting.updateFormFields![key]"
|
||||||
|
placeholder="请输入"
|
||||||
|
:disabled="!key"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="mr-1 pt-1 cursor-pointer">
|
||||||
|
<el-form-item>
|
||||||
|
<Icon icon="ep:delete" :size="18" @click="deleteFormFieldSetting(key)" />
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-button type="primary" text @click="addFormFieldSetting()">
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" />添加修改字段
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-divider />
|
||||||
|
<div>
|
||||||
|
<el-button type="primary" @click="saveConfig">确 定</el-button>
|
||||||
|
<el-button @click="closeDrawer">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { SimpleFlowNode, NodeType, TriggerSetting, TRIGGER_TYPES, TriggerTypeEnum } from '../consts'
|
||||||
|
import { useWatchNode, useDrawer, useNodeName, useFormFields } from '../node'
|
||||||
|
import HttpRequestParamSetting from './components/HttpRequestParamSetting.vue'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'TriggerNodeConfig'
|
||||||
|
})
|
||||||
|
const props = defineProps({
|
||||||
|
flowNode: {
|
||||||
|
type: Object as () => SimpleFlowNode,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
// 抽屉配置
|
||||||
|
const { settingVisible, closeDrawer, openDrawer } = useDrawer()
|
||||||
|
// 当前节点
|
||||||
|
const currentNode = useWatchNode(props)
|
||||||
|
// 节点名称
|
||||||
|
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.TRIGGER_NODE)
|
||||||
|
// 触发器表单配置
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
// 表单校验规则
|
||||||
|
const formRules = reactive({
|
||||||
|
type: [{ required: true, message: '触发器类型不能为空', trigger: 'change' }],
|
||||||
|
'httpRequestSetting.url': [{ required: true, message: '请求地址不能为空', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
// 触发器配置表单数据
|
||||||
|
const configForm = ref<TriggerSetting>({
|
||||||
|
type: TriggerTypeEnum.HTTP_REQUEST,
|
||||||
|
httpRequestSetting: {
|
||||||
|
url: '',
|
||||||
|
header: [],
|
||||||
|
body: [],
|
||||||
|
response: []
|
||||||
|
},
|
||||||
|
normalFormSetting: { updateFormFields: {} }
|
||||||
|
})
|
||||||
|
// 流程表单字段
|
||||||
|
const formFields = useFormFields()
|
||||||
|
|
||||||
|
// 可选的修改的表单字段
|
||||||
|
const optionalUpdateFormFields = computed(() => {
|
||||||
|
const usedFields = Object.keys(configForm.value.normalFormSetting?.updateFormFields || {})
|
||||||
|
return formFields.map((field) => ({
|
||||||
|
title: field.title,
|
||||||
|
field: field.field,
|
||||||
|
disabled: usedFields.includes(field.field)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateFormFieldKey = (oldKey: string, newKey: string) => {
|
||||||
|
if (!configForm.value.normalFormSetting?.updateFormFields) return
|
||||||
|
const value = configForm.value.normalFormSetting.updateFormFields[oldKey]
|
||||||
|
delete configForm.value.normalFormSetting.updateFormFields[oldKey]
|
||||||
|
configForm.value.normalFormSetting.updateFormFields[newKey] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 添加 HTTP 请求返回值设置项*/
|
||||||
|
const addHttpResponseSetting = (responseSetting: Record<string, string>[]) => {
|
||||||
|
responseSetting.push({
|
||||||
|
key: '',
|
||||||
|
value: ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除 HTTP 请求返回值设置项 */
|
||||||
|
const deleteHttpResponseSetting = (responseSetting: Record<string, string>[], index: number) => {
|
||||||
|
responseSetting.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 添加修改表单设置项 */
|
||||||
|
const addFormFieldSetting = () => {
|
||||||
|
if (configForm.value.normalFormSetting!.updateFormFields === undefined) {
|
||||||
|
configForm.value.normalFormSetting!.updateFormFields = {}
|
||||||
|
}
|
||||||
|
configForm.value.normalFormSetting!.updateFormFields[''] = undefined
|
||||||
|
}
|
||||||
|
/** 删除修改表单设置项 */
|
||||||
|
const deleteFormFieldSetting = (key: string) => {
|
||||||
|
if (!configForm.value.normalFormSetting?.updateFormFields) return
|
||||||
|
delete configForm.value.normalFormSetting.updateFormFields[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 保存配置 */
|
||||||
|
const saveConfig = async () => {
|
||||||
|
if (!formRef) return false
|
||||||
|
const valid = await formRef.value.validate()
|
||||||
|
if (!valid) return false
|
||||||
|
const showText = getShowText()
|
||||||
|
if (!showText) return false
|
||||||
|
currentNode.value.name = nodeName.value!
|
||||||
|
currentNode.value.showText = showText
|
||||||
|
if (configForm.value.type === TriggerTypeEnum.HTTP_REQUEST) {
|
||||||
|
configForm.value.normalFormSetting = undefined
|
||||||
|
}
|
||||||
|
if (configForm.value.type === TriggerTypeEnum.UPDATE_NORMAL_FORM) {
|
||||||
|
configForm.value.httpRequestSetting = undefined
|
||||||
|
}
|
||||||
|
currentNode.value.triggerSetting = configForm.value
|
||||||
|
settingVisible.value = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取节点展示内容 */
|
||||||
|
const getShowText = (): string => {
|
||||||
|
let showText = ''
|
||||||
|
if (configForm.value.type === TriggerTypeEnum.HTTP_REQUEST) {
|
||||||
|
showText = `${configForm.value.httpRequestSetting?.url}`
|
||||||
|
} else if (configForm.value.type === TriggerTypeEnum.UPDATE_NORMAL_FORM) {
|
||||||
|
const updatefields = Object.keys(configForm.value.normalFormSetting?.updateFormFields || {})
|
||||||
|
if (updatefields.length === 0) {
|
||||||
|
message.warning('请设置修改表单字段')
|
||||||
|
} else {
|
||||||
|
showText = '修改表单数据'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return showText
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 显示触发器节点配置, 由父组件传过来 */
|
||||||
|
const showTriggerNodeConfig = (node: SimpleFlowNode) => {
|
||||||
|
nodeName.value = node.name
|
||||||
|
if (node.triggerSetting) {
|
||||||
|
configForm.value = {
|
||||||
|
type: node.triggerSetting.type,
|
||||||
|
httpRequestSetting: node.triggerSetting.httpRequestSetting || {
|
||||||
|
url: '',
|
||||||
|
header: [],
|
||||||
|
body: [],
|
||||||
|
response: []
|
||||||
|
},
|
||||||
|
normalFormSetting: node.triggerSetting.normalFormSetting || { updateFormFields: {} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ openDrawer, showTriggerNodeConfig }) // 暴露方法给父组件
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
:append-to-body="true"
|
:append-to-body="true"
|
||||||
v-model="settingVisible"
|
v-model="settingVisible"
|
||||||
:show-close="false"
|
:show-close="false"
|
||||||
:size="550"
|
:size="580"
|
||||||
:before-close="saveConfig"
|
:before-close="saveConfig"
|
||||||
class="justify-start"
|
class="justify-start"
|
||||||
>
|
>
|
||||||
@@ -19,7 +19,8 @@
|
|||||||
:placeholder="nodeName"
|
:placeholder="nodeName"
|
||||||
/>
|
/>
|
||||||
<div v-else class="node-name">
|
<div v-else class="node-name">
|
||||||
{{ nodeName }} <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
|
{{ nodeName }}
|
||||||
|
<Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
|
||||||
</div>
|
</div>
|
||||||
<div class="divide-line"></div>
|
<div class="divide-line"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -46,14 +47,13 @@
|
|||||||
v-model="configForm.candidateStrategy"
|
v-model="configForm.candidateStrategy"
|
||||||
@change="changeCandidateStrategy"
|
@change="changeCandidateStrategy"
|
||||||
>
|
>
|
||||||
<el-radio
|
<el-row>
|
||||||
v-for="(dict, index) in CANDIDATE_STRATEGY"
|
<el-col v-for="(dict, index) in CANDIDATE_STRATEGY" :key="index" :span="8">
|
||||||
:key="index"
|
<el-radio :value="dict.value" :label="dict.value">
|
||||||
:value="dict.value"
|
{{ dict.label }}
|
||||||
:label="dict.value"
|
</el-radio>
|
||||||
>
|
</el-col>
|
||||||
{{ dict.label }}
|
</el-row>
|
||||||
</el-radio>
|
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item
|
<el-form-item
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
label="指定角色"
|
label="指定角色"
|
||||||
prop="roleIds"
|
prop="roleIds"
|
||||||
>
|
>
|
||||||
<el-select v-model="configForm.roleIds" clearable multiple style="width: 100%">
|
<el-select filterable v-model="configForm.roleIds" clearable multiple style="width: 100%">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in roleOptions"
|
v-for="item in roleOptions"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
prop="postIds"
|
prop="postIds"
|
||||||
span="24"
|
span="24"
|
||||||
>
|
>
|
||||||
<el-select v-model="configForm.postIds" clearable multiple style="width: 100%">
|
<el-select filterable v-model="configForm.postIds" clearable multiple style="width: 100%">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in postOptions"
|
v-for="item in postOptions"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
prop="userIds"
|
prop="userIds"
|
||||||
span="24"
|
span="24"
|
||||||
>
|
>
|
||||||
<el-select v-model="configForm.userIds" clearable multiple style="width: 100%">
|
<el-select filterable v-model="configForm.userIds" clearable multiple style="width: 100%">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in userOptions"
|
v-for="item in userOptions"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
label="指定用户组"
|
label="指定用户组"
|
||||||
prop="userGroups"
|
prop="userGroups"
|
||||||
>
|
>
|
||||||
<el-select v-model="configForm.userGroups" clearable multiple style="width: 100%">
|
<el-select filterable v-model="configForm.userGroups" clearable multiple style="width: 100%">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in userGroupOptions"
|
v-for="item in userGroupOptions"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@@ -142,13 +142,13 @@
|
|||||||
label="表单内用户字段"
|
label="表单内用户字段"
|
||||||
prop="formUser"
|
prop="formUser"
|
||||||
>
|
>
|
||||||
<el-select v-model="configForm.formUser" clearable style="width: 100%">
|
<el-select filterable v-model="configForm.formUser" clearable style="width: 100%">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="(item, idx) in userFieldOnFormOptions"
|
v-for="(item, idx) in userFieldOnFormOptions"
|
||||||
:key="idx"
|
:key="idx"
|
||||||
:label="item.title"
|
:label="item.title"
|
||||||
:value="item.field"
|
:value="item.field"
|
||||||
:disabled ="!item.required"
|
:disabled="!item.required"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -157,13 +157,13 @@
|
|||||||
label="表单内部门字段"
|
label="表单内部门字段"
|
||||||
prop="formDept"
|
prop="formDept"
|
||||||
>
|
>
|
||||||
<el-select v-model="configForm.formDept" clearable style="width: 100%">
|
<el-select filterable v-model="configForm.formDept" clearable style="width: 100%">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="(item, idx) in deptFieldOnFormOptions"
|
v-for="(item, idx) in deptFieldOnFormOptions"
|
||||||
:key="idx"
|
:key="idx"
|
||||||
:label="item.title"
|
:label="item.title"
|
||||||
:value="item.field"
|
:value="item.field"
|
||||||
:disabled ="!item.required"
|
:disabled="!item.required"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -179,7 +179,7 @@
|
|||||||
prop="deptLevel"
|
prop="deptLevel"
|
||||||
span="24"
|
span="24"
|
||||||
>
|
>
|
||||||
<el-select v-model="configForm.deptLevel" clearable>
|
<el-select filterable v-model="configForm.deptLevel" clearable>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="(item, index) in MULTI_LEVEL_DEPT"
|
v-for="(item, index) in MULTI_LEVEL_DEPT"
|
||||||
:key="index"
|
:key="index"
|
||||||
@@ -245,7 +245,7 @@
|
|||||||
label="驳回节点"
|
label="驳回节点"
|
||||||
prop="returnNodeId"
|
prop="returnNodeId"
|
||||||
>
|
>
|
||||||
<el-select v-model="configForm.returnNodeId" clearable style="width: 100%">
|
<el-select filterable v-model="configForm.returnNodeId" clearable style="width: 100%">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in returnTaskList"
|
v-for="item in returnTaskList"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@@ -293,6 +293,7 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-select
|
<el-select
|
||||||
|
filterable
|
||||||
v-model="timeUnit"
|
v-model="timeUnit"
|
||||||
class="mr-2"
|
class="mr-2"
|
||||||
:style="{ width: '100px' }"
|
:style="{ width: '100px' }"
|
||||||
@@ -332,6 +333,7 @@
|
|||||||
span="24"
|
span="24"
|
||||||
>
|
>
|
||||||
<el-select
|
<el-select
|
||||||
|
filterable
|
||||||
v-model="configForm.assignEmptyHandlerUserIds"
|
v-model="configForm.assignEmptyHandlerUserIds"
|
||||||
clearable
|
clearable
|
||||||
multiple
|
multiple
|
||||||
@@ -356,6 +358,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-divider content-position="left">是否需要签名</el-divider>
|
||||||
|
<el-form-item prop="signEnable">
|
||||||
|
<el-switch v-model="configForm.signEnable" active-text="是" inactive-text="否" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<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>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
@@ -435,6 +447,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="监听器" name="listener">
|
||||||
|
<UserTaskListener ref="userTaskListenerRef" v-model="configForm" :form-field-options="formFieldOptions" />
|
||||||
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-divider />
|
<el-divider />
|
||||||
@@ -484,6 +499,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'
|
||||||
})
|
})
|
||||||
@@ -609,9 +625,11 @@ const {
|
|||||||
cTimeoutMaxRemindCount
|
cTimeoutMaxRemindCount
|
||||||
} = useTimeoutHandler()
|
} = useTimeoutHandler()
|
||||||
|
|
||||||
|
const userTaskListenerRef = ref()
|
||||||
|
|
||||||
// 保存配置
|
// 保存配置
|
||||||
const saveConfig = async () => {
|
const saveConfig = async () => {
|
||||||
activeTabName.value = 'user'
|
// activeTabName.value = 'user'
|
||||||
// 设置审批节点名称
|
// 设置审批节点名称
|
||||||
currentNode.value.name = nodeName.value!
|
currentNode.value.name = nodeName.value!
|
||||||
// 设置审批类型
|
// 设置审批类型
|
||||||
@@ -624,7 +642,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
|
||||||
@@ -663,6 +682,31 @@ const saveConfig = async () => {
|
|||||||
currentNode.value.fieldsPermission = fieldsPermissionConfig.value
|
currentNode.value.fieldsPermission = fieldsPermissionConfig.value
|
||||||
// 设置按钮权限
|
// 设置按钮权限
|
||||||
currentNode.value.buttonsSetting = buttonsSetting.value
|
currentNode.value.buttonsSetting = buttonsSetting.value
|
||||||
|
// 创建任务监听器
|
||||||
|
currentNode.value.taskCreateListener = {
|
||||||
|
enable: configForm.value.taskCreateListenerEnable ?? false,
|
||||||
|
path: configForm.value.taskCreateListenerPath,
|
||||||
|
header: configForm.value.taskCreateListener?.header,
|
||||||
|
body: configForm.value.taskCreateListener?.body
|
||||||
|
}
|
||||||
|
// 指派任务监听器
|
||||||
|
currentNode.value.taskAssignListener = {
|
||||||
|
enable: configForm.value.taskAssignListenerEnable ?? false,
|
||||||
|
path: configForm.value.taskAssignListenerPath,
|
||||||
|
header: configForm.value.taskAssignListener?.header,
|
||||||
|
body: configForm.value.taskAssignListener?.body
|
||||||
|
}
|
||||||
|
// 完成任务监听器
|
||||||
|
currentNode.value.taskCompleteListener = {
|
||||||
|
enable: configForm.value.taskCompleteListenerEnable ?? false,
|
||||||
|
path: configForm.value.taskCompleteListenerPath,
|
||||||
|
header: configForm.value.taskCompleteListener?.header,
|
||||||
|
body: configForm.value.taskCompleteListener?.body
|
||||||
|
}
|
||||||
|
// 签名
|
||||||
|
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
|
||||||
@@ -714,6 +758,32 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
|
|||||||
buttonsSetting.value = cloneDeep(node.buttonsSetting) || DEFAULT_BUTTON_SETTING
|
buttonsSetting.value = cloneDeep(node.buttonsSetting) || DEFAULT_BUTTON_SETTING
|
||||||
// 4. 表单字段权限配置
|
// 4. 表单字段权限配置
|
||||||
getNodeConfigFormFields(node.fieldsPermission)
|
getNodeConfigFormFields(node.fieldsPermission)
|
||||||
|
// 5. 监听器
|
||||||
|
// 5.1 创建任务
|
||||||
|
configForm.value.taskCreateListenerEnable = node.taskCreateListener?.enable
|
||||||
|
configForm.value.taskCreateListenerPath = node.taskCreateListener?.path
|
||||||
|
configForm.value.taskCreateListener = {
|
||||||
|
header: node.taskCreateListener?.header ?? [],
|
||||||
|
body: node.taskCreateListener?.body ?? []
|
||||||
|
}
|
||||||
|
// 5.2 指派任务
|
||||||
|
configForm.value.taskAssignListenerEnable = node.taskAssignListener?.enable
|
||||||
|
configForm.value.taskAssignListenerPath = node.taskAssignListener?.path
|
||||||
|
configForm.value.taskAssignListener = {
|
||||||
|
header: node.taskAssignListener?.header ?? [],
|
||||||
|
body: node.taskAssignListener?.body ?? []
|
||||||
|
}
|
||||||
|
// 5.3 完成任务
|
||||||
|
configForm.value.taskCompleteListenerEnable = node.taskCompleteListener?.enable
|
||||||
|
configForm.value.taskCompleteListenerPath = node.taskCompleteListener?.path
|
||||||
|
configForm.value.taskCompleteListener = {
|
||||||
|
header: node.taskCompleteListener?.header ?? [],
|
||||||
|
body: node.taskCompleteListener?.body ?? []
|
||||||
|
}
|
||||||
|
// 6. 签名
|
||||||
|
configForm.value.signEnable = node?.signEnable ?? false
|
||||||
|
// 7. 审批意见
|
||||||
|
configForm.value.reasonRequire = node?.reasonRequire ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({ openDrawer, showUserTaskNodeConfig }) // 暴露方法给父组件
|
defineExpose({ openDrawer, showUserTaskNodeConfig }) // 暴露方法给父组件
|
||||||
|
|||||||
@@ -0,0 +1,264 @@
|
|||||||
|
<template>
|
||||||
|
<el-form ref="formRef" :model="condition" :rules="formRules" label-position="top">
|
||||||
|
<el-form-item label="配置方式" prop="conditionType">
|
||||||
|
<el-radio-group v-model="condition.conditionType" @change="changeConditionType">
|
||||||
|
<el-radio
|
||||||
|
v-for="(dict, indexConditionType) in conditionConfigTypes"
|
||||||
|
:key="indexConditionType"
|
||||||
|
:value="dict.value"
|
||||||
|
:label="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="condition.conditionType === ConditionType.RULE && condition.conditionGroups" label="条件规则">
|
||||||
|
<div class="condition-group-tool">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="mr-4">条件组关系</div>
|
||||||
|
<el-switch
|
||||||
|
v-model="condition.conditionGroups.and"
|
||||||
|
inline-prompt
|
||||||
|
active-text="且"
|
||||||
|
inactive-text="或"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-space direction="vertical" :spacer="condition.conditionGroups.and ? '且' : '或'">
|
||||||
|
<el-card
|
||||||
|
class="condition-group"
|
||||||
|
style="width: 530px"
|
||||||
|
v-for="(equation, cIdx) in condition.conditionGroups.conditions"
|
||||||
|
:key="cIdx"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="condition-group-delete"
|
||||||
|
v-if="condition.conditionGroups.conditions.length > 1"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
color="#0089ff"
|
||||||
|
icon="ep:circle-close-filled"
|
||||||
|
:size="18"
|
||||||
|
@click="deleteConditionGroup(condition.conditionGroups.conditions, 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="equation.and"
|
||||||
|
inline-prompt
|
||||||
|
active-text="且"
|
||||||
|
inactive-text="或"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="flex pt-2" v-for="(rule, rIdx) in equation.rules" :key="rIdx">
|
||||||
|
<div class="mr-2">
|
||||||
|
<el-form-item
|
||||||
|
:prop="`conditionGroups.conditions.${cIdx}.rules.${rIdx}.leftSide`"
|
||||||
|
:rules="{
|
||||||
|
required: true,
|
||||||
|
message: '左值不能为空',
|
||||||
|
trigger: 'change'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<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 class="mr-2">
|
||||||
|
<el-select v-model="rule.opCode" style="width: 100px">
|
||||||
|
<el-option
|
||||||
|
v-for="operator in COMPARISON_OPERATORS"
|
||||||
|
:key="operator.value"
|
||||||
|
:label="operator.label"
|
||||||
|
:value="operator.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="mr-2">
|
||||||
|
<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 class="mr-1 flex items-center" v-if="equation.rules.length > 1">
|
||||||
|
<Icon icon="ep:delete" :size="18" @click="deleteConditionRule(equation, rIdx)" />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<Icon icon="ep:plus" :size="18" @click="addConditionRule(equation, rIdx)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-space>
|
||||||
|
<div title="添加条件组" class="mt-4 cursor-pointer">
|
||||||
|
<Icon
|
||||||
|
color="#0089ff"
|
||||||
|
icon="ep:plus"
|
||||||
|
:size="24"
|
||||||
|
@click="addConditionGroup(condition.conditionGroups?.conditions)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
COMPARISON_OPERATORS,
|
||||||
|
CONDITION_CONFIG_TYPES,
|
||||||
|
ConditionType,
|
||||||
|
DEFAULT_CONDITION_GROUP_VALUE
|
||||||
|
} from '../../consts'
|
||||||
|
import { BpmModelFormType } from '@/utils/constants'
|
||||||
|
import { useFormFieldsAndStartUser } from '../../node'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const condition = computed({
|
||||||
|
get() {
|
||||||
|
return props.modelValue
|
||||||
|
},
|
||||||
|
set(newValue) {
|
||||||
|
emit('update:modelValue', newValue)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
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 fieldOptions = useFormFieldsAndStartUser()
|
||||||
|
|
||||||
|
// 表单校验规则
|
||||||
|
const formRules = reactive({
|
||||||
|
conditionType: [{ required: true, message: '配置方式不能为空', trigger: 'blur' }],
|
||||||
|
conditionExpression: [{ required: true, message: '条件表达式不能为空', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
|
||||||
|
/** 切换条件配置方式 */
|
||||||
|
const changeConditionType = () => {
|
||||||
|
if (condition.value.conditionType === ConditionType.RULE) {
|
||||||
|
if (!condition.value.conditionGroups) {
|
||||||
|
condition.value.conditionGroups = DEFAULT_CONDITION_GROUP_VALUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const deleteConditionGroup = (conditions, index) => {
|
||||||
|
conditions.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteConditionRule = (condition, index) => {
|
||||||
|
condition.rules.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addConditionRule = (condition, index) => {
|
||||||
|
const rule = {
|
||||||
|
opCode: '==',
|
||||||
|
leftSide: '',
|
||||||
|
rightSide: ''
|
||||||
|
}
|
||||||
|
condition.rules.splice(index + 1, 0, rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addConditionGroup = (conditions) => {
|
||||||
|
const condition = {
|
||||||
|
and: true,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
opCode: '==',
|
||||||
|
leftSide: '',
|
||||||
|
rightSide: ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
conditions.push(condition)
|
||||||
|
}
|
||||||
|
|
||||||
|
const validate = async () => {
|
||||||
|
if (!formRef) return false
|
||||||
|
return await formRef.value.validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ validate })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.condition-group-tool {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 500px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition-group {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #0089ff;
|
||||||
|
|
||||||
|
.condition-group-delete {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition-group-delete {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.el-card__header) {
|
||||||
|
padding: 8px var(--el-card-padding);
|
||||||
|
border-bottom: 1px solid var(--el-card-border-color);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+184
@@ -0,0 +1,184 @@
|
|||||||
|
<template>
|
||||||
|
<el-form-item label="请求头">
|
||||||
|
<div class="flex pt-2" v-for="(item, index) in props.header" :key="index">
|
||||||
|
<div class="mr-2">
|
||||||
|
<el-form-item
|
||||||
|
:prop="`${bind}.header.${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 BPM_HTTP_REQUEST_PARAM_TYPES"
|
||||||
|
:key="types.value"
|
||||||
|
:label="types.label"
|
||||||
|
:value="types.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="mr-2">
|
||||||
|
<el-form-item
|
||||||
|
:prop="`${bind}.header.${index}.value`"
|
||||||
|
:rules="{
|
||||||
|
required: true,
|
||||||
|
message: '参数值不能为空',
|
||||||
|
trigger: 'blur'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-if="item.type === BpmHttpRequestParamTypeEnum.FIXED_VALUE"
|
||||||
|
class="w-160px"
|
||||||
|
v-model="item.value"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:prop="`${bind}.header.${index}.value`"
|
||||||
|
:rules="{
|
||||||
|
required: true,
|
||||||
|
message: '参数值不能为空',
|
||||||
|
trigger: 'change'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-select
|
||||||
|
v-if="item.type === BpmHttpRequestParamTypeEnum.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="deleteHttpRequestParam(props.header, index)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-button type="primary" text @click="addHttpRequestParam(props.header)">
|
||||||
|
<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 props.body" :key="index">
|
||||||
|
<div class="mr-2">
|
||||||
|
<el-form-item
|
||||||
|
:prop="`${bind}.body.${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 BPM_HTTP_REQUEST_PARAM_TYPES"
|
||||||
|
:key="types.value"
|
||||||
|
:label="types.label"
|
||||||
|
:value="types.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="mr-2">
|
||||||
|
<el-form-item
|
||||||
|
:prop="`${bind}.body.${index}.value`"
|
||||||
|
:rules="{
|
||||||
|
required: true,
|
||||||
|
message: '参数值不能为空',
|
||||||
|
trigger: 'blur'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-if="item.type === BpmHttpRequestParamTypeEnum.FIXED_VALUE"
|
||||||
|
class="w-160px"
|
||||||
|
v-model="item.value"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:prop="`${bind}.body.${index}.value`"
|
||||||
|
:rules="{
|
||||||
|
required: true,
|
||||||
|
message: '参数值不能为空',
|
||||||
|
trigger: 'change'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-select
|
||||||
|
v-if="item.type === BpmHttpRequestParamTypeEnum.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="deleteHttpRequestParam(props.body, index)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-button type="primary" text @click="addHttpRequestParam(props.body)">
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" />添加一行
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { HttpRequestParam, BPM_HTTP_REQUEST_PARAM_TYPES, BpmHttpRequestParamTypeEnum } from '../../consts'
|
||||||
|
import { useFormFieldsAndStartUser } from '../../node'
|
||||||
|
defineOptions({
|
||||||
|
name: 'HttpRequestParamSetting'
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
header: {
|
||||||
|
type: Array as () => HttpRequestParam[],
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
type: Array as () => HttpRequestParam[],
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
bind: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 流程表单字段,发起人字段
|
||||||
|
const formFieldOptions = useFormFieldsAndStartUser()
|
||||||
|
/** 添加请求配置项 */
|
||||||
|
const addHttpRequestParam = (arr: HttpRequestParam[]) => {
|
||||||
|
arr.push({
|
||||||
|
key: '',
|
||||||
|
type: BpmHttpRequestParamTypeEnum.FIXED_VALUE,
|
||||||
|
value: ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除请求配置项 */
|
||||||
|
const deleteHttpRequestParam = (arr: HttpRequestParam[], index: number) => {
|
||||||
|
arr.splice(index, 1)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
<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>
|
||||||
|
<HttpRequestParamSetting
|
||||||
|
:header="configForm[`task${listener.type}Listener`].header"
|
||||||
|
:body="configForm[`task${listener.type}Listener`].body"
|
||||||
|
:bind="`task${listener.type}Listener`"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import HttpRequestParamSetting from './HttpRequestParamSetting.vue'
|
||||||
|
|
||||||
|
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 validate = async () => {
|
||||||
|
if (!listenerFormRef) return false
|
||||||
|
return await listenerFormRef.value.validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ validate })
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
<template>
|
||||||
|
<div class="node-wrapper">
|
||||||
|
<div class="node-container">
|
||||||
|
<div
|
||||||
|
class="node-box"
|
||||||
|
:class="[
|
||||||
|
{ 'node-config-error': !currentNode.showText },
|
||||||
|
`${useTaskStatusClass(currentNode?.activityStatus)}`
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<div class="node-title-container">
|
||||||
|
<div class="node-title-icon delay-node"><span class="iconfont icon-delay"></span></div>
|
||||||
|
<input
|
||||||
|
v-if="!readonly && showInput"
|
||||||
|
type="text"
|
||||||
|
class="editable-title-input"
|
||||||
|
@blur="blurEvent()"
|
||||||
|
v-mountedFocus
|
||||||
|
v-model="currentNode.name"
|
||||||
|
:placeholder="currentNode.name"
|
||||||
|
/>
|
||||||
|
<div v-else class="node-title" @click="clickTitle">
|
||||||
|
{{ currentNode.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="node-content" @click="openNodeConfig">
|
||||||
|
<div class="node-text" :title="currentNode.showText" v-if="currentNode.showText">
|
||||||
|
{{ currentNode.showText }}
|
||||||
|
</div>
|
||||||
|
<div class="node-text" v-else>
|
||||||
|
{{ NODE_DEFAULT_TEXT.get(NodeType.DELAY_TIMER_NODE) }}
|
||||||
|
</div>
|
||||||
|
<Icon v-if="!readonly" icon="ep:arrow-right-bold" />
|
||||||
|
</div>
|
||||||
|
<div v-if="!readonly" class="node-toolbar">
|
||||||
|
<div class="toolbar-icon"
|
||||||
|
><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
|
||||||
|
/></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
|
||||||
|
<NodeHandler
|
||||||
|
v-if="currentNode"
|
||||||
|
v-model:child-node="currentNode.childNode"
|
||||||
|
:current-node="currentNode"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<DelayTimerNodeConfig
|
||||||
|
v-if="!readonly && currentNode"
|
||||||
|
ref="nodeSetting"
|
||||||
|
:flow-node="currentNode"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||||
|
import NodeHandler from '../NodeHandler.vue'
|
||||||
|
import { useNodeName2, useWatchNode, useTaskStatusClass } from '../node'
|
||||||
|
import DelayTimerNodeConfig from '../nodes-config/DelayTimerNodeConfig.vue'
|
||||||
|
defineOptions({
|
||||||
|
name: 'DelayTimerNode'
|
||||||
|
})
|
||||||
|
const props = defineProps({
|
||||||
|
flowNode: {
|
||||||
|
type: Object as () => SimpleFlowNode,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 定义事件,更新父组件。
|
||||||
|
const emits = defineEmits<{
|
||||||
|
'update:flowNode': [node: SimpleFlowNode | undefined]
|
||||||
|
}>()
|
||||||
|
// 是否只读
|
||||||
|
const readonly = inject<Boolean>('readonly')
|
||||||
|
// 监控节点的变化
|
||||||
|
const currentNode = useWatchNode(props)
|
||||||
|
// 节点名称编辑
|
||||||
|
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.DELAY_TIMER_NODE)
|
||||||
|
|
||||||
|
const nodeSetting = ref()
|
||||||
|
// 打开节点配置
|
||||||
|
const openNodeConfig = () => {
|
||||||
|
if (readonly) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nodeSetting.value.showDelayTimerNodeConfig(currentNode.value)
|
||||||
|
nodeSetting.value.openDrawer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除节点。更新当前节点为孩子节点
|
||||||
|
const deleteNode = () => {
|
||||||
|
emits('update:flowNode', currentNode.value.childNode)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
@@ -77,7 +77,7 @@ const props = defineProps({
|
|||||||
const currentNode = useWatchNode(props)
|
const currentNode = useWatchNode(props)
|
||||||
// 是否只读
|
// 是否只读
|
||||||
const readonly = inject<Boolean>('readonly')
|
const readonly = inject<Boolean>('readonly')
|
||||||
const processInstance = inject<Ref<any>>('processInstance')
|
const processInstance = inject<Ref<any>>('processInstance', ref({}))
|
||||||
// 审批信息的弹窗显示,用于只读模式
|
// 审批信息的弹窗显示,用于只读模式
|
||||||
const dialogVisible = ref(false) // 弹窗可见性
|
const dialogVisible = ref(false) // 弹窗可见性
|
||||||
const processInstanceInfos = ref<any[]>([]) // 流程的审批信息
|
const processInstanceInfos = ref<any[]>([]) // 流程的审批信息
|
||||||
|
|||||||
@@ -108,7 +108,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import NodeHandler from '../NodeHandler.vue'
|
import NodeHandler from '../NodeHandler.vue'
|
||||||
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
||||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
import { SimpleFlowNode, NodeType, ConditionType, DEFAULT_CONDITION_GROUP_VALUE, NODE_DEFAULT_TEXT } from '../consts'
|
||||||
import { getDefaultConditionNodeName } from '../utils'
|
import { getDefaultConditionNodeName } from '../utils'
|
||||||
import { useTaskStatusClass } from '../node'
|
import { useTaskStatusClass } from '../node'
|
||||||
import { generateUUID } from '@/utils'
|
import { generateUUID } from '@/utils'
|
||||||
@@ -149,7 +149,7 @@ const blurEvent = (index: number) => {
|
|||||||
showInputs.value[index] = false
|
showInputs.value[index] = false
|
||||||
const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
|
const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
|
||||||
conditionNode.name =
|
conditionNode.name =
|
||||||
conditionNode.name || getDefaultConditionNodeName(index, conditionNode.defaultFlow)
|
conditionNode.name || getDefaultConditionNodeName(index, conditionNode.conditionSetting?.defaultFlow)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点击条件名称
|
// 点击条件名称
|
||||||
@@ -178,8 +178,11 @@ const addCondition = () => {
|
|||||||
type: NodeType.CONDITION_NODE,
|
type: NodeType.CONDITION_NODE,
|
||||||
childNode: undefined,
|
childNode: undefined,
|
||||||
conditionNodes: [],
|
conditionNodes: [],
|
||||||
conditionType: 1,
|
conditionSetting: {
|
||||||
defaultFlow: false
|
defaultFlow: false,
|
||||||
|
conditionType: ConditionType.RULE,
|
||||||
|
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
conditionNodes.splice(lastIndex, 0, conditionData)
|
conditionNodes.splice(lastIndex, 0, conditionData)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<div class="branch-node-title-container">
|
<div class="branch-node-title-container">
|
||||||
<div v-if="showInputs[index]">
|
<div v-if="!readonly && showInputs[index]">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="editable-title-input"
|
class="editable-title-input"
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import NodeHandler from '../NodeHandler.vue'
|
import NodeHandler from '../NodeHandler.vue'
|
||||||
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
||||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
import { SimpleFlowNode, NodeType, ConditionType, DEFAULT_CONDITION_GROUP_VALUE, NODE_DEFAULT_TEXT } from '../consts'
|
||||||
import { useTaskStatusClass } from '../node'
|
import { useTaskStatusClass } from '../node'
|
||||||
import { getDefaultInclusiveConditionNodeName } from '../utils'
|
import { getDefaultInclusiveConditionNodeName } from '../utils'
|
||||||
import { generateUUID } from '@/utils'
|
import { generateUUID } from '@/utils'
|
||||||
@@ -153,7 +153,7 @@ const blurEvent = (index: number) => {
|
|||||||
showInputs.value[index] = false
|
showInputs.value[index] = false
|
||||||
const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
|
const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
|
||||||
conditionNode.name =
|
conditionNode.name =
|
||||||
conditionNode.name || getDefaultInclusiveConditionNodeName(index, conditionNode.defaultFlow)
|
conditionNode.name || getDefaultInclusiveConditionNodeName(index, conditionNode.conditionSetting?.defaultFlow)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点击条件名称
|
// 点击条件名称
|
||||||
@@ -182,8 +182,11 @@ const addCondition = () => {
|
|||||||
type: NodeType.CONDITION_NODE,
|
type: NodeType.CONDITION_NODE,
|
||||||
childNode: undefined,
|
childNode: undefined,
|
||||||
conditionNodes: [],
|
conditionNodes: [],
|
||||||
conditionType: 1,
|
conditionSetting: {
|
||||||
defaultFlow: false
|
defaultFlow: false,
|
||||||
|
conditionType: ConditionType.RULE,
|
||||||
|
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
conditionNodes.splice(lastIndex, 0, conditionData)
|
conditionNodes.splice(lastIndex, 0, conditionData)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
<template>
|
||||||
|
<div class="node-wrapper">
|
||||||
|
<div class="node-container">
|
||||||
|
<div
|
||||||
|
class="node-box"
|
||||||
|
:class="[
|
||||||
|
{ 'node-config-error': !currentNode.showText },
|
||||||
|
`${useTaskStatusClass(currentNode?.activityStatus)}`
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<div class="node-title-container">
|
||||||
|
<div class="node-title-icon router-node">
|
||||||
|
<span class="iconfont icon-router"></span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
v-if="!readonly && showInput"
|
||||||
|
type="text"
|
||||||
|
class="editable-title-input"
|
||||||
|
@blur="blurEvent()"
|
||||||
|
v-mountedFocus
|
||||||
|
v-model="currentNode.name"
|
||||||
|
:placeholder="currentNode.name"
|
||||||
|
/>
|
||||||
|
<div v-else class="node-title" @click="clickTitle">
|
||||||
|
{{ currentNode.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="node-content" @click="openNodeConfig">
|
||||||
|
<div class="node-text" :title="currentNode.showText" v-if="currentNode.showText">
|
||||||
|
{{ currentNode.showText }}
|
||||||
|
</div>
|
||||||
|
<div class="node-text" v-else>
|
||||||
|
{{ NODE_DEFAULT_TEXT.get(NodeType.ROUTER_BRANCH_NODE) }}
|
||||||
|
</div>
|
||||||
|
<Icon v-if="!readonly" icon="ep:arrow-right-bold" />
|
||||||
|
</div>
|
||||||
|
<div v-if="!readonly" class="node-toolbar">
|
||||||
|
<div class="toolbar-icon"
|
||||||
|
><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
|
||||||
|
/></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
|
||||||
|
<NodeHandler
|
||||||
|
v-if="currentNode"
|
||||||
|
v-model:child-node="currentNode.childNode"
|
||||||
|
:current-node="currentNode"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<RouterNodeConfig v-if="!readonly && currentNode" ref="nodeSetting" :flow-node="currentNode" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||||
|
import NodeHandler from '../NodeHandler.vue'
|
||||||
|
import { useNodeName2, useWatchNode, useTaskStatusClass } from '../node'
|
||||||
|
import RouterNodeConfig from '../nodes-config/RouterNodeConfig.vue'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'RouterNode'
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
flowNode: {
|
||||||
|
type: Object as () => SimpleFlowNode,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 定义事件,更新父组件
|
||||||
|
const emits = defineEmits<{
|
||||||
|
'update:flowNode': [node: SimpleFlowNode | undefined]
|
||||||
|
}>()
|
||||||
|
// 是否只读
|
||||||
|
const readonly = inject<Boolean>('readonly')
|
||||||
|
// 监控节点的变化
|
||||||
|
const currentNode = useWatchNode(props)
|
||||||
|
// 节点名称编辑
|
||||||
|
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.ROUTER_BRANCH_NODE)
|
||||||
|
|
||||||
|
const nodeSetting = ref()
|
||||||
|
// 打开节点配置
|
||||||
|
const openNodeConfig = () => {
|
||||||
|
if (readonly) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nodeSetting.value.showRouteNodeConfig(currentNode.value)
|
||||||
|
nodeSetting.value.openDrawer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除节点。更新当前节点为孩子节点
|
||||||
|
const deleteNode = () => {
|
||||||
|
emits('update:flowNode', currentNode.value.childNode)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
><span class="iconfont icon-start-user"></span
|
><span class="iconfont icon-start-user"></span
|
||||||
></div>
|
></div>
|
||||||
<input
|
<input
|
||||||
v-if="showInput"
|
v-if="!readonly && showInput"
|
||||||
type="text"
|
type="text"
|
||||||
class="editable-title-input"
|
class="editable-title-input"
|
||||||
@blur="blurEvent()"
|
@blur="blurEvent()"
|
||||||
@@ -117,7 +117,7 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
const readonly = inject<Boolean>('readonly') // 是否只读
|
const readonly = inject<Boolean>('readonly') // 是否只读
|
||||||
const tasks = inject<Ref<any[]>>('tasks')
|
const tasks = inject<Ref<any[]>>('tasks', ref([]))
|
||||||
// 定义事件,更新父组件。
|
// 定义事件,更新父组件。
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
'update:modelValue': [node: SimpleFlowNode | undefined]
|
'update:modelValue': [node: SimpleFlowNode | undefined]
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
<template>
|
||||||
|
<div class="node-wrapper">
|
||||||
|
<div class="node-container">
|
||||||
|
<div
|
||||||
|
class="node-box"
|
||||||
|
:class="[
|
||||||
|
{ 'node-config-error': !currentNode.showText },
|
||||||
|
`${useTaskStatusClass(currentNode?.activityStatus)}`
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<div class="node-title-container">
|
||||||
|
<div class="node-title-icon trigger-node">
|
||||||
|
<span class="iconfont icon-trigger"></span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
v-if="!readonly && showInput"
|
||||||
|
type="text"
|
||||||
|
class="editable-title-input"
|
||||||
|
@blur="blurEvent()"
|
||||||
|
v-mountedFocus
|
||||||
|
v-model="currentNode.name"
|
||||||
|
:placeholder="currentNode.name"
|
||||||
|
/>
|
||||||
|
<div v-else class="node-title" @click="clickTitle">
|
||||||
|
{{ currentNode.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="node-content" @click="openNodeConfig">
|
||||||
|
<div class="node-text" :title="currentNode.showText" v-if="currentNode.showText">
|
||||||
|
{{ currentNode.showText }}
|
||||||
|
</div>
|
||||||
|
<div class="node-text" v-else>
|
||||||
|
{{ NODE_DEFAULT_TEXT.get(NodeType.TRIGGER_NODE) }}
|
||||||
|
</div>
|
||||||
|
<Icon v-if="!readonly" icon="ep:arrow-right-bold" />
|
||||||
|
</div>
|
||||||
|
<div v-if="!readonly" class="node-toolbar">
|
||||||
|
<div class="toolbar-icon"
|
||||||
|
><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
|
||||||
|
/></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
|
||||||
|
<NodeHandler
|
||||||
|
v-if="currentNode"
|
||||||
|
v-model:child-node="currentNode.childNode"
|
||||||
|
:current-node="currentNode"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<TriggerNodeConfig v-if="!readonly && currentNode" ref="nodeSetting" :flow-node="currentNode" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||||
|
import NodeHandler from '../NodeHandler.vue'
|
||||||
|
import { useNodeName2, useWatchNode, useTaskStatusClass } from '../node'
|
||||||
|
import TriggerNodeConfig from '../nodes-config/TriggerNodeConfig.vue'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'TriggerNode'
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
flowNode: {
|
||||||
|
type: Object as () => SimpleFlowNode,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 定义事件,更新父组件
|
||||||
|
const emits = defineEmits<{
|
||||||
|
'update:flowNode': [node: SimpleFlowNode | undefined]
|
||||||
|
}>()
|
||||||
|
// 是否只读
|
||||||
|
const readonly = inject<Boolean>('readonly')
|
||||||
|
// 监控节点的变化
|
||||||
|
const currentNode = useWatchNode(props)
|
||||||
|
// 节点名称编辑
|
||||||
|
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.TRIGGER_NODE)
|
||||||
|
|
||||||
|
const nodeSetting = ref()
|
||||||
|
// 打开节点配置
|
||||||
|
const openNodeConfig = () => {
|
||||||
|
if (readonly) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nodeSetting.value.showTriggerNodeConfig(currentNode.value)
|
||||||
|
nodeSetting.value.openDrawer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除节点。更新当前节点为孩子节点
|
||||||
|
const deleteNode = () => {
|
||||||
|
emits('update:flowNode', currentNode.value.childNode)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
@@ -131,7 +131,7 @@ const emits = defineEmits<{
|
|||||||
|
|
||||||
// 是否只读
|
// 是否只读
|
||||||
const readonly = inject<Boolean>('readonly')
|
const readonly = inject<Boolean>('readonly')
|
||||||
const tasks = inject<Ref<any[]>>('tasks')
|
const tasks = inject<Ref<any[]>>('tasks', ref([]))
|
||||||
// 监控节点变化
|
// 监控节点变化
|
||||||
const currentNode = useWatchNode(props)
|
const currentNode = useWatchNode(props)
|
||||||
// 节点名称编辑
|
// 节点名称编辑
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -113,18 +113,21 @@
|
|||||||
|
|
||||||
// 节点连线气泡卡片样式
|
// 节点连线气泡卡片样式
|
||||||
.handler-item-wrapper {
|
.handler-item-wrapper {
|
||||||
|
width: 320px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.handler-item {
|
.handler-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.handler-item-icon {
|
.handler-item-icon {
|
||||||
width: 60px;
|
width: 50px;
|
||||||
height: 60px;
|
height: 50px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #e2e2e2;
|
border: 1px solid #e2e2e2;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
@@ -138,13 +141,14 @@
|
|||||||
|
|
||||||
.icon-size {
|
.icon-size {
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
line-height: 60px;
|
line-height: 50px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.approve {
|
.approve {
|
||||||
color: #ff943e;
|
color: #ff943e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy {
|
.copy {
|
||||||
color: #3296fa;
|
color: #3296fa;
|
||||||
}
|
}
|
||||||
@@ -161,6 +165,18 @@
|
|||||||
color: #345da2;
|
color: #345da2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.delay {
|
||||||
|
color: #e47470;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger {
|
||||||
|
color: #3373d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.router {
|
||||||
|
color: #ca3a31
|
||||||
|
}
|
||||||
|
|
||||||
.handler-item-text {
|
.handler-item-text {
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
width: 80px;
|
width: 80px;
|
||||||
@@ -266,6 +282,18 @@
|
|||||||
&.start-user {
|
&.start-user {
|
||||||
color: #676565;
|
color: #676565;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.delay-node {
|
||||||
|
color: #e47470;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.trigger-node {
|
||||||
|
color: #3373d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.router-node {
|
||||||
|
color: #ca3a31
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-title {
|
.node-title {
|
||||||
@@ -711,45 +739,56 @@
|
|||||||
|
|
||||||
// iconfont 样式
|
// iconfont 样式
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'iconfont'; /* Project id 4495938 */
|
font-family: "iconfont"; /* Project id 4495938 */
|
||||||
src:
|
src: url('iconfont.woff2?t=1737639517142') format('woff2'),
|
||||||
url('iconfont.woff2?t=1724339470412') format('woff2'),
|
url('iconfont.woff?t=1737639517142') format('woff'),
|
||||||
url('iconfont.woff?t=1724339470412') format('woff'),
|
url('iconfont.ttf?t=1737639517142') format('truetype');
|
||||||
url('iconfont.ttf?t=1724339470412') format('truetype');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
font-family: 'iconfont' !important;
|
font-family: "iconfont" !important;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-trigger:before {
|
||||||
|
content: "\e6d3";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-router:before {
|
||||||
|
content: "\e6b2";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-delay:before {
|
||||||
|
content: "\e600";
|
||||||
|
}
|
||||||
|
|
||||||
.icon-start-user:before {
|
.icon-start-user:before {
|
||||||
content: '\e679';
|
content: "\e679";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-inclusive:before {
|
.icon-inclusive:before {
|
||||||
content: '\e602';
|
content: "\e602";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-copy:before {
|
.icon-copy:before {
|
||||||
content: '\e7eb';
|
content: "\e7eb";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-handle:before {
|
.icon-handle:before {
|
||||||
content: '\e61c';
|
content: "\e61c";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-exclusive:before {
|
.icon-exclusive:before {
|
||||||
content: '\e717';
|
content: "\e717";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-approve:before {
|
.icon-approve:before {
|
||||||
content: '\e715';
|
content: "\e715";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-parallel:before {
|
.icon-parallel:before {
|
||||||
content: '\e688';
|
content: "\e688";
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<!-- 列表选择通用组件,参考 ProductList 组件使用 -->
|
<!-- 列表选择通用组件,参考 ProductList 组件使用 -->
|
||||||
|
<!-- TODO 芋艿:可能会移除 -->
|
||||||
<template>
|
<template>
|
||||||
<Dialog v-model="dialogVisible" :appendToBody="true" :scroll="true" :title="title" width="60%">
|
<Dialog v-model="dialogVisible" :appendToBody="true" :scroll="true" :title="title" width="60%">
|
||||||
<el-table
|
<el-table
|
||||||
|
|||||||
@@ -308,28 +308,6 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 监听value变化,重新加载流程图
|
|
||||||
watch(
|
|
||||||
() => props.value,
|
|
||||||
(newValue) => {
|
|
||||||
if (newValue && bpmnModeler) {
|
|
||||||
createNewDiagram(newValue)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
// 监听processId和processName变化
|
|
||||||
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 ------------------------------------------------------ */
|
||||||
|
|||||||
+39
@@ -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": []
|
||||||
|
|||||||
@@ -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" -->
|
||||||
|
|||||||
@@ -152,6 +152,9 @@ watch(
|
|||||||
handleKeyUpdate(props.model.key)
|
handleKeyUpdate(props.model.key)
|
||||||
handleNameUpdate(props.model.name)
|
handleNameUpdate(props.model.name)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+33
-2
@@ -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), {
|
||||||
|
|||||||
+21
-12
@@ -1,6 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="panel-tab__content">
|
<div class="panel-tab__content">
|
||||||
<el-radio-group v-model="approveMethod" @change="onApproveMethodChange">
|
<el-radio-group
|
||||||
|
v-if="type === 'UserTask'"
|
||||||
|
v-model="approveMethod"
|
||||||
|
@change="onApproveMethodChange"
|
||||||
|
>
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div v-for="(item, index) in APPROVE_METHODS" :key="index">
|
<div v-for="(item, index) in APPROVE_METHODS" :key="index">
|
||||||
<el-radio :value="item.value" :label="item.value">
|
<el-radio :value="item.value" :label="item.value">
|
||||||
@@ -23,6 +27,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
|
<div v-else>
|
||||||
|
除了UserTask以外节点的多实例待实现
|
||||||
|
</div>
|
||||||
<!-- 与Simple设计器配置合并,保留以前的代码 -->
|
<!-- 与Simple设计器配置合并,保留以前的代码 -->
|
||||||
<el-form label-width="90px" style="display: none">
|
<el-form label-width="90px" style="display: none">
|
||||||
<el-form-item label="快捷配置">
|
<el-form-item label="快捷配置">
|
||||||
@@ -301,19 +308,21 @@ const approveMethod = ref()
|
|||||||
const approveRatio = ref(100)
|
const approveRatio = ref(100)
|
||||||
const otherExtensions = ref()
|
const otherExtensions = ref()
|
||||||
const getElementLoopNew = () => {
|
const getElementLoopNew = () => {
|
||||||
const extensionElements =
|
if (props.type === 'UserTask') {
|
||||||
bpmnElement.value.businessObject?.extensionElements ??
|
const extensionElements =
|
||||||
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
|
bpmnElement.value.businessObject?.extensionElements ??
|
||||||
approveMethod.value = extensionElements.values.filter(
|
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
|
||||||
(ex) => ex.$type === `${prefix}:ApproveMethod`
|
approveMethod.value = extensionElements.values.filter(
|
||||||
)?.[0]?.value
|
(ex) => ex.$type === `${prefix}:ApproveMethod`
|
||||||
|
)?.[0]?.value
|
||||||
|
|
||||||
otherExtensions.value =
|
otherExtensions.value =
|
||||||
extensionElements.values.filter((ex) => ex.$type !== `${prefix}:ApproveMethod`) ?? []
|
extensionElements.values.filter((ex) => ex.$type !== `${prefix}:ApproveMethod`) ?? []
|
||||||
|
|
||||||
if (!approveMethod.value) {
|
if (!approveMethod.value) {
|
||||||
approveMethod.value = ApproveMethodType.SEQUENTIAL_APPROVE
|
approveMethod.value = ApproveMethodType.SEQUENTIAL_APPROVE
|
||||||
updateLoopCharacteristics()
|
updateLoopCharacteristics()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const onApproveMethodChange = () => {
|
const onApproveMethodChange = () => {
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,15 @@
|
|||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
|
/** 判断权限的指令 directive */
|
||||||
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 +19,13 @@ export function hasPermi(app: App<Element>) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 判断权限的方法 function */
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const all_permission = '*:*:*'
|
||||||
|
export const hasPermission = (permission: string[]) => {
|
||||||
|
return (
|
||||||
|
userStore.permissions.has(all_permission) ||
|
||||||
|
permission.some((permission) => userStore.permissions.has(permission))
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -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()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|||||||
+4
-1
@@ -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',
|
||||||
|
|||||||
@@ -141,7 +141,10 @@ export default {
|
|||||||
btnQRCode: '二维码登录',
|
btnQRCode: '二维码登录',
|
||||||
qrcode: '扫描二维码登录',
|
qrcode: '扫描二维码登录',
|
||||||
btnRegister: '注册',
|
btnRegister: '注册',
|
||||||
SmsSendMsg: '验证码已发送'
|
SmsSendMsg: '验证码已发送',
|
||||||
|
resetPassword: "重置密码",
|
||||||
|
resetPasswordSuccess: "重置密码成功",
|
||||||
|
invalidTenantName: "无效的租户名称"
|
||||||
},
|
},
|
||||||
captcha: {
|
captcha: {
|
||||||
verification: '请完成安全验证',
|
verification: '请完成安全验证',
|
||||||
|
|||||||
@@ -307,6 +307,18 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
activityId: route.query.activityId
|
activityId: route.query.activityId
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'process-instance/report',
|
||||||
|
component: () => import('@/views/bpm/processInstance/report/index.vue'),
|
||||||
|
name: 'BpmProcessInstanceReport',
|
||||||
|
meta: {
|
||||||
|
noCache: true,
|
||||||
|
hidden: true,
|
||||||
|
canTo: true,
|
||||||
|
title: '数据报表',
|
||||||
|
activeMenu: '/bpm/manager/model'
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'oa/leave/create',
|
path: 'oa/leave/create',
|
||||||
component: () => import('@/views/bpm/oa/leave/create.vue'),
|
component: () => import('@/views/bpm/oa/leave/create.vue'),
|
||||||
@@ -344,7 +356,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'manager/model/update/:id',
|
// TODO @zws:1)建议,在加一个路由。然后标题是“复制流程”,这样体验会好点;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: {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ interface UserVO {
|
|||||||
|
|
||||||
interface UserInfoVO {
|
interface UserInfoVO {
|
||||||
// USER 缓存
|
// USER 缓存
|
||||||
permissions: string[]
|
permissions: Set<string>
|
||||||
roles: string[]
|
roles: string[]
|
||||||
isSetUser: boolean
|
isSetUser: boolean
|
||||||
user: UserVO
|
user: UserVO
|
||||||
@@ -23,7 +23,7 @@ interface UserInfoVO {
|
|||||||
|
|
||||||
export const useUserStore = defineStore('admin-user', {
|
export const useUserStore = defineStore('admin-user', {
|
||||||
state: (): UserInfoVO => ({
|
state: (): UserInfoVO => ({
|
||||||
permissions: [],
|
permissions: new Set<string>(),
|
||||||
roles: [],
|
roles: [],
|
||||||
isSetUser: false,
|
isSetUser: false,
|
||||||
user: {
|
user: {
|
||||||
@@ -34,7 +34,7 @@ export const useUserStore = defineStore('admin-user', {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
getPermissions(): string[] {
|
getPermissions(): Set<string> {
|
||||||
return this.permissions
|
return this.permissions
|
||||||
},
|
},
|
||||||
getRoles(): string[] {
|
getRoles(): string[] {
|
||||||
@@ -57,7 +57,7 @@ export const useUserStore = defineStore('admin-user', {
|
|||||||
if (!userInfo) {
|
if (!userInfo) {
|
||||||
userInfo = await getInfo()
|
userInfo = await getInfo()
|
||||||
}
|
}
|
||||||
this.permissions = userInfo.permissions
|
this.permissions = new Set(userInfo.permissions)
|
||||||
this.roles = userInfo.roles
|
this.roles = userInfo.roles
|
||||||
this.user = userInfo.user
|
this.user = userInfo.user
|
||||||
this.isSetUser = true
|
this.isSetUser = true
|
||||||
@@ -85,7 +85,7 @@ export const useUserStore = defineStore('admin-user', {
|
|||||||
this.resetState()
|
this.resetState()
|
||||||
},
|
},
|
||||||
resetState() {
|
resetState() {
|
||||||
this.permissions = []
|
this.permissions = new Set<string>()
|
||||||
this.roles = []
|
this.roles = []
|
||||||
this.isSetUser = false
|
this.isSetUser = false
|
||||||
this.user = {
|
this.user = {
|
||||||
|
|||||||
@@ -457,3 +457,9 @@ export const BpmProcessInstanceStatus = {
|
|||||||
REJECT: 3, // 审批不通过
|
REJECT: 3, // 审批不通过
|
||||||
CANCEL: 4 // 已取消
|
CANCEL: 4 // 已取消
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const BpmAutoApproveType = {
|
||||||
|
NONE: 0, // 不自动通过
|
||||||
|
APPROVE_ALL: 1, // 仅审批一次,后续重复的审批节点均自动通过
|
||||||
|
APPROVE_SEQUENT: 2, // 仅针对连续审批的节点自动通过
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-15
@@ -1,4 +1,6 @@
|
|||||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||||
|
import {hasPermission} from "@/directives/permission/hasPermi";
|
||||||
|
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
@@ -7,21 +9,8 @@ const { t } = useI18n() // 国际化
|
|||||||
* @param {Array} value 校验值
|
* @param {Array} value 校验值
|
||||||
* @returns {Boolean}
|
* @returns {Boolean}
|
||||||
*/
|
*/
|
||||||
export function checkPermi(value: string[]) {
|
export function checkPermi(permission: string[]) {
|
||||||
if (value && value instanceof Array && value.length > 0) {
|
return hasPermission(permission)
|
||||||
const { wsCache } = useCache()
|
|
||||||
const permissionDatas = value
|
|
||||||
const all_permission = '*:*:*'
|
|
||||||
const userInfo = wsCache.get(CACHE_KEY.USER)
|
|
||||||
const permissions = userInfo?.permissions || []
|
|
||||||
const hasPermission = permissions.some((permission: string) => {
|
|
||||||
return all_permission === permission || permissionDatas.includes(permission)
|
|
||||||
})
|
|
||||||
return !!hasPermission
|
|
||||||
} else {
|
|
||||||
console.error(t('permission.hasPermission'))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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' })
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 }
|
||||||
@@ -13,6 +13,9 @@
|
|||||||
<el-form-item label="分类标志" prop="code">
|
<el-form-item label="分类标志" prop="code">
|
||||||
<el-input v-model="formData.code" placeholder="请输入分类标志" />
|
<el-input v-model="formData.code" placeholder="请输入分类标志" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="分类描述" prop="description">
|
||||||
|
<el-input v-model="formData.description" type="textarea" placeholder="请输入分类描述" />
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="分类状态" prop="status">
|
<el-form-item label="分类状态" prop="status">
|
||||||
<el-radio-group v-model="formData.status">
|
<el-radio-group v-model="formData.status">
|
||||||
<el-radio
|
<el-radio
|
||||||
@@ -58,6 +61,7 @@ const formData = ref({
|
|||||||
id: undefined,
|
id: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
code: undefined,
|
code: undefined,
|
||||||
|
description: undefined,
|
||||||
status: CommonStatusEnum.ENABLE,
|
status: CommonStatusEnum.ENABLE,
|
||||||
sort: undefined
|
sort: undefined
|
||||||
})
|
})
|
||||||
@@ -117,6 +121,7 @@ const resetForm = () => {
|
|||||||
id: undefined,
|
id: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
code: undefined,
|
code: undefined,
|
||||||
|
description: undefined,
|
||||||
status: CommonStatusEnum.ENABLE,
|
status: CommonStatusEnum.ENABLE,
|
||||||
sort: undefined
|
sort: undefined
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex items-center h-50px">
|
<div class="flex items-center h-50px" v-memo="[categoryInfo.name, isCategorySorting]">
|
||||||
<!-- 头部:分类名 -->
|
<!-- 头部:分类名 -->
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<el-tooltip content="拖动排序" v-if="isCategorySorting">
|
<el-tooltip content="拖动排序" v-if="isCategorySorting">
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<div class="color-gray-600 text-16px"> ({{ categoryInfo.modelList?.length || 0 }}) </div>
|
<div class="color-gray-600 text-16px"> ({{ categoryInfo.modelList?.length || 0 }}) </div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 头部:操作 -->
|
<!-- 头部:操作 -->
|
||||||
<div class="flex-1 flex" v-if="!isCategorySorting">
|
<div class="flex-1 flex" v-show="!isCategorySorting">
|
||||||
<div
|
<div
|
||||||
v-if="categoryInfo.modelList.length > 0"
|
v-if="categoryInfo.modelList.length > 0"
|
||||||
class="ml-20px flex items-center"
|
class="ml-20px flex items-center"
|
||||||
@@ -64,20 +64,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 模型列表 -->
|
<!-- 模型列表 -->
|
||||||
<el-collapse-transition>
|
<el-collapse-transition>
|
||||||
<div v-show="isExpand">
|
<div v-show="isExpand">
|
||||||
<el-table
|
<el-table
|
||||||
|
v-if="modelList && modelList.length > 0"
|
||||||
:class="categoryInfo.name"
|
:class="categoryInfo.name"
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
:header-cell-style="{ backgroundColor: isDark ? '' : '#edeff0', paddingLeft: '10px' }"
|
|
||||||
:cell-style="{ paddingLeft: '10px' }"
|
|
||||||
:row-style="{ height: '68px' }"
|
|
||||||
:data="modelList"
|
:data="modelList"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
|
:header-cell-style="tableHeaderStyle"
|
||||||
|
:cell-style="tableCellStyle"
|
||||||
|
:row-style="{ height: '68px' }"
|
||||||
>
|
>
|
||||||
<el-table-column label="流程名" prop="name" min-width="150">
|
<el-table-column label="流程名" prop="name" min-width="150">
|
||||||
<template #default="scope">
|
<template #default="{ row }">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<el-tooltip content="拖动排序" v-if="isModelSorting">
|
<el-tooltip content="拖动排序" v-if="isModelSorting">
|
||||||
<Icon
|
<Icon
|
||||||
@@ -85,32 +87,30 @@
|
|||||||
class="drag-icon cursor-move text-#8a909c mr-10px"
|
class="drag-icon cursor-move text-#8a909c mr-10px"
|
||||||
/>
|
/>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-image :src="scope.row.icon" class="h-38px w-38px mr-10px rounded" />
|
<el-image v-if="row.icon" :src="row.icon" class="h-38px w-38px mr-10px rounded" />
|
||||||
{{ scope.row.name }}
|
{{ row.name }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="可见范围" prop="startUserIds" min-width="100">
|
<el-table-column label="可见范围" prop="startUserIds" min-width="150">
|
||||||
<template #default="scope">
|
<template #default="{ row }">
|
||||||
<el-text v-if="!scope.row.startUsers || scope.row.startUsers.length === 0">
|
<el-text v-if="!row.startUsers?.length"> 全部可见 </el-text>
|
||||||
全部可见
|
<el-text v-else-if="row.startUsers.length === 1">
|
||||||
</el-text>
|
{{ row.startUsers[0].nickname }}
|
||||||
<el-text v-else-if="scope.row.startUsers.length == 1">
|
|
||||||
{{ scope.row.startUsers[0].nickname }}
|
|
||||||
</el-text>
|
</el-text>
|
||||||
<el-text v-else>
|
<el-text v-else>
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
class="box-item"
|
class="box-item"
|
||||||
effect="dark"
|
effect="dark"
|
||||||
placement="top"
|
placement="top"
|
||||||
:content="scope.row.startUsers.map((user: any) => user.nickname).join('、')"
|
:content="row.startUsers.map((user: any) => user.nickname).join('、')"
|
||||||
>
|
>
|
||||||
{{ scope.row.startUsers[0].nickname }}等 {{ scope.row.startUsers.length }} 人可见
|
{{ row.startUsers[0].nickname }}等 {{ row.startUsers.length }} 人可见
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</el-text>
|
</el-text>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="表单信息" prop="formType" min-width="200">
|
<el-table-column label="表单信息" prop="formType" min-width="150">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="scope.row.formType === BpmModelFormType.NORMAL"
|
v-if="scope.row.formType === BpmModelFormType.NORMAL"
|
||||||
@@ -157,27 +157,26 @@
|
|||||||
link
|
link
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="openModelForm('update', scope.row.id)"
|
@click="openModelForm('update', scope.row.id)"
|
||||||
v-hasPermi="['bpm:model:update']"
|
v-if="hasPermiUpdate"
|
||||||
:disabled="!isManagerUser(scope.row)"
|
:disabled="!isManagerUser(scope.row)"
|
||||||
>
|
>
|
||||||
修改
|
修改
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
link
|
link
|
||||||
class="!ml-5px"
|
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="handleDesign(scope.row)"
|
@click="openModelForm('copy', scope.row.id)"
|
||||||
v-hasPermi="['bpm:model:update']"
|
v-if="hasPermiUpdate"
|
||||||
:disabled="!isManagerUser(scope.row)"
|
:disabled="!isManagerUser(scope.row)"
|
||||||
>
|
>
|
||||||
设计
|
复制
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
link
|
link
|
||||||
class="!ml-5px"
|
class="!ml-5px"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="handleDeploy(scope.row)"
|
@click="handleDeploy(scope.row)"
|
||||||
v-hasPermi="['bpm:model:deploy']"
|
v-if="hasPermiDeploy"
|
||||||
:disabled="!isManagerUser(scope.row)"
|
:disabled="!isManagerUser(scope.row)"
|
||||||
>
|
>
|
||||||
发布
|
发布
|
||||||
@@ -185,28 +184,43 @@
|
|||||||
<el-dropdown
|
<el-dropdown
|
||||||
class="!align-middle ml-5px"
|
class="!align-middle ml-5px"
|
||||||
@command="(command) => handleModelCommand(command, scope.row)"
|
@command="(command) => handleModelCommand(command, scope.row)"
|
||||||
v-hasPermi="['bpm:process-definition:query', 'bpm:model:update', 'bpm:model:delete']"
|
v-if="hasPermiMore"
|
||||||
>
|
>
|
||||||
<el-button type="primary" link>更多</el-button>
|
<el-button type="primary" link>更多</el-button>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
<el-dropdown-item
|
<el-dropdown-item command="handleDefinitionList" v-if="hasPermiPdQuery">
|
||||||
command="handleDefinitionList"
|
|
||||||
v-if="checkPermi(['bpm:process-definition:query'])"
|
|
||||||
>
|
|
||||||
历史
|
历史
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item
|
||||||
|
command="handleReport"
|
||||||
|
v-if="
|
||||||
|
checkPermi(['bpm:process-instance:manager-query']) &&
|
||||||
|
scope.row.processDefinition
|
||||||
|
"
|
||||||
|
:disabled="!isManagerUser(scope.row)"
|
||||||
|
>
|
||||||
|
报表
|
||||||
|
</el-dropdown-item>
|
||||||
<el-dropdown-item
|
<el-dropdown-item
|
||||||
command="handleChangeState"
|
command="handleChangeState"
|
||||||
v-if="checkPermi(['bpm:model:update']) && scope.row.processDefinition"
|
v-if="hasPermiUpdate && scope.row.processDefinition"
|
||||||
:disabled="!isManagerUser(scope.row)"
|
:disabled="!isManagerUser(scope.row)"
|
||||||
>
|
>
|
||||||
{{ 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"
|
||||||
v-if="checkPermi(['bpm:model:delete'])"
|
v-if="hasPermiDelete"
|
||||||
:disabled="!isManagerUser(scope.row)"
|
:disabled="!isManagerUser(scope.row)"
|
||||||
>
|
>
|
||||||
删除
|
删除
|
||||||
@@ -235,16 +249,11 @@
|
|||||||
</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 { formatDate } from '@/utils/formatTime'
|
import { formatDate } from '@/utils/formatTime'
|
||||||
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'
|
||||||
@@ -253,26 +262,89 @@ import { BpmModelFormType } from '@/utils/constants'
|
|||||||
import { checkPermi } from '@/utils/permission'
|
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, isEqual } from 'lodash-es'
|
||||||
|
import { useTagsView } from '@/hooks/web/useTagsView'
|
||||||
|
import { useDebounceFn } from '@vueuse/core'
|
||||||
|
|
||||||
defineOptions({ name: 'BpmModel' })
|
defineOptions({ name: 'BpmModel' })
|
||||||
|
|
||||||
const props = defineProps({
|
// 优化 Props 类型定义
|
||||||
categoryInfo: propTypes.object.def([]), // 分类后的数据
|
interface UserInfo {
|
||||||
isCategorySorting: propTypes.bool.def(false) // 是否分类在排序
|
nickname: string
|
||||||
})
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProcessDefinition {
|
||||||
|
deploymentTime: string
|
||||||
|
version: number
|
||||||
|
suspensionState: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModelInfo {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
icon?: string
|
||||||
|
startUsers?: UserInfo[]
|
||||||
|
processDefinition?: ProcessDefinition
|
||||||
|
formType?: number
|
||||||
|
formId?: number
|
||||||
|
formName?: string
|
||||||
|
formCustomCreatePath?: string
|
||||||
|
managerUserIds?: number[]
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CategoryInfoProps {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
modelList: ModelInfo[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
categoryInfo: CategoryInfoProps
|
||||||
|
isCategorySorting: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits(['success'])
|
const emit = defineEmits(['success'])
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const { push } = useRouter() // 路由
|
const { push } = useRouter() // 路由
|
||||||
const userStore = useUserStoreWithOut() // 用户信息缓存
|
const userStore = useUserStoreWithOut() // 用户信息缓存
|
||||||
const isDark = computed(() => useAppStore().getIsDark) // 是否黑暗模式
|
const isDark = computed(() => useAppStore().getIsDark) // 是否黑暗模式
|
||||||
|
const router = useRouter() // 路由
|
||||||
|
|
||||||
const isModelSorting = ref(false) // 是否正处于排序状态
|
const isModelSorting = ref(false) // 是否正处于排序状态
|
||||||
const originalData: any = ref([]) // 原始数据
|
const originalData = ref<ModelInfo[]>([]) // 原始数据
|
||||||
const modelList: any = ref([]) // 模型列表
|
const modelList = ref<ModelInfo[]>([]) // 模型列表
|
||||||
const isExpand = ref(false) // 是否处于展开状态
|
const isExpand = ref(false) // 是否处于展开状态
|
||||||
|
|
||||||
|
// 使用 computed 优化表格样式计算
|
||||||
|
const tableHeaderStyle = computed(() => ({
|
||||||
|
backgroundColor: isDark.value ? '' : '#edeff0',
|
||||||
|
paddingLeft: '10px'
|
||||||
|
}))
|
||||||
|
|
||||||
|
const tableCellStyle = computed(() => ({
|
||||||
|
paddingLeft: '10px'
|
||||||
|
}))
|
||||||
|
|
||||||
|
/** 权限校验:通过 computed 解决列表的卡顿问题 */
|
||||||
|
const hasPermiUpdate = computed(() => {
|
||||||
|
return checkPermi(['bpm:model:update'])
|
||||||
|
})
|
||||||
|
const hasPermiDelete = computed(() => {
|
||||||
|
return checkPermi(['bpm:model:delete'])
|
||||||
|
})
|
||||||
|
const hasPermiDeploy = computed(() => {
|
||||||
|
return checkPermi(['bpm:model:deploy'])
|
||||||
|
})
|
||||||
|
const hasPermiMore = computed(() => {
|
||||||
|
return checkPermi(['bpm:process-definition:query', 'bpm:model:update', 'bpm:model:delete'])
|
||||||
|
})
|
||||||
|
const hasPermiPdQuery = computed(() => {
|
||||||
|
return checkPermi(['bpm:process-definition:query'])
|
||||||
|
})
|
||||||
|
|
||||||
/** '更多'操作按钮 */
|
/** '更多'操作按钮 */
|
||||||
const handleModelCommand = (command: string, row: any) => {
|
const handleModelCommand = (command: string, row: any) => {
|
||||||
switch (command) {
|
switch (command) {
|
||||||
@@ -285,6 +357,18 @@ const handleModelCommand = (command: string, row: any) => {
|
|||||||
case 'handleChangeState':
|
case 'handleChangeState':
|
||||||
handleChangeState(row)
|
handleChangeState(row)
|
||||||
break
|
break
|
||||||
|
case 'handleClean':
|
||||||
|
handleClean(row)
|
||||||
|
break
|
||||||
|
case 'handleReport':
|
||||||
|
router.push({
|
||||||
|
name: 'BpmProcessInstanceReport',
|
||||||
|
query: {
|
||||||
|
processDefinitionId: row.processDefinition.id,
|
||||||
|
processDefinitionKey: row.key
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -318,6 +402,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
|
||||||
@@ -337,14 +434,6 @@ const handleChangeState = async (row: any) => {
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 设计流程 */
|
|
||||||
const handleDesign = (row: any) => {
|
|
||||||
push({
|
|
||||||
name: 'BpmModelUpdate',
|
|
||||||
params: { id: row.id }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 发布流程 */
|
/** 发布流程 */
|
||||||
const handleDeploy = async (row: any) => {
|
const handleDeploy = async (row: any) => {
|
||||||
try {
|
try {
|
||||||
@@ -422,14 +511,15 @@ const handleModelSortCancel = () => {
|
|||||||
|
|
||||||
/** 创建拖拽实例 */
|
/** 创建拖拽实例 */
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
const initSort = () => {
|
const initSort = useDebounceFn(() => {
|
||||||
const table = document.querySelector(`.${props.categoryInfo.name} .el-table__body-wrapper tbody`)
|
const table = document.querySelector(`.${props.categoryInfo.name} .el-table__body-wrapper tbody`)
|
||||||
|
if (!table) return
|
||||||
|
|
||||||
Sortable.create(table, {
|
Sortable.create(table, {
|
||||||
group: 'shared',
|
group: 'shared',
|
||||||
animation: 150,
|
animation: 150,
|
||||||
draggable: '.el-table__row',
|
draggable: '.el-table__row',
|
||||||
handle: '.drag-icon',
|
handle: '.drag-icon',
|
||||||
// 结束拖动事件
|
|
||||||
onEnd: ({ newDraggableIndex, oldDraggableIndex }) => {
|
onEnd: ({ newDraggableIndex, oldDraggableIndex }) => {
|
||||||
if (oldDraggableIndex !== newDraggableIndex) {
|
if (oldDraggableIndex !== newDraggableIndex) {
|
||||||
modelList.value.splice(
|
modelList.value.splice(
|
||||||
@@ -440,15 +530,18 @@ const initSort = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}, 200)
|
||||||
|
|
||||||
/** 更新 modelList 模型列表 */
|
/** 更新 modelList 模型列表 */
|
||||||
const updateModeList = () => {
|
const updateModeList = useDebounceFn(() => {
|
||||||
modelList.value = cloneDeep(props.categoryInfo.modelList)
|
const newModelList = props.categoryInfo.modelList
|
||||||
if (props.categoryInfo.modelList.length > 0) {
|
if (!isEqual(modelList.value, newModelList)) {
|
||||||
isExpand.value = true
|
modelList.value = cloneDeep(newModelList)
|
||||||
|
if (newModelList?.length > 0) {
|
||||||
|
isExpand.value = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}, 100)
|
||||||
|
|
||||||
/** 重命名弹窗确定 */
|
/** 重命名弹窗确定 */
|
||||||
const renameCategoryVisible = ref(false)
|
const renameCategoryVisible = ref(false)
|
||||||
@@ -483,26 +576,31 @@ 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('复制流程')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => props.categoryInfo.modelList, updateModeList, { immediate: true })
|
watchEffect(() => {
|
||||||
watch(
|
if (props.categoryInfo?.modelList) {
|
||||||
() => props.isCategorySorting,
|
updateModeList()
|
||||||
(val) => {
|
}
|
||||||
if (val) isExpand.value = false
|
|
||||||
},
|
if (props.isCategorySorting) {
|
||||||
{ immediate: true }
|
isExpand.value = false
|
||||||
)
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -519,10 +617,16 @@ watch(
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
:deep() {
|
.category-draggable-model {
|
||||||
.el-table__cell {
|
:deep(.el-table__cell) {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-bottom: none !important;
|
border-bottom: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 优化表格渲染性能
|
||||||
|
:deep(.el-table__body) {
|
||||||
|
will-change: transform;
|
||||||
|
transform: translateZ(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -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) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
|
||||||
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>
|
|
||||||
@@ -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('保存失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听 key、name 和 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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表单校验 */
|
/** 表单校验 */
|
||||||
|
|||||||
Loaded 100 of 127 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user