Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c144439e5b | ||
|
|
42f5223941 | ||
|
|
1b2c7ceb90 | ||
|
|
8855b77770 | ||
|
|
f3b28f43f1 | ||
|
|
732b1741c1 | ||
|
|
1e161d2e64 | ||
|
|
59281a7126 | ||
|
|
51cffa4e03 | ||
|
|
e985b2507a | ||
|
|
aa37021a68 | ||
|
|
c56eeccaef | ||
|
|
d8851721b8 | ||
|
|
472da9274e | ||
|
|
6f724d9d52 | ||
|
|
6791de9d7c | ||
|
|
241e27137c | ||
|
|
eeda821b74 | ||
|
|
3ba5cb460c | ||
|
|
a8c1fd4473 | ||
|
|
e50cd2231c | ||
|
|
40c2cb72ff | ||
|
|
6cf3de0166 | ||
|
|
2f4599f664 | ||
|
|
7c7fbba2a3 | ||
|
|
36157a8f24 | ||
|
|
a82862be47 | ||
|
|
f86bd0df0b | ||
|
|
53841a598f | ||
|
|
ab11818ce2 | ||
|
|
5a0bcfe6b5 | ||
|
|
ecfe1b6145 | ||
|
|
1df1850ef9 | ||
|
|
ea97abc0af | ||
|
|
0ae9acdfd6 | ||
|
|
25484a941b | ||
|
|
169e3600d9 | ||
|
|
b5aa9d24ac | ||
|
|
413721bdfd | ||
|
|
a6461da0f8 | ||
|
|
6e6754e44a | ||
|
|
e891a4bfd2 | ||
|
|
5e386336b9 | ||
|
|
f87b37eaac | ||
|
|
c083a0c986 | ||
|
|
9685fbf822 | ||
|
|
746aeaff35 | ||
|
|
21c28c3665 | ||
|
|
9f0f5b0e6c | ||
|
|
0ab7f2342c | ||
|
|
a389392c8e | ||
|
|
5d31881f48 | ||
|
|
3e826776f4 | ||
|
|
4c5c6ff5c3 | ||
|
|
d76df547ae | ||
|
|
bfb9e2e77f | ||
|
|
fe2ac97f9d | ||
|
|
4bd020857f | ||
|
|
a9c924a421 | ||
|
|
b09ff417c3 | ||
|
|
6b397104fc | ||
|
|
9e2958f7d0 | ||
|
|
dc8f5cf977 | ||
|
|
9459a7296f | ||
|
|
8c52c6867f | ||
|
|
46985c6e1b | ||
|
|
7697054918 | ||
|
|
acb00ba866 | ||
|
|
cff65aa901 | ||
|
|
a6de16a02d | ||
|
|
0b6af855ac | ||
|
|
5785d741bb | ||
|
|
e52049876e | ||
|
|
7474c6b83b | ||
|
|
1f19b567c2 | ||
|
|
2efd0d69d9 | ||
|
|
ee46a31a81 | ||
|
|
4835316fca | ||
|
|
9bdafb0f6f | ||
|
|
740de45a82 | ||
|
|
7917bca9fd | ||
|
|
2835c9d111 | ||
|
|
f7477c3264 | ||
|
|
9c2b13f2d0 | ||
|
|
e8315e2864 | ||
|
|
c2f51b3c12 | ||
|
|
c00e6988a5 | ||
|
|
8ea43f30af | ||
|
|
abdaf2e0ca | ||
|
|
eb6f3a3e49 | ||
|
|
175d6ba0cb | ||
|
|
f0c797deac | ||
|
|
6e077ac7d9 | ||
|
|
557bc29d24 | ||
|
|
5063db47f3 | ||
|
|
8e4d91f43e | ||
|
|
97b1ac6ad3 | ||
|
|
be33226178 | ||
|
|
1a95687f61 | ||
|
|
f2f9391328 | ||
|
|
2de62667dc | ||
|
|
c79f027d62 | ||
|
|
4754685d84 | ||
|
|
1597891244 | ||
|
|
b108b07a96 | ||
|
|
7ee8cb89a8 | ||
|
|
c665fad8bc | ||
|
|
019bd9533e | ||
|
|
8ff9443f28 | ||
|
|
e591aa8867 | ||
|
|
9da5beb879 | ||
|
|
e3a39b7732 | ||
|
|
e0e287c88b | ||
|
|
d0ae37da4e | ||
|
|
d017ab55db | ||
|
|
e0bc260fc1 | ||
|
|
70c3a09f76 | ||
|
|
f93919b750 | ||
|
|
3c7526de2f | ||
|
|
886f3391aa | ||
|
|
22e9eb2add | ||
|
|
6dc5f5dcb5 | ||
|
|
455c192a9a | ||
|
|
9f8f790ec9 | ||
|
|
7c391a315e | ||
|
|
9d8e06a54b | ||
|
|
929b67b9ab | ||
|
|
690243dc4f | ||
|
|
a4dd4773ca | ||
|
|
6eb013b2a6 | ||
|
|
6dee7415cb | ||
|
|
513ea33f3f | ||
|
|
41f8f0cc9a | ||
|
|
6041a6eb31 | ||
|
|
ad0d4803e2 | ||
|
|
e587d68fef | ||
|
|
8e5a4d09d2 | ||
|
|
101c704a8f | ||
|
|
154dd6c40b | ||
|
|
d228df5210 | ||
|
|
5cdc15387c | ||
|
|
64e2ddfa18 | ||
|
|
90c71804e2 | ||
|
|
f125df7dd2 | ||
|
|
22199c64fd | ||
|
|
4ffaa2dc6e | ||
|
|
7b4b5ebbd1 | ||
|
|
16aef13573 | ||
|
|
bcc75bdd14 | ||
|
|
4423c5f84a | ||
|
|
4b0d3ec29c | ||
|
|
97a68ce4ba | ||
|
|
068b32d0d4 | ||
|
|
00a19cfc78 | ||
|
|
1d4339fac8 | ||
|
|
295a43d516 | ||
|
|
41b8407841 | ||
|
|
f1e161279c | ||
|
|
8f7a75bf3b | ||
|
|
29a0c691a0 | ||
|
|
47f1f13ca2 | ||
|
|
24e1374993 | ||
|
|
3a9f8db3fa | ||
|
|
397c09a195 | ||
|
|
738289bc06 | ||
|
|
f8b0cd53e5 | ||
|
|
7895d184e8 | ||
|
|
fc5df39b1a | ||
|
|
a07f61a9ed | ||
|
|
2cc9652d4b | ||
|
|
a283dc8cba | ||
|
|
b5d99e2c75 | ||
|
|
39f183bb3e | ||
|
|
2d1b31a841 | ||
|
|
4d2c038972 | ||
|
|
138d535e87 | ||
|
|
522e358eaf | ||
|
|
056684e291 | ||
|
|
12bd71e70b | ||
|
|
158708c1b1 | ||
|
|
6cd6429102 | ||
|
|
7b8e4ea238 | ||
|
|
c34bfc18bc | ||
|
|
caa31a67fe | ||
|
|
5f2e23f4da | ||
|
|
6304a8e9b6 | ||
|
|
3783582b33 | ||
|
|
ba2cdcc310 | ||
|
|
c119e910d0 | ||
|
|
2fe28af05d | ||
|
|
756addd423 | ||
|
|
eefabf34a5 | ||
|
|
0924f50266 | ||
|
|
67769c299e | ||
|
|
6ac0ee5844 | ||
|
|
da0a780163 | ||
|
|
046e6ce5a7 | ||
|
|
2719541462 | ||
|
|
0db0b66051 | ||
|
|
735837f0f9 | ||
|
|
2bca7bb44a | ||
|
|
d03fdecc26 | ||
|
|
e451f483c5 | ||
|
|
5eb415437d | ||
|
|
2c8125e218 | ||
|
|
3b49f7dec9 | ||
|
|
18c7693ed6 | ||
|
|
5e7afae972 | ||
|
|
b9e1529623 | ||
|
|
649c24ed51 | ||
|
|
fdee8d8628 | ||
|
|
33a4100ed4 | ||
|
|
52a8c46a0a | ||
|
|
8f32dce2d6 | ||
|
|
81d2a38363 | ||
|
|
4ba5dad22e | ||
|
|
91b1ecd3bb | ||
|
|
8b0778ca0c | ||
|
|
e4b57bd8c0 | ||
|
|
1c23298d19 | ||
|
|
2aa2b4609f | ||
|
|
7d29c6319f | ||
|
|
df34f65df8 | ||
|
|
e342e91f79 | ||
|
|
066607ab08 | ||
|
|
d052356ff0 | ||
|
|
1e6762ea69 | ||
|
|
0fae9a4ba4 | ||
|
|
5814826fa8 | ||
|
|
32d0093488 | ||
|
|
6dd6f9c2a5 | ||
|
|
8df5991940 | ||
|
|
8b5740d05c | ||
|
|
92314ee945 | ||
|
|
0fbd98291e | ||
|
|
31cef4f3bc | ||
|
|
8a97a33a29 | ||
|
|
cd69659c93 | ||
|
|
1c3be2b92a | ||
|
|
078407fa10 | ||
|
|
1e39af84d3 | ||
|
|
6f5cc3248f | ||
|
|
d3f3044655 | ||
|
|
70abc5fc17 | ||
|
|
ce3380fa3f | ||
|
|
40d7f5786d | ||
|
|
34fe09e2db | ||
|
|
55da358336 | ||
|
|
65f0b2f8ab | ||
|
|
13272c2293 | ||
|
|
21fc3fd7e6 | ||
|
|
71de0c3ecf | ||
|
|
c3cf479f6f | ||
|
|
77062cb391 | ||
|
|
1bb6a23ff8 | ||
|
|
fec7af453f | ||
|
|
2df0aa20ff | ||
|
|
98e9ba49ae | ||
|
|
5121d5694d | ||
|
|
bf87c46d0f | ||
|
|
c52a7e5012 | ||
|
|
12f52b288b | ||
|
|
3202378b04 | ||
|
|
a097560f81 | ||
|
|
e504be7d42 | ||
|
|
812e5b5865 | ||
|
|
8465f8fada | ||
|
|
7044bcee60 | ||
|
|
0284893b4a | ||
|
|
a57840c804 | ||
|
|
9b290ae3ce | ||
|
|
c9b12c8b39 | ||
|
|
67430a83fb | ||
|
|
c1109e90c4 | ||
|
|
a43bca5785 | ||
|
|
137b33e7cf | ||
|
|
458242a27b | ||
|
|
cf200b2618 | ||
|
|
c1924d566f | ||
|
|
b4139676f3 | ||
|
|
92f4160fe1 | ||
|
|
c1a2228022 | ||
|
|
71a2fddb1b | ||
|
|
8bdf60c886 | ||
|
|
38360eef12 | ||
|
|
d599134de6 | ||
|
|
dca54d0760 | ||
|
|
bd4ac52a2d | ||
|
|
17225ef3df | ||
|
|
d477b35f83 | ||
|
|
a6f325b14d | ||
|
|
57ab876886 | ||
|
|
8a8ae557f7 | ||
|
|
c7ecb0a69f | ||
|
|
a401d8ede4 | ||
|
|
de073e82bc | ||
|
|
dfa9cd5a3c | ||
|
|
3859a48825 | ||
|
|
0a4a9e0e55 | ||
|
|
30052b05f5 | ||
|
|
520fc784bc | ||
|
|
cd9cefa2c8 | ||
|
|
ffc09e8e35 | ||
|
|
aa6610e36c | ||
|
|
43b7dff0d0 | ||
|
|
dcdce4120d | ||
|
|
410c3bc9a7 | ||
|
|
d6e89db05c | ||
|
|
1f34aa8dee | ||
|
|
5116b94873 | ||
|
|
5267fe459f | ||
|
|
498244020d | ||
|
|
0f8522e306 | ||
|
|
f47e8f3a25 | ||
|
|
d818e338e3 | ||
|
|
fb2ea334b9 | ||
|
|
ac13524515 | ||
|
|
c7bd9f7138 | ||
|
|
4bc79c382b | ||
|
|
3a9197dfdf | ||
|
|
a31bafbb4a | ||
|
|
708e12b6ce | ||
|
|
91ea4b7936 | ||
|
|
2db1f726c3 | ||
|
|
57820cb3e1 | ||
|
|
10e432243e | ||
|
|
a033706fb4 | ||
|
|
ea02192b64 | ||
|
|
1d01955b8b | ||
|
|
13706de227 | ||
|
|
b056db55b2 | ||
|
|
842fe09e8b | ||
|
|
5ef5ee6e14 | ||
|
|
d10a3af3d1 | ||
|
|
6a9f5cc10f | ||
|
|
dcf1ad6b38 | ||
|
|
ca9a389ae8 | ||
|
|
2aefcd9070 | ||
|
|
dad97e2f17 | ||
|
|
22e425208c | ||
|
|
77e8827f8b | ||
|
|
6b06f320b9 | ||
|
|
33f3bc0567 | ||
|
|
4342c3ee05 | ||
|
|
c828517c55 | ||
|
|
2ff9904f63 | ||
|
|
fe21db3704 | ||
|
|
abcecc92c5 | ||
|
|
9dc268efb4 | ||
|
|
c49e8bd3f9 | ||
|
|
a4a4dcdfc2 | ||
|
|
9ec660817c | ||
|
|
b293f15779 | ||
|
|
c6b6d2f404 | ||
|
|
a69dc3a187 | ||
|
|
753a32aecc | ||
|
|
019104f460 | ||
|
|
f545dcc63d | ||
|
|
09d1dbedf9 |
No files matched your search
@@ -32,3 +32,6 @@ VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
|
||||
|
||||
# 验证码的开关
|
||||
VITE_APP_CAPTCHA_ENABLE=true
|
||||
|
||||
# GoView域名
|
||||
VITE_GOVIEW_URL='http://127.0.0.1:3000'
|
||||
@@ -29,3 +29,6 @@ VITE_MALL_H5_DOMAIN='http://localhost:3000'
|
||||
|
||||
# 验证码的开关
|
||||
VITE_APP_CAPTCHA_ENABLE=false
|
||||
|
||||
# GoView域名
|
||||
VITE_GOVIEW_URL='http://127.0.0.1:3000'
|
||||
@@ -29,3 +29,6 @@ VITE_OUT_DIR=dist-prod
|
||||
|
||||
# 商城H5会员端域名
|
||||
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
|
||||
|
||||
# GoView域名
|
||||
VITE_GOVIEW_URL='http://127.0.0.1:3000'
|
||||
@@ -29,3 +29,6 @@ VITE_OUT_DIR=dist-stage
|
||||
|
||||
# 商城H5会员端域名
|
||||
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
|
||||
|
||||
# GoView域名
|
||||
VITE_GOVIEW_URL='http://127.0.0.1:3000'
|
||||
@@ -29,3 +29,6 @@ VITE_OUT_DIR=dist-test
|
||||
|
||||
# 商城H5会员端域名
|
||||
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
|
||||
|
||||
# GoView域名
|
||||
VITE_GOVIEW_URL='http://127.0.0.1:3000'
|
||||
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 127 KiB |
@@ -69,11 +69,11 @@
|
||||
|
||||
支持 Spring Boot、Spring Cloud 两种架构:
|
||||
|
||||
① Spring Boot 单体架构:<https://github.com/YunaiV/ruoyi-vue-pro>
|
||||
① Spring Boot 单体架构:<https://doc.iocoder.cn>
|
||||
|
||||

|
||||
|
||||
② Spring Cloud 微服务架构:<https://github.com/YunaiV/yudao-cloud>
|
||||
② Spring Cloud 微服务架构:<https://cloud.iocoder.cn>
|
||||
|
||||

|
||||
|
||||
@@ -120,18 +120,22 @@
|
||||
|
||||
### 工作流程
|
||||
|
||||
| | 功能 | 描述 |
|
||||
|-----|-------|----------------------------------------|
|
||||
| 🚀 | 流程模型 | 配置工作流的流程模型,支持文件导入与在线设计流程图,提供 7 种任务分配规则 |
|
||||
| 🚀 | 流程表单 | 拖动表单元素生成相应的工作流表单,覆盖 Element UI 所有的表单组件 |
|
||||
| 🚀 | 用户分组 | 自定义用户分组,可用于工作流的审批分组 |
|
||||
| 🚀 | 我的流程 | 查看我发起的工作流程,支持新建、取消流程等操作,高亮流程图、审批时间线 |
|
||||
| 🚀 | 待办任务 | 查看自己【未】审批的工作任务,支持通过、不通过、转发、委派、退回等操作 |
|
||||
| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,未来会支持回退操作 |
|
||||
| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 |
|
||||
| | 功能 | 描述 |
|
||||
|----|-------|-----------------------------------------|
|
||||
| 🚀 | 流程模型 | 配置工作流的流程模型,支持 BPMN 和仿钉钉/飞书设计器 |
|
||||
| 🚀 | 流程表单 | 拖动表单元素生成相应的工作流表单,覆盖 Element UI 所有的表单组件 |
|
||||
| 🚀 | 用户分组 | 自定义用户分组,可用于工作流的审批分组 |
|
||||
| 🚀 | 我的流程 | 查看我发起的工作流程,支持新建、取消流程等操作,高亮流程图、审批时间线 |
|
||||
| 🚀 | 待办任务 | 查看自己【未】审批的工作任务,支持通过、不通过、转派、委派、退回、加减签等操作 |
|
||||
| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,支持流程预测,展示未来审批人信息 |
|
||||
| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 |
|
||||
|
||||

|
||||
|
||||
| BPMN 设计器 | 钉钉/飞书设计器 |
|
||||
|------------------------------|--------------------------------|
|
||||
|  |  |
|
||||
|
||||
### 支付系统
|
||||
|
||||
| | 功能 | 描述 |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "yudao-ui-admin-vue3",
|
||||
"version": "2.3.0-snapshot",
|
||||
"version": "2.4.0-snapshot",
|
||||
"description": "基于vue3、vite4、element-plus、typesScript",
|
||||
"author": "xingyu",
|
||||
"private": false,
|
||||
@@ -9,11 +9,11 @@
|
||||
"dev": "vite --mode env.local",
|
||||
"dev-server": "vite --mode dev",
|
||||
"ts:check": "vue-tsc --noEmit",
|
||||
"build:local": "node ./node_modules/vite/bin/vite.js build",
|
||||
"build:dev": "node ./node_modules/vite/bin/vite.js build --mode dev",
|
||||
"build:test": "node ./node_modules/vite/bin/vite.js build --mode test",
|
||||
"build:stage": "node ./node_modules/vite/bin/vite.js build --mode stage",
|
||||
"build:prod": "node ./node_modules/vite/bin/vite.js build --mode prod",
|
||||
"build:local": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build",
|
||||
"build:dev": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode dev",
|
||||
"build:test": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode test",
|
||||
"build:stage": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode stage",
|
||||
"build:prod": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode prod",
|
||||
"serve:dev": "vite preview --mode dev",
|
||||
"serve:prod": "vite preview --mode prod",
|
||||
"preview": "pnpm build:local && vite preview",
|
||||
@@ -26,8 +26,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.1.0",
|
||||
"@form-create/designer": "^3.1.3",
|
||||
"@form-create/element-ui": "^3.1.24",
|
||||
"@form-create/designer": "^3.2.6",
|
||||
"@form-create/element-ui": "^3.2.11",
|
||||
"@iconify/iconify": "^3.1.1",
|
||||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"@videojs-player/vue": "^1.0.0",
|
||||
@@ -64,13 +64,14 @@
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"qrcode": "^1.5.3",
|
||||
"qs": "^6.12.0",
|
||||
"sortablejs": "^1.15.3",
|
||||
"steady-xml": "^0.1.0",
|
||||
"url": "^0.11.3",
|
||||
"video.js": "^7.21.5",
|
||||
"vue": "3.4.21",
|
||||
"vue": "3.5.12",
|
||||
"vue-dompurify-html": "^4.1.4",
|
||||
"vue-i18n": "9.10.2",
|
||||
"vue-router": "^4.3.0",
|
||||
"vue-router": "4.4.5",
|
||||
"vue-types": "^5.1.1",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"web-storage-cache": "^1.1.1",
|
||||
@@ -95,8 +96,8 @@
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"bpmn-js": "8.9.0",
|
||||
"bpmn-js-properties-panel": "0.46.0",
|
||||
"bpmn-js": "^17.9.2",
|
||||
"bpmn-js-properties-panel": "5.23.0",
|
||||
"consola": "^3.2.3",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
@@ -143,6 +144,7 @@
|
||||
"url": "https://gitee.com/yudaocode/yudao-ui-admin-vue3/issues"
|
||||
},
|
||||
"homepage": "https://gitee.com/yudaocode/yudao-ui-admin-vue3",
|
||||
"web-types": "./web-types.json",
|
||||
"engines": {
|
||||
"node": ">= 16.0.0",
|
||||
"pnpm": ">=8.6.0"
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
export const getActivityList = async (params) => {
|
||||
return await request.get({
|
||||
url: '/bpm/activity/list',
|
||||
params
|
||||
})
|
||||
}
|
||||
@@ -36,6 +36,16 @@ export const CategoryApi = {
|
||||
return await request.put({ url: `/bpm/category/update`, data })
|
||||
},
|
||||
|
||||
// 批量修改流程分类的排序
|
||||
updateCategorySortBatch: async (ids: number[]) => {
|
||||
return await request.put({
|
||||
url: `/bpm/category/update-sort-batch`,
|
||||
params: {
|
||||
ids: ids.join(',')
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 删除流程分类
|
||||
deleteCategory: async (id: number) => {
|
||||
return await request.delete({ url: `/bpm/category/delete?id=` + id })
|
||||
|
||||
@@ -26,8 +26,8 @@ export type ModelVO = {
|
||||
bpmnXml: string
|
||||
}
|
||||
|
||||
export const getModelPage = async (params) => {
|
||||
return await request.get({ url: '/bpm/model/page', params })
|
||||
export const getModelList = async (name: string | undefined) => {
|
||||
return await request.get({ url: '/bpm/model/list', params: { name } })
|
||||
}
|
||||
|
||||
export const getModel = async (id: string) => {
|
||||
@@ -38,6 +38,16 @@ export const updateModel = async (data: ModelVO) => {
|
||||
return await request.put({ url: '/bpm/model/update', data: data })
|
||||
}
|
||||
|
||||
// 批量修改流程分类的排序
|
||||
export const updateModelSortBatch = async (ids: number[]) => {
|
||||
return await request.put({
|
||||
url: `/bpm/model/update-sort-batch`,
|
||||
params: {
|
||||
ids: ids.join(',')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const updateModelBpmn = async (data: ModelVO) => {
|
||||
return await request.put({ url: '/bpm/model/update-bpmn', data: data })
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import request from '@/config/axios'
|
||||
import { ProcessDefinitionVO } from '@/api/bpm/model'
|
||||
import { NodeType } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import { NodeType, CandidateStrategy } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
export type Task = {
|
||||
id: string
|
||||
name: string
|
||||
@@ -24,30 +24,30 @@ export type ProcessInstanceVO = {
|
||||
|
||||
// 用户信息
|
||||
export type User = {
|
||||
id: number,
|
||||
nickname: string,
|
||||
id: number
|
||||
nickname: string
|
||||
avatar: string
|
||||
}
|
||||
|
||||
// 审批任务信息
|
||||
export type ApprovalTaskInfo = {
|
||||
id: number,
|
||||
ownerUser: User,
|
||||
assigneeUser: User,
|
||||
status: number,
|
||||
id: number
|
||||
ownerUser: User
|
||||
assigneeUser: User
|
||||
status: number
|
||||
reason: string
|
||||
|
||||
}
|
||||
|
||||
// 审批节点信息
|
||||
export type ApprovalNodeInfo = {
|
||||
id : number
|
||||
id: number
|
||||
name: string
|
||||
nodeType: NodeType
|
||||
candidateStrategy?: CandidateStrategy
|
||||
status: number
|
||||
startTime?: Date
|
||||
endTime?: Date
|
||||
candidateUserList?: User[]
|
||||
candidateUsers?: User[]
|
||||
tasks: ApprovalTaskInfo[]
|
||||
}
|
||||
|
||||
@@ -88,12 +88,16 @@ export const getProcessInstanceCopyPage = async (params: any) => {
|
||||
}
|
||||
|
||||
// 获取审批详情
|
||||
export const getApprovalDetail = async (processInstanceId?:string, processDefinitionId?:string) => {
|
||||
const param = processInstanceId ? '?processInstanceId='+ processInstanceId : '?processDefinitionId='+ processDefinitionId
|
||||
return await request.get({ url: 'bpm/process-instance/get-approval-detail'+ param })
|
||||
export const getApprovalDetail = async (params: any) => {
|
||||
return await request.get({ url: 'bpm/process-instance/get-approval-detail' , params })
|
||||
}
|
||||
|
||||
// 获取表单字段权限
|
||||
export const getFormFieldsPermission = async (params: any) => {
|
||||
return await request.get({ url: '/bpm/process-instance/get-form-fields-permission', params })
|
||||
}
|
||||
|
||||
// 获取流程实例的 BPMN 模型视图
|
||||
export const getProcessInstanceBpmnModelView = async (id: string) => {
|
||||
return await request.get({ url: '/bpm/process-instance/get-bpmn-model-view?id=' + id })
|
||||
}
|
||||
@@ -9,10 +9,10 @@ export enum TaskStatusEnum {
|
||||
*/
|
||||
NOT_START = -1,
|
||||
|
||||
/**
|
||||
/**
|
||||
* 待审批
|
||||
*/
|
||||
WAIT = 0,
|
||||
WAIT = 0,
|
||||
/**
|
||||
* 审批中
|
||||
*/
|
||||
@@ -26,7 +26,7 @@ export enum TaskStatusEnum {
|
||||
* 审批不通过
|
||||
*/
|
||||
REJECT = 3,
|
||||
|
||||
|
||||
/**
|
||||
* 已取消
|
||||
*/
|
||||
@@ -35,19 +35,10 @@ export enum TaskStatusEnum {
|
||||
* 已退回
|
||||
*/
|
||||
RETURN = 5,
|
||||
/**
|
||||
* 委派中
|
||||
*/
|
||||
DELEGATE = 6,
|
||||
/**
|
||||
* 审批通过中
|
||||
*/
|
||||
APPROVING = 7,
|
||||
|
||||
}
|
||||
|
||||
export type TaskVO = {
|
||||
id: number
|
||||
APPROVING = 7
|
||||
}
|
||||
|
||||
export const getTaskTodoPage = async (params: any) => {
|
||||
@@ -76,12 +67,12 @@ export const getTaskListByProcessInstanceId = async (processInstanceId: string)
|
||||
})
|
||||
}
|
||||
|
||||
// 获取所有可回退的节点
|
||||
// 获取所有可退回的节点
|
||||
export const getTaskListByReturn = async (id: string) => {
|
||||
return await request.get({ url: '/bpm/task/list-by-return', params: { id } })
|
||||
}
|
||||
|
||||
// 回退
|
||||
// 退回
|
||||
export const returnTask = async (data: any) => {
|
||||
return await request.put({ url: '/bpm/task/return', data })
|
||||
}
|
||||
@@ -106,6 +97,16 @@ export const signDeleteTask = async (data: any) => {
|
||||
return await request.delete({ url: '/bpm/task/delete-sign', data })
|
||||
}
|
||||
|
||||
// 抄送
|
||||
export const copyTask = async (data: any) => {
|
||||
return await request.put({ url: '/bpm/task/copy', data })
|
||||
}
|
||||
|
||||
// 获取我的待办任务
|
||||
export const myTodoTask = async (processInstanceId: string) => {
|
||||
return await request.get({ url: '/bpm/task/my-todo?processInstanceId=' + processInstanceId })
|
||||
}
|
||||
|
||||
// 获取减签任务列表
|
||||
export const getChildrenTaskList = async (id: string) => {
|
||||
return await request.get({ url: '/bpm/task/list-by-parent-task-id?parentTaskId=' + id })
|
||||
|
||||
@@ -77,6 +77,7 @@ export const socialAuthRedirect = (type: number, redirectUri: string) => {
|
||||
}
|
||||
// 获取验证图片以及 token
|
||||
export const getCode = (data) => {
|
||||
debugger
|
||||
return request.postOriginal({ url: 'system/captcha/get', data })
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export const authorize = (
|
||||
return request.post({
|
||||
url: '/system/oauth2/authorize',
|
||||
headers: {
|
||||
'Content-type': 'application/x-www-form-urlencoded'
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
params: {
|
||||
response_type: responseType,
|
||||
|
||||
@@ -21,6 +21,10 @@ export const KeFuConversationApi = {
|
||||
getConversationList: async () => {
|
||||
return await request.get({ url: '/promotion/kefu-conversation/list' })
|
||||
},
|
||||
// 获得客服会话
|
||||
getConversation: async (id: number) => {
|
||||
return await request.get({ url: `/promotion/kefu-conversation/get?id=` + id })
|
||||
},
|
||||
// 客服会话置顶
|
||||
updateConversationPinned: async (data: any) => {
|
||||
return await request.put({
|
||||
@@ -30,6 +34,6 @@ export const KeFuConversationApi = {
|
||||
},
|
||||
// 删除客服会话
|
||||
deleteConversation: async (id: number) => {
|
||||
return await request.delete({ url: `/promotion/kefu-conversation/delete?id=${id}`})
|
||||
return await request.delete({ url: `/promotion/kefu-conversation/delete?id=${id}` })
|
||||
}
|
||||
}
|
||||
@@ -29,8 +29,8 @@ export const KeFuMessageApi = {
|
||||
url: '/promotion/kefu-message/update-read-status?conversationId=' + conversationId
|
||||
})
|
||||
},
|
||||
// 获得消息分页数据
|
||||
getKeFuMessagePage: async (params: any) => {
|
||||
return await request.get({ url: '/promotion/kefu-message/page', params })
|
||||
// 获得消息列表(流式加载)
|
||||
getKeFuMessageList: async (params: any) => {
|
||||
return await request.get({ url: '/promotion/kefu-message/list', params })
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,11 @@ export interface BrokerageUserVO {
|
||||
avatar: string
|
||||
}
|
||||
|
||||
// 创建分销用户
|
||||
export const createBrokerageUser = (data: any) => {
|
||||
return request.post({ url: '/trade/brokerage-user/create', data })
|
||||
}
|
||||
|
||||
// 查询分销用户列表
|
||||
export const getBrokerageUserPage = async (params: any) => {
|
||||
return await request.get({ url: `/trade/brokerage-user/page`, params })
|
||||
|
||||
@@ -13,10 +13,11 @@ export interface DeliveryPickUpStoreVO {
|
||||
latitude: number
|
||||
longitude: number
|
||||
status: number
|
||||
verifyUserIds: number[] // 绑定用户编号组数
|
||||
}
|
||||
|
||||
// 查询自提门店列表
|
||||
export const getDeliveryPickUpStorePage = async (params) => {
|
||||
export const getDeliveryPickUpStorePage = async (params: any) => {
|
||||
return await request.get({ url: '/trade/delivery/pick-up-store/page', params })
|
||||
}
|
||||
|
||||
@@ -26,8 +27,8 @@ export const getDeliveryPickUpStore = async (id: number) => {
|
||||
}
|
||||
|
||||
// 查询自提门店精简列表
|
||||
export const getListAllSimple = async (): Promise<DeliveryPickUpStoreVO[]> => {
|
||||
return await request.get({ url: '/trade/delivery/pick-up-store/list-all-simple' })
|
||||
export const getSimpleDeliveryPickUpStoreList = async (): Promise<DeliveryPickUpStoreVO[]> => {
|
||||
return await request.get({ url: '/trade/delivery/pick-up-store/simple-list' })
|
||||
}
|
||||
|
||||
// 新增自提门店
|
||||
@@ -44,3 +45,8 @@ export const updateDeliveryPickUpStore = async (data: DeliveryPickUpStoreVO) =>
|
||||
export const deleteDeliveryPickUpStore = async (id: number) => {
|
||||
return await request.delete({ url: '/trade/delivery/pick-up-store/delete?id=' + id })
|
||||
}
|
||||
|
||||
// 绑定自提店员
|
||||
export const bindStoreStaffId = async (data: any) => {
|
||||
return await request.post({ url: '/trade/delivery/pick-up-store/bind', data })
|
||||
}
|
||||
@@ -8,6 +8,7 @@ export interface AppVO {
|
||||
remark: string
|
||||
payNotifyUrl: string
|
||||
refundNotifyUrl: string
|
||||
transferNotifyUrl: string
|
||||
merchantId: number
|
||||
merchantName: string
|
||||
createTime: Date
|
||||
@@ -19,6 +20,7 @@ export interface AppPageReqVO extends PageParam {
|
||||
remark?: string
|
||||
payNotifyUrl?: string
|
||||
refundNotifyUrl?: string
|
||||
transferNotifyUrl?: string
|
||||
merchantName?: string
|
||||
createTime?: Date[]
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg t="1731390087280" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4297" width="200" height="200"><path d="M639.9 541.7c76.4-44.2 127.9-126.8 127.9-221.5C767.7 179 653.2 64.5 512 64.5S256.3 179 256.3 320.2c0 89.6 46.1 168.4 115.8 214.1C193.5 593 64.5 761.2 64.5 959.5h63.9c0-211.5 172.1-383.6 383.6-383.6 44.9 0 87.8 8.1 127.9 22.4v-56.6zM320.2 320.2c0-105.8 86-191.8 191.8-191.8s191.8 86 191.8 191.8S617.7 512 512 512s-191.8-86-191.8-191.8zM831.6 767.7V639.9h-63.9v127.8H639.9v63.9h127.8v127.9h63.9V831.6h127.9v-63.9z" fill="#5f6266" p-id="4298"></path></svg>
|
||||
|
After Width: | Height: | Size: 608 B |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1 @@
|
||||
<svg t="1729561718271" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8640" width="200" height="200"><path d="M908.5952 920.4224H164.7616a31.0784 31.0784 0 0 1-30.976-30.976c0-17.0496 13.9264-30.976 30.976-30.976h743.8336c17.0496 0 31.0272 13.9264 31.0272 30.976a31.0784 31.0784 0 0 1-31.0272 30.976z m0-123.9552H164.7616a31.0784 31.0784 0 0 1-30.976-30.976v-154.9824c0-51.1488 41.8304-92.9792 92.9792-92.9792h198.3488c-6.1952-37.1712-24.7808-72.8064-51.1488-103.8336a216.576 216.576 0 0 1-54.2208-144.128c0-58.88 23.2448-114.688 66.6112-156.4672C429.7728 71.168 485.5296 51.0976 545.9968 52.6848c111.5648 4.608 206.08 100.6592 207.616 212.2752 1.536 55.808-20.1216 110.0288-57.344 151.8592-26.3168 27.904-41.8304 61.952-48.0256 100.7104h198.3488c51.2 0 93.0304 41.8304 93.0304 92.9792v154.9824a31.0784 31.0784 0 0 1-31.0272 30.976z m-712.8064-61.952H877.568v-124.0064a31.0784 31.0784 0 0 0-31.0272-30.976h-232.448a31.0784 31.0784 0 0 1-30.976-31.0272c0-65.024 23.2448-127.0784 66.6624-173.568 27.8528-29.3888 41.8304-68.1472 41.8304-108.4416-1.536-80.5888-68.1984-148.7872-148.7872-151.8592a150.528 150.528 0 0 0-113.152 43.3664 153.6 153.6 0 0 0-48.0256 111.616c0 37.1712 13.9776 74.3424 38.7584 102.2464 44.9536 51.1488 69.7344 113.152 69.7344 176.64a31.0784 31.0784 0 0 1-30.976 31.0272h-232.448a31.0784 31.0784 0 0 0-30.976 30.976v123.9552z" fill="#fff" p-id="8641"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1 @@
|
||||
<svg t="1729178183592" class="icon" viewBox="0 0 1300 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4332" width="200" height="200"><path d="M784.074702 99.196443l10.927871 18.473304-21.302843-2.56935-14.180213 16.066571-4.130475-21.042655-19.676671-8.521137 18.733492-10.440019 2.016452-21.335366 15.708814 14.603017 20.945085-4.683373-9.041512 19.449008zM1067.22363 642.402668l-18.440781 10.92787 2.56935-21.302842-16.099094-14.180213 21.042655-4.130475 8.521137-19.676671 10.440019 18.733492 21.367889 2.016452-14.603017 15.708814 4.683373 20.945085-19.481531-9.041512z" fill="#8a8a8a" p-id="4333"></path><path d="M1067.22363 642.402668l-18.440781 10.92787 2.56935-21.302842-16.099094-14.180213 21.042655-4.130475 8.521137-19.676671 10.440019 18.733492 21.367889 2.016452-14.603017 15.708814 4.683373 20.945085-19.481531-9.041512zM571.924408 100.009528l-17.400031-12.488994 20.52228-6.211974 6.504685-20.457234 12.261331 17.595172 21.432936-0.09757-12.944323 17.074798 6.732349 20.359663-20.262093-7.02506-17.269938 12.716659 0.422804-21.46546zM991.444053 784.43246l-21.172749 3.480006 10.114785-18.928632-9.822074-19.026203 21.107702 3.772717 15.090868-15.253486 2.927109 21.237796 19.156296 9.626933-19.318914 9.366746-3.219819 21.205273-14.863204-15.48115zM428.008258 156.795426l-20.749945-5.333841 16.879657-13.237034-1.365983-21.400413 17.822836 11.936097 19.936859-7.870669-5.88674 20.619851 13.692361 16.521899-21.432936 0.813086-11.513292 18.083024-7.382817-20.132zM854.260251 896.475655l-20.749945-5.333841 16.879657-13.237034-1.365983-21.400413 17.822836 11.96862 19.936859-7.903192-5.854217 20.619851 13.659838 16.554423-21.432936 0.780562-11.513292 18.115547-7.382817-20.164523zM562.460092 923.665237l10.895347 18.440782-21.302843-2.569351-14.180212 16.099095-4.130475-21.042655-19.676672-8.521137 18.733493-10.440019 2.016452-21.36789 15.708814 14.603018 20.945085-4.683373-9.008989 19.48153zM242.787359 420.788058l-18.473305 10.895347 2.569351-21.302843-16.066572-14.180213 21.042656-4.130474 8.521137-19.676672 10.440019 18.733492 21.335366 2.016453-14.603018 15.708813 4.683374 20.945085-19.449008-9.008988z" fill="#8a8a8a" p-id="4334"></path><path d="M242.787359 420.788058l-18.473305 10.895347 2.569351-21.302843-16.066572-14.180213 21.042656-4.130474 8.521137-19.676672 10.440019 18.733492 21.335366 2.016453-14.603018 15.708813 4.683374 20.945085-19.449008-9.008988zM700.814737 943.959854l-17.400032-12.521518 20.522281-6.211974 6.504685-20.42471 12.26133 17.595172 21.432937-0.130094-12.944323 17.107321 6.732349 20.359663-20.262093-7.025059-17.269938 12.684135 0.422804-21.432936zM303.541115 278.823313l-21.140226 3.480006 10.114785-18.928633-9.854597-19.058726 21.107702 3.772717 15.090868-15.220962 2.927109 21.237796 19.156296 9.626933-19.28639 9.366746-3.252342 21.172749-14.863205-15.448626z" fill="#8a8a8a" p-id="4335"></path><path d="M407.648595 90.642782a486.713038 486.713038 0 0 1 504.568397 11.578339l25.010513-14.407877A512.081309 512.081309 0 0 0 139.850723 547.401747l24.977989-14.407877a486.778085 486.778085 0 0 1 242.819883-442.351088zM893.28836 933.422265a486.810608 486.810608 0 0 1-504.568398-11.610863l-25.010513 14.407877a512.081309 512.081309 0 0 0 797.5394-459.621026l-24.97799 14.505447a486.843132 486.843132 0 0 1-242.982499 442.318565z" fill="#8a8a8a" p-id="4336"></path><path d="M814.061299 795.880705a326.665269 326.665269 0 0 1-258.170939 29.563792l-29.791456 17.172368a353.236906 353.236906 0 0 0 472.793013-272.448721l-29.693886 17.172367a326.762839 326.762839 0 0 1-155.136732 208.540194zM486.875655 228.119295a326.795363 326.795363 0 0 1 258.170939-29.563792l29.791456-17.172368a353.236906 353.236906 0 0 0-472.793013 272.448721l29.82398-17.172367a326.762839 326.762839 0 0 1 155.006638-208.540194zM1288.350389 374.73489a53.923837 53.923837 0 0 1-14.34283 12.001143L229.420232 988.712085A53.793743 53.793743 0 0 1 156.112434 968.937843l-148.924757-258.235985a53.76122 53.76122 0 0 1 19.741718-73.437891L1071.516722 35.352962A53.826266 53.826266 0 0 1 1144.82452 55.062157l148.827187 258.268508a53.793743 53.793743 0 0 1-5.398888 61.404225zM32.19819 665.754486a28.360426 28.360426 0 0 0-5.626553 10.73273 28.067715 28.067715 0 0 0 2.699444 21.432936L178.195839 956.188661a28.165285 28.165285 0 0 0 38.442687 10.342449l1044.587328-601.976052a28.132762 28.132762 0 0 0 10.440019-38.442687l-148.924758-258.268509a28.197808 28.197808 0 0 0-38.442687-10.342449L39.711101 659.444942a28.230332 28.230332 0 0 0-7.512911 6.309544z" fill="#8a8a8a" p-id="4337"></path><path d="M498.941845 597.390249l-138.322121 79.877529 38.637827 66.933207q8.000762 13.854979 21.595554 5.98431l114.254788-65.957504a21.172749 21.172749 0 0 0 9.952167-11.123011q2.634397-9.757027-16.91218-47.321582l18.440781-4.130474q20.489757 43.22363 18.148071 56.167953a36.166047 36.166047 0 0 1-16.261712 19.514054l-123.068636 71.031158q-25.17313 14.603017-40.394092-11.77348L317.103383 639.020232l16.066571-9.269176 18.570875 32.133143 122.027886-70.47826-33.596697-Line truncated
|
||||
|
After Width: | Height: | Size: 7.4 KiB |
@@ -0,0 +1 @@
|
||||
<svg t="1729585232424" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1602" width="200" height="200"><path d="M925.5 898.9H804.9c-19 0-34.5-15.4-34.5-34.4V761.3c0-19 15.4-34.4 34.5-34.4h34.5V572.2c0-19-15.4-34.4-34.5-34.4H529.2V727h34.5c19 0 34.5 15.4 34.5 34.4v103.2c0 19-15.4 34.4-34.5 34.4H443.1c-19 0-34.5-15.4-34.5-34.4V761.3c0-19 15.4-34.4 34.5-34.4h34.5V537.8H219.1c-19 0-34.5 15.4-34.5 34.4V727h34.5c19 0 34.5 15.4 34.5 34.4v103.2c0 19-15.4 34.4-34.5 34.4H98.5c-19 0-34.5-15.4-34.5-34.4V761.3c0-19 15.4-34.4 34.5-34.4H133V555c0-38 30.9-68.8 68.9-68.8h275.7V297.1h-34.5c-19 0-34.5-15.4-34.5-34.4V159.5c0-19 15.4-34.4 34.5-34.4h120.6c19 0 34.5 15.4 34.5 34.4v103.2c0 19-15.4 34.4-34.5 34.4h-34.5v189.2h292.9c38.1 0 68.9 30.8 68.9 68.8v172h34.5c19 0 34.5 15.4 34.5 34.4v103.2c0 18.8-15.4 34.2-34.5 34.2z m0 0" p-id="1603" fill="#fff"></path></svg>
|
||||
|
After Width: | Height: | Size: 897 B |
@@ -0,0 +1 @@
|
||||
<svg t="1729649333541" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1644" width="200" height="200"><path d="M647.888 893.84L491.904 571.52l393.888-393.888-237.904 716.208zM872.32 123.232L459.872 535.68 134.96 380.88 872.32 123.232z m90.72-68.32a23.968 23.968 0 0 0-24.784-5.568L64.08 354.816a23.984 23.984 0 0 0-2.4 44.32l381.392 181.728 187.36 387.088a24.048 24.048 0 0 0 23.152 13.504 24.032 24.032 0 0 0 21.232-16.4L968.96 79.552c2.88-8.672 0.592-18.24-5.92-24.64z" fill="#fff" p-id="1645"></path></svg>
|
||||
|
After Width: | Height: | Size: 553 B |
@@ -0,0 +1 @@
|
||||
<svg t="1730189225011" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2651" id="mx_n_1730189225011" width="200" height="200"><path d="M793.889347 200.380242c27.648573 20.615681 42.196018 32.710677 63.781037 56.119312 25.313864 27.453234 43.242957 48.52047 64.502857 86.507991 44.537416 79.580127 53.527718 136.949077 53.517684 212.063821 0 64.933675-15.452562 130.459388-40.138263 187.311893-22.076044 50.841799-61.545336 104.359483-101.886297 138.933914-45.506755 39.001681-81.214423 60.462941-137.605337 81.826531-55.699867 21.102023-114.070267 28.641326-181.379458 27.791064-68.274516-0.862973-129.364283-11.040029-180.533878-31.80489-46.159002-18.731189-98.338744-46.827973-141.596418-87.541551-43.946046-41.361142-70.369064-75.958317-93.88139-127.198155-26.157437-57.004361-40.094111-129.065922-39.680686-191.781288 0-36.980719 4.033895-70.902234 12.252873-105.241856 8.532726-35.651474 20.069131-69.572989 38.13135-102.35257 18.856956-34.221214 36.754607-62.067803 58.869452-88.973149 23.248751-28.285434 39.2104-46.417894 64.295476-63.475987 18.297696-12.442861 36.879036-9.295353 47.199252-2.306612 4.403836 2.982273 8.919391 6.577992 12.933218 12.933217 9.572307 15.156208-0.334486 29.769212-6.69038 38.465836-7.148625 9.781026-23.130343 26.023643-38.738775 43.218205-38.192895 42.075603-55.133918 65.965228-74.986303 106.965794-30.772668 63.552249-37.495827 115.718611-38.131349 166.573791-0.668971 53.517684 9.995096 99.647251 27.427813 140.483919 33.916163 80.572211 94.807915 144.44289 175.270414 178.615938 41.108271 17.845472 113.812713 37.319888 181.960793 38.13135 56.193568 0.668971 125.919751-11.321666 166.574459-28.096784 45.935566-18.954626 97.223569-56.862539 127.10383-94.324918 23.013273-28.852721 52.179742-70.910931 64.413884-105.694749 14.863868-42.260239 24.806784-87.661297 24.559934-132.458943 0-54.414105-11.53373-108.417461-36.918505-156.856317-20.16747-38.483228-46.480777-74.607665-84.66899-108.048189-13.377414-11.714352-23.822728-20.067124-38.808348-31.619586-10.191774-7.857065-36.059546-25.027545-28.923632-47.326356 4.970455-15.53217 18.303717-25.294464 31.887843-27.205046 19.456354-2.736092 28.565733 2.427027 43.705885 12.041479l6.179955 4.322891zM510.755379 531.65738c-8.696624-0.668971-10.034566-0.446204-20.738102-6.689711-11.031333-6.434832-17.839451-21.183637-16.514219-35.175166V92.220334c0-18.178619 0.386665-22.815926 8.988295-31.685813 5.351768-5.519011 10.963097-11.381873 26.08987-11.539751 16.055305-0.167243 21.407073 3.846584 27.929542 9.700081 9.70677 8.711341 10.703537 17.56049 10.377078 33.525483v397.5715c-0.509756 15.273947 0.326458 22.967114-11.380535 33.502739-3.884046 3.495374-8.027653 7.693167-20.96087 8.362138l-3.791059 0.000669z m4.453341 0.573308" p-id="2652" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1 @@
|
||||
<svg t="1729585239190" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1755" width="200" height="200"><path d="M901.489435 536.822664v-0.931601l-1.001722-198.240726c-0.100172-19.162936-9.21584-37.474409-25.043042-50.246361-14.024104-11.349507-32.265456-17.60025-51.348255-17.610268l-618.062295-0.18031c-19.142902 0-37.424323 6.280795-51.478478 17.690405-15.827203 12.842072-24.902802 31.2437-24.892785 50.486775v196.798247A114.987635 114.987635 0 1 0 195.295664 536.922836V338.782282c1.15198-1.252152 4.808264-3.596181 10.768509-3.596181l276.725622 0.090155v199.753326a114.987635 114.987635 0 1 0 65.612772 1.412428V335.326342l275.693849 0.080138c6.01033 0 9.626546 2.344029 10.768508 3.596181l1.001722 195.70637a114.987635 114.987635 0 1 0 65.592737 2.113633zM214.979496 645.910158a56.437001 56.437001 0 1 1-56.437001-56.437001 56.507122 56.507122 0 0 1 56.437001 56.437001z m354.689623 0a56.437001 56.437001 0 1 1-56.437001-56.437001 56.507122 56.507122 0 0 1 56.437001 56.437001z m295.507904 56.437001a56.437001 56.437001 0 1 1 56.437001-56.437001 56.507122 56.507122 0 0 1-56.457035 56.437001z" p-id="1756" fill='#fff'></path></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="22" height="22" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#FAFAFA" d="M0 0h22v22H0z"/><circle fill="#919BAE" cx="1" cy="1" r="1"/></g></svg>
|
||||
|
After Width: | Height: | Size: 192 B |
@@ -0,0 +1 @@
|
||||
<svg t="1729561814171" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1359" width="200" height="200"><path d="M674.496 603.456c120.256 0 218.176 90.752 221.44 203.84l0.064 5.888v125.888c0 11.52-9.92 20.928-22.144 20.928h-44.352a21.568 21.568 0 0 1-22.144-20.928v-125.888c0-67.712-56.512-123.264-128-125.76l-4.928-0.064H349.568c-71.488 0-130.176 53.504-132.864 121.152l-0.064 4.672v125.888c0 11.52-9.92 20.928-22.144 20.928h-44.352A21.568 21.568 0 0 1 128 939.072v-125.888c0-113.92 95.872-206.528 215.36-209.664l6.208-0.064h324.928zM497.216 128c122.368 0 221.568 93.888 221.568 209.728s-99.2 209.792-221.568 209.792c-122.304 0-221.44-93.952-221.44-209.728C275.712 221.952 374.848 128 497.152 128z m0 83.904c-73.408 0-132.864 56.32-132.864 125.888 0 69.504 59.52 125.824 132.864 125.824 73.408 0 132.928-56.32 132.928-125.824 0-69.504-59.52-125.888-132.928-125.888z" fill="#fff" p-id="1360"></path></svg>
|
||||
|
After Width: | Height: | Size: 947 B |
@@ -2,17 +2,17 @@
|
||||
<div class="h-40px flex items-center justify-center">
|
||||
<MagicCubeEditor
|
||||
v-model="cellList"
|
||||
class="m-b-16px"
|
||||
:rows="1"
|
||||
:cols="cellCount"
|
||||
:cube-size="38"
|
||||
:rows="1"
|
||||
class="m-b-16px"
|
||||
@hot-area-selected="handleHotAreaSelected"
|
||||
/>
|
||||
<img src="@/assets/imgs/diy/app-nav-bar-mp.png" alt="" class="h-30px w-76px" v-if="isMp" />
|
||||
<img v-if="isMp" alt="" class="h-30px w-76px" src="@/assets/imgs/diy/app-nav-bar-mp.png" />
|
||||
</div>
|
||||
<template v-for="(cell, cellIndex) in cellList" :key="cellIndex">
|
||||
<template v-if="selectedHotAreaIndex === cellIndex">
|
||||
<el-form-item label="类型" :prop="`cell[${cellIndex}].type`">
|
||||
<el-form-item :prop="`cell[${cellIndex}].type`" label="类型">
|
||||
<el-radio-group v-model="cell.type">
|
||||
<el-radio value="text">文字</el-radio>
|
||||
<el-radio value="image">图片</el-radio>
|
||||
@@ -21,37 +21,40 @@
|
||||
</el-form-item>
|
||||
<!-- 1. 文字 -->
|
||||
<template v-if="cell.type === 'text'">
|
||||
<el-form-item label="内容" :prop="`cell[${cellIndex}].text`">
|
||||
<el-form-item :prop="`cell[${cellIndex}].text`" label="内容">
|
||||
<el-input v-model="cell!.text" maxlength="10" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="颜色" :prop="`cell[${cellIndex}].text`">
|
||||
<el-form-item :prop="`cell[${cellIndex}].text`" label="颜色">
|
||||
<ColorInput v-model="cell!.textColor" />
|
||||
</el-form-item>
|
||||
<el-form-item :prop="`cell[${cellIndex}].url`" label="链接">
|
||||
<AppLinkInput v-model="cell.url" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<!-- 2. 图片 -->
|
||||
<template v-else-if="cell.type === 'image'">
|
||||
<el-form-item label="图片" :prop="`cell[${cellIndex}].imgUrl`">
|
||||
<el-form-item :prop="`cell[${cellIndex}].imgUrl`" label="图片">
|
||||
<UploadImg v-model="cell.imgUrl" :limit="1" height="56px" width="56px">
|
||||
<template #tip>建议尺寸 56*56</template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
<el-form-item label="链接" :prop="`cell[${cellIndex}].url`">
|
||||
<el-form-item :prop="`cell[${cellIndex}].url`" label="链接">
|
||||
<AppLinkInput v-model="cell.url" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<!-- 3. 搜索框 -->
|
||||
<template v-else>
|
||||
<el-form-item label="提示文字" :prop="`cell[${cellIndex}].placeholder`">
|
||||
<el-form-item :prop="`cell[${cellIndex}].placeholder`" label="提示文字">
|
||||
<el-input v-model="cell.placeholder" maxlength="10" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="圆角" :prop="`cell[${cellIndex}].borderRadius`">
|
||||
<el-form-item :prop="`cell[${cellIndex}].borderRadius`" label="圆角">
|
||||
<el-slider
|
||||
v-model="cell.borderRadius"
|
||||
:max="100"
|
||||
:min="0"
|
||||
show-input
|
||||
input-size="small"
|
||||
:show-input-controls="false"
|
||||
input-size="small"
|
||||
show-input
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
@@ -59,7 +62,7 @@
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import { NavigationBarCellProperty } from '../config'
|
||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||
// 导航栏属性面板
|
||||
@@ -87,4 +90,4 @@ const handleHotAreaSelected = (cellValue: NavigationBarCellProperty, index: numb
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -1,9 +1,7 @@
|
||||
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
||||
import {ComponentStyle, DiyComponent} from '@/components/DiyEditor/util'
|
||||
|
||||
/** 标题栏属性 */
|
||||
export interface TitleBarProperty {
|
||||
// 背景图
|
||||
bgImgUrl: string
|
||||
// 偏移
|
||||
marginLeft: number
|
||||
// 显示位置
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
<template>
|
||||
<div class="title-bar">
|
||||
<el-image v-if="property.bgImgUrl" :src="property.bgImgUrl" fit="cover" class="w-full" />
|
||||
<div class="absolute left-0 top-0 w-full">
|
||||
<div
|
||||
:style="{
|
||||
background:
|
||||
property.style.bgType === 'color' ? property.style.bgColor : `url(${property.style.bgImg})`,
|
||||
backgroundSize: '100% 100%',
|
||||
backgroundRepeat: 'no-repeat'
|
||||
}"
|
||||
class="title-bar"
|
||||
>
|
||||
<!-- 内容 -->
|
||||
<div>
|
||||
<!-- 标题 -->
|
||||
<div
|
||||
v-if="property.title"
|
||||
:style="{
|
||||
fontSize: `${property.titleSize}px`,
|
||||
fontWeight: property.titleWeight,
|
||||
color: property.titleColor,
|
||||
textAlign: property.textAlign
|
||||
}"
|
||||
v-if="property.title"
|
||||
>
|
||||
{{ property.title }}
|
||||
</div>
|
||||
<!-- 副标题 -->
|
||||
<div
|
||||
v-if="property.description"
|
||||
:style="{
|
||||
fontSize: `${property.descriptionSize}px`,
|
||||
fontWeight: property.descriptionWeight,
|
||||
@@ -23,25 +32,24 @@
|
||||
textAlign: property.textAlign
|
||||
}"
|
||||
class="m-t-8px"
|
||||
v-if="property.description"
|
||||
>
|
||||
{{ property.description }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 更多 -->
|
||||
<div
|
||||
class="more"
|
||||
v-show="property.more.show"
|
||||
:style="{
|
||||
color: property.descriptionColor
|
||||
}"
|
||||
class="more"
|
||||
>
|
||||
<span v-if="property.more.type !== 'icon'"> {{ property.more.text }} </span>
|
||||
<Icon icon="ep:arrow-right" v-if="property.more.type !== 'text'" />
|
||||
<Icon v-if="property.more.type !== 'text'" icon="ep:arrow-right" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import { TitleBarProperty } from './config'
|
||||
|
||||
/** 标题栏 */
|
||||
@@ -49,7 +57,7 @@ defineOptions({ name: 'TitleBar' })
|
||||
|
||||
defineProps<{ property: TitleBarProperty }>()
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.title-bar {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
<template>
|
||||
<ComponentContainerProperty v-model="formData.style">
|
||||
<el-form label-width="85px" :model="formData" :rules="rules">
|
||||
<el-card header="风格" class="property-group" shadow="never">
|
||||
<el-form-item label="背景图片" prop="bgImgUrl">
|
||||
<UploadImg v-model="formData.bgImgUrl" width="100%" height="40px">
|
||||
<template #tip>建议尺寸 750*80</template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
<el-form :model="formData" :rules="rules" label-width="85px">
|
||||
<el-card class="property-group" header="风格" shadow="never">
|
||||
<el-form-item label="标题位置" prop="textAlign">
|
||||
<el-radio-group v-model="formData!.textAlign">
|
||||
<el-tooltip content="居左" placement="top">
|
||||
@@ -22,65 +17,65 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card header="主标题" class="property-group" shadow="never">
|
||||
<el-form-item label="文字" prop="title" label-width="40px">
|
||||
<el-card class="property-group" header="主标题" shadow="never">
|
||||
<el-form-item label="文字" label-width="40px" prop="title">
|
||||
<InputWithColor
|
||||
v-model="formData.title"
|
||||
v-model:color="formData.titleColor"
|
||||
show-word-limit
|
||||
maxlength="20"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="大小" prop="titleSize" label-width="40px">
|
||||
<el-form-item label="大小" label-width="40px" prop="titleSize">
|
||||
<el-slider
|
||||
v-model="formData.titleSize"
|
||||
:max="60"
|
||||
:min="10"
|
||||
show-input
|
||||
input-size="small"
|
||||
show-input
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="粗细" prop="titleWeight" label-width="40px">
|
||||
<el-form-item label="粗细" label-width="40px" prop="titleWeight">
|
||||
<el-slider
|
||||
v-model="formData.titleWeight"
|
||||
:min="100"
|
||||
:max="900"
|
||||
:min="100"
|
||||
:step="100"
|
||||
show-input
|
||||
input-size="small"
|
||||
show-input
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card header="副标题" class="property-group" shadow="never">
|
||||
<el-form-item label="文字" prop="description" label-width="40px">
|
||||
<el-card class="property-group" header="副标题" shadow="never">
|
||||
<el-form-item label="文字" label-width="40px" prop="description">
|
||||
<InputWithColor
|
||||
v-model="formData.description"
|
||||
v-model:color="formData.descriptionColor"
|
||||
show-word-limit
|
||||
maxlength="50"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="大小" prop="descriptionSize" label-width="40px">
|
||||
<el-form-item label="大小" label-width="40px" prop="descriptionSize">
|
||||
<el-slider
|
||||
v-model="formData.descriptionSize"
|
||||
:max="60"
|
||||
:min="10"
|
||||
show-input
|
||||
input-size="small"
|
||||
show-input
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="粗细" prop="descriptionWeight" label-width="40px">
|
||||
<el-form-item label="粗细" label-width="40px" prop="descriptionWeight">
|
||||
<el-slider
|
||||
v-model="formData.descriptionWeight"
|
||||
:min="100"
|
||||
:max="900"
|
||||
:min="100"
|
||||
:step="100"
|
||||
show-input
|
||||
input-size="small"
|
||||
show-input
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card header="查看更多" class="property-group" shadow="never">
|
||||
<el-card class="property-group" header="查看更多" shadow="never">
|
||||
<el-form-item label="是否显示" prop="more.show">
|
||||
<el-checkbox v-model="formData.more.show" />
|
||||
</el-form-item>
|
||||
@@ -93,7 +88,7 @@
|
||||
<el-radio value="all">文字+图标</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="更多文字" prop="more.text" v-show="formData.more.type !== 'icon'">
|
||||
<el-form-item v-show="formData.more.type !== 'icon'" label="更多文字" prop="more.text">
|
||||
<el-input v-model="formData.more.text" />
|
||||
</el-form-item>
|
||||
<el-form-item label="跳转链接" prop="more.url">
|
||||
@@ -104,7 +99,7 @@
|
||||
</el-form>
|
||||
</ComponentContainerProperty>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import { TitleBarProperty } from './config'
|
||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||
// 导航栏属性面板
|
||||
@@ -118,4 +113,4 @@ const { formData } = usePropertyForm(props.modelValue, emit)
|
||||
const rules = {}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -12,17 +12,17 @@
|
||||
<el-button-group class="header-right">
|
||||
<el-tooltip content="重置">
|
||||
<el-button @click="handleReset">
|
||||
<Icon icon="system-uicons:reset-alt" :size="24" />
|
||||
<Icon :size="24" icon="system-uicons:reset-alt" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="预览" v-if="previewUrl">
|
||||
<el-tooltip v-if="previewUrl" content="预览">
|
||||
<el-button @click="handlePreview">
|
||||
<Icon icon="ep:view" :size="24" />
|
||||
<Icon :size="24" icon="ep:view" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="保存">
|
||||
<el-button @click="handleSave">
|
||||
<Icon icon="ep:check" :size="24" />
|
||||
<Icon :size="24" icon="ep:check" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</el-button-group>
|
||||
@@ -31,21 +31,21 @@
|
||||
<!-- 中心区域 -->
|
||||
<el-container class="editor-container">
|
||||
<!-- 左侧:组件库(ComponentLibrary) -->
|
||||
<ComponentLibrary ref="componentLibrary" :list="libs" v-if="libs && libs.length > 0" />
|
||||
<ComponentLibrary v-if="libs && libs.length > 0" ref="componentLibrary" :list="libs" />
|
||||
<!-- 中心:设计区域(ComponentContainer) -->
|
||||
<div class="editor-center page-prop-area" @click="handlePageSelected">
|
||||
<!-- 手机顶部 -->
|
||||
<div class="editor-design-top">
|
||||
<!-- 手机顶部状态栏 -->
|
||||
<img src="@/assets/imgs/diy/statusBar.png" alt="" class="status-bar" />
|
||||
<img alt="" class="status-bar" src="@/assets/imgs/diy/statusBar.png" />
|
||||
<!-- 手机顶部导航栏 -->
|
||||
<ComponentContainer
|
||||
v-if="showNavigationBar"
|
||||
:active="selectedComponent?.id === navigationBarComponent.id"
|
||||
:component="navigationBarComponent"
|
||||
:show-toolbar="false"
|
||||
:active="selectedComponent?.id === navigationBarComponent.id"
|
||||
@click="handleNavigationBarSelected"
|
||||
class="cursor-pointer!"
|
||||
@click="handleNavigationBarSelected"
|
||||
/>
|
||||
</div>
|
||||
<!-- 绝对定位的组件:例如 弹窗、浮动按钮等 -->
|
||||
@@ -55,43 +55,43 @@
|
||||
@click="handleComponentSelected(component, index)"
|
||||
>
|
||||
<component
|
||||
v-if="component.position === 'fixed' && selectedComponent?.uid === component.uid"
|
||||
:is="component.id"
|
||||
v-if="component.position === 'fixed' && selectedComponent?.uid === component.uid"
|
||||
:property="component.property"
|
||||
/>
|
||||
</div>
|
||||
<!-- 手机页面编辑区域 -->
|
||||
<el-scrollbar
|
||||
height="100%"
|
||||
wrap-class="editor-design-center page-prop-area"
|
||||
view-class="phone-container"
|
||||
:view-style="{
|
||||
backgroundColor: pageConfigComponent.property.backgroundColor,
|
||||
backgroundImage: `url(${pageConfigComponent.property.backgroundImage})`
|
||||
}"
|
||||
height="100%"
|
||||
view-class="phone-container"
|
||||
wrap-class="editor-design-center page-prop-area"
|
||||
>
|
||||
<draggable
|
||||
class="page-prop-area drag-area"
|
||||
v-model="pageComponents"
|
||||
item-key="index"
|
||||
:animation="200"
|
||||
:force-fallback="true"
|
||||
class="page-prop-area drag-area"
|
||||
filter=".component-toolbar"
|
||||
ghost-class="draggable-ghost"
|
||||
:force-fallback="true"
|
||||
group="component"
|
||||
item-key="index"
|
||||
@change="handleComponentChange"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<ComponentContainer
|
||||
v-if="!element.position || element.position === 'center'"
|
||||
:component="element"
|
||||
:active="selectedComponentIndex === index"
|
||||
:can-move-up="index > 0"
|
||||
:can-move-down="index < pageComponents.length - 1"
|
||||
@move="(direction) => handleMoveComponent(index, direction)"
|
||||
:can-move-up="index > 0"
|
||||
:component="element"
|
||||
@click="handleComponentSelected(element, index)"
|
||||
@copy="handleCopyComponent(index)"
|
||||
@delete="handleDeleteComponent(index)"
|
||||
@click="handleComponentSelected(element, index)"
|
||||
@move="(direction) => handleMoveComponent(index, direction)"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
@@ -99,9 +99,9 @@
|
||||
<!-- 手机底部导航 -->
|
||||
<div v-if="showTabBar" :class="['editor-design-bottom', 'component', 'cursor-pointer!']">
|
||||
<ComponentContainer
|
||||
:active="selectedComponent?.id === tabBarComponent.id"
|
||||
:component="tabBarComponent"
|
||||
:show-toolbar="false"
|
||||
:active="selectedComponent?.id === tabBarComponent.id"
|
||||
@click="handleTabBarSelected"
|
||||
/>
|
||||
</div>
|
||||
@@ -109,9 +109,9 @@
|
||||
<div class="fixed-component-action-group">
|
||||
<el-tag
|
||||
v-if="showPageConfig"
|
||||
size="large"
|
||||
:effect="selectedComponent?.uid === pageConfigComponent.uid ? 'dark' : 'plain'"
|
||||
:type="selectedComponent?.uid === pageConfigComponent.uid ? '' : 'info'"
|
||||
size="large"
|
||||
@click="handleComponentSelected(pageConfigComponent)"
|
||||
>
|
||||
<Icon :icon="pageConfigComponent.icon" :size="12" />
|
||||
@@ -120,10 +120,10 @@
|
||||
<template v-for="(component, index) in pageComponents" :key="index">
|
||||
<el-tag
|
||||
v-if="component.position === 'fixed'"
|
||||
size="large"
|
||||
closable
|
||||
:effect="selectedComponent?.uid === component.uid ? 'dark' : 'plain'"
|
||||
:type="selectedComponent?.uid === component.uid ? '' : 'info'"
|
||||
closable
|
||||
size="large"
|
||||
@click="handleComponentSelected(component)"
|
||||
@close="handleDeleteComponent(index)"
|
||||
>
|
||||
@@ -134,11 +134,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧:属性面板(ComponentContainerProperty) -->
|
||||
<el-aside class="editor-right" width="350px" v-if="selectedComponent?.property">
|
||||
<el-aside v-if="selectedComponent?.property" class="editor-right" width="350px">
|
||||
<el-card
|
||||
shadow="never"
|
||||
body-class="h-[calc(100%-var(--el-card-padding)-var(--el-card-padding))]"
|
||||
class="h-full"
|
||||
shadow="never"
|
||||
>
|
||||
<!-- 组件名称 -->
|
||||
<template #header>
|
||||
@@ -152,8 +152,8 @@
|
||||
view-class="p-[var(--el-card-padding)] p-b-[calc(var(--el-card-padding)+var(--el-card-padding))] property"
|
||||
>
|
||||
<component
|
||||
:key="selectedComponent?.uid || selectedComponent?.id"
|
||||
:is="selectedComponent?.id + 'Property'"
|
||||
:key="selectedComponent?.uid || selectedComponent?.id"
|
||||
v-model="selectedComponent.property"
|
||||
/>
|
||||
</el-scrollbar>
|
||||
@@ -166,8 +166,8 @@
|
||||
<Dialog v-model="previewDialogVisible" title="预览" width="700">
|
||||
<div class="flex justify-around">
|
||||
<IFrame
|
||||
class="w-375px border-4px border-rounded-8px border-solid p-2px h-667px!"
|
||||
:src="previewUrl"
|
||||
class="w-375px border-4px border-rounded-8px border-solid p-2px h-667px!"
|
||||
/>
|
||||
<div class="flex flex-col">
|
||||
<el-text>手机扫码预览</el-text>
|
||||
@@ -179,6 +179,7 @@
|
||||
<script lang="ts">
|
||||
// 注册所有的组件
|
||||
import { components } from './components/mobile/index'
|
||||
|
||||
export default {
|
||||
components: { ...components }
|
||||
}
|
||||
@@ -257,6 +258,11 @@ watch(
|
||||
|
||||
// 保存
|
||||
const handleSave = () => {
|
||||
// 发送保存通知
|
||||
emits('save')
|
||||
}
|
||||
// 监听配置修改
|
||||
const pageConfigChange = () => {
|
||||
const pageConfig = {
|
||||
page: pageConfigComponent.value.property,
|
||||
navigationBar: navigationBarComponent.value.property,
|
||||
@@ -272,10 +278,19 @@ const handleSave = () => {
|
||||
// 发送数据更新通知
|
||||
const modelValue = isString(props.modelValue) ? JSON.stringify(pageConfig) : pageConfig
|
||||
emits('update:modelValue', modelValue)
|
||||
// 发送保存通知
|
||||
emits('save', pageConfig)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [
|
||||
pageConfigComponent.value.property,
|
||||
navigationBarComponent.value.property,
|
||||
tabBarComponent.value.property,
|
||||
pageComponents.value
|
||||
],
|
||||
() => {
|
||||
pageConfigChange()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
// 处理页面选中:显示属性表单
|
||||
const handlePageSelected = (event: any) => {
|
||||
if (!props.showPageConfig) return
|
||||
@@ -547,6 +562,7 @@ $toolbar-height: 42px;
|
||||
:deep(.el-tag) {
|
||||
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1);
|
||||
border: none;
|
||||
|
||||
.el-tag__content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { propTypes } from '@/utils/propTypes'
|
||||
import { isNumber } from '@/utils/is'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useLocaleStore } from '@/store/modules/locale'
|
||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
||||
import { getRefreshToken, getTenantId } from '@/utils/auth'
|
||||
import { getUploadUrl } from '@/components/UploadFile/src/useUpload'
|
||||
|
||||
defineOptions({ name: 'Editor' })
|
||||
@@ -100,7 +100,7 @@ const editorConfig = computed((): IEditorConfig => {
|
||||
// 自定义增加 http header
|
||||
headers: {
|
||||
Accept: '*',
|
||||
Authorization: 'Bearer ' + getAccessToken(),
|
||||
Authorization: 'Bearer ' + getRefreshToken(), // 使用 getRefreshToken() 方法,而不使用 getAccessToken() 方法的原因:Editor 无法方便的刷新访问令牌
|
||||
'tenant-id': getTenantId()
|
||||
},
|
||||
|
||||
@@ -148,7 +148,7 @@ const editorConfig = computed((): IEditorConfig => {
|
||||
// 自定义增加 http header
|
||||
headers: {
|
||||
Accept: '*',
|
||||
Authorization: 'Bearer ' + getAccessToken(),
|
||||
Authorization: 'Bearer ' + getRefreshToken(), // 使用 getRefreshToken() 方法,而不使用 getAccessToken() 方法的原因:Editor 无法方便的刷新访问令牌
|
||||
'tenant-id': getTenantId()
|
||||
},
|
||||
|
||||
|
||||
@@ -185,7 +185,6 @@ export const useApiSelect = (option: ApiSelectProps) => {
|
||||
</el-select>
|
||||
)
|
||||
}
|
||||
debugger
|
||||
return (
|
||||
<el-select
|
||||
class="w-1/1"
|
||||
|
||||
@@ -16,3 +16,46 @@ export const localeProps = (t, prefix, rules) => {
|
||||
return rule
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析表单组件的 field, title 等字段(递归,如果组件包含子组件)
|
||||
*
|
||||
* @param rule 组件的生成规则 https://www.form-create.com/v3/guide/rule
|
||||
* @param fields 解析后表单组件字段
|
||||
* @param parentTitle 如果是子表单,子表单的标题,默认为空
|
||||
*/
|
||||
export const parseFormFields = (
|
||||
rule: Record<string, any>,
|
||||
fields: Array<Record<string, any>> = [],
|
||||
parentTitle: string = ''
|
||||
) => {
|
||||
const { type, field, $required, title: tempTitle, children } = rule
|
||||
if (field && tempTitle) {
|
||||
let title = tempTitle
|
||||
if (parentTitle) {
|
||||
title = `${parentTitle}.${tempTitle}`
|
||||
}
|
||||
let required = false
|
||||
if ($required) {
|
||||
required = true
|
||||
}
|
||||
fields.push({
|
||||
field,
|
||||
title,
|
||||
type,
|
||||
required
|
||||
})
|
||||
// TODO 子表单 需要处理子表单字段
|
||||
// if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
|
||||
// // 解析子表单的字段
|
||||
// rule.props.rule.forEach((item) => {
|
||||
// parseFields(item, fieldsPermission, title)
|
||||
// })
|
||||
// }
|
||||
}
|
||||
if (children && Array.isArray(children)) {
|
||||
children.forEach((rule) => {
|
||||
parseFormFields(rule, fields)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
<div v-else class="custom-hover" @click.stop="showTopSearch = !showTopSearch">
|
||||
<Icon icon="ep:search" />
|
||||
<el-select
|
||||
@click.stop
|
||||
filterable
|
||||
:reserve-keyword="false"
|
||||
remote
|
||||
@@ -98,6 +99,8 @@ onUnmounted(() => {
|
||||
// 监听 ctrl + k
|
||||
function listenKey(event) {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
|
||||
// 阻止触发浏览器默认事件
|
||||
event.preventDefault()
|
||||
showSearch.value = !showSearch.value
|
||||
// 这里可以执行相应的操作(例如打开搜索框等)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<div class="node-handler-wrapper">
|
||||
<div class="node-handler" v-if="props.showAdd">
|
||||
<div class="node-handler">
|
||||
<el-popover
|
||||
trigger="hover"
|
||||
v-model:visible="popoverShow"
|
||||
placement="right-start"
|
||||
width="auto"
|
||||
v-if="!readonly"
|
||||
>
|
||||
<div class="handler-item-wrapper">
|
||||
<div class="handler-item" @click="addNode(NodeType.USER_TASK_NODE)">
|
||||
@@ -27,11 +28,17 @@
|
||||
<div class="handler-item-text">条件分支</div>
|
||||
</div>
|
||||
<div class="handler-item" @click="addNode(NodeType.PARALLEL_BRANCH_NODE)">
|
||||
<div class="handler-item-icon condition">
|
||||
<div class="handler-item-icon parallel">
|
||||
<span class="iconfont icon-size icon-parallel"></span>
|
||||
</div>
|
||||
<div class="handler-item-text">并行分支</div>
|
||||
</div>
|
||||
<div class="handler-item" @click="addNode(NodeType.INCLUSIVE_BRANCH_NODE)">
|
||||
<div class="handler-item-icon inclusive">
|
||||
<span class="iconfont icon-size icon-inclusive"></span>
|
||||
</div>
|
||||
<div class="handler-item-text">包容分支</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #reference>
|
||||
<div class="add-icon"><Icon icon="ep:plus" /></div>
|
||||
@@ -56,23 +63,36 @@ import { generateUUID } from '@/utils'
|
||||
defineOptions({
|
||||
name: 'NodeHandler'
|
||||
})
|
||||
const popoverShow = ref(false)
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const popoverShow = ref(false)
|
||||
const props = defineProps({
|
||||
childNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
default: null
|
||||
},
|
||||
showAdd: {
|
||||
// 是否显示添加节点
|
||||
type: Boolean,
|
||||
default: true
|
||||
currentNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emits = defineEmits(['update:childNode'])
|
||||
|
||||
const readonly = inject<Boolean>('readonly') // 是否只读
|
||||
|
||||
const addNode = (type: number) => {
|
||||
// 校验:条件分支、包容分支后面,不允许直接添加并行分支
|
||||
if (
|
||||
type === NodeType.PARALLEL_BRANCH_NODE &&
|
||||
[NodeType.CONDITION_BRANCH_NODE, NodeType.INCLUSIVE_BRANCH_NODE].includes(
|
||||
props.currentNode?.type
|
||||
)
|
||||
) {
|
||||
message.error('条件分支、包容分支后面,不允许直接添加并行分支')
|
||||
return
|
||||
}
|
||||
|
||||
popoverShow.value = false
|
||||
if (type === NodeType.USER_TASK_NODE) {
|
||||
const id = 'Activity_' + generateUUID()
|
||||
@@ -122,12 +142,11 @@ const addNode = (type: number) => {
|
||||
childNode: undefined,
|
||||
conditionType: 1,
|
||||
defaultFlow: false
|
||||
|
||||
},
|
||||
{
|
||||
id: 'Flow_' + generateUUID(),
|
||||
name: '其它情况',
|
||||
showText: '其它情况进入此流程',
|
||||
showText: '未满足其它条件时,将进入此分支',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
conditionType: undefined,
|
||||
@@ -162,6 +181,33 @@ const addNode = (type: number) => {
|
||||
}
|
||||
emits('update:childNode', data)
|
||||
}
|
||||
if (type === NodeType.INCLUSIVE_BRANCH_NODE) {
|
||||
const data: SimpleFlowNode = {
|
||||
name: '包容分支',
|
||||
type: NodeType.INCLUSIVE_BRANCH_NODE,
|
||||
id: 'GateWay_' + generateUUID(),
|
||||
childNode: props.childNode,
|
||||
conditionNodes: [
|
||||
{
|
||||
id: 'Flow_' + generateUUID(),
|
||||
name: '包容条件1',
|
||||
showText: '',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
defaultFlow: false
|
||||
},
|
||||
{
|
||||
id: 'Flow_' + generateUUID(),
|
||||
name: '其它情况',
|
||||
showText: '未满足其它条件时,将进入此分支',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
defaultFlow: true
|
||||
}
|
||||
]
|
||||
}
|
||||
emits('update:childNode', data)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -31,6 +31,13 @@
|
||||
@update:model-value="handleModelValueUpdate"
|
||||
@find:parent-node="findFromParentNode"
|
||||
/>
|
||||
<!-- 包容分支节点 -->
|
||||
<InclusiveNode
|
||||
v-if="currentNode && currentNode.type === NodeType.INCLUSIVE_BRANCH_NODE"
|
||||
:flow-node="currentNode"
|
||||
@update:model-value="handleModelValueUpdate"
|
||||
@find:parent-node="findFromParentNode"
|
||||
/>
|
||||
<!-- 递归显示孩子节点 -->
|
||||
<ProcessNodeTree
|
||||
v-if="currentNode && currentNode.childNode"
|
||||
@@ -40,7 +47,10 @@
|
||||
/>
|
||||
|
||||
<!-- 结束节点 -->
|
||||
<EndEventNode v-if="currentNode && currentNode.type === NodeType.END_EVENT_NODE" />
|
||||
<EndEventNode
|
||||
v-if="currentNode && currentNode.type === NodeType.END_EVENT_NODE"
|
||||
:flow-node="currentNode"
|
||||
/>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import StartUserNode from './nodes/StartUserNode.vue'
|
||||
@@ -49,6 +59,7 @@ import UserTaskNode from './nodes/UserTaskNode.vue'
|
||||
import CopyTaskNode from './nodes/CopyTaskNode.vue'
|
||||
import ExclusiveNode from './nodes/ExclusiveNode.vue'
|
||||
import ParallelNode from './nodes/ParallelNode.vue'
|
||||
import InclusiveNode from './nodes/InclusiveNode.vue'
|
||||
import { SimpleFlowNode, NodeType } from './consts'
|
||||
import { useWatchNode } from './node'
|
||||
defineOptions({
|
||||
|
||||
@@ -1,23 +1,12 @@
|
||||
<template>
|
||||
<div class="simple-flow-canvas" v-loading="loading">
|
||||
<div class="simple-flow-container">
|
||||
<div class="top-area-container">
|
||||
<div class="top-actions">
|
||||
<div class="canvas-control">
|
||||
<span class="control-scale-group">
|
||||
<span class="control-scale-button"> <Icon icon="ep:plus" @click="zoomOut()" /></span>
|
||||
<span class="control-scale-label">{{ scaleValue }}%</span>
|
||||
<span class="control-scale-button"><Icon icon="ep:minus" @click="zoomIn()" /></span>
|
||||
</span>
|
||||
</div>
|
||||
<el-button type="primary" @click="saveSimpleFlowModel">保存</el-button>
|
||||
<!-- <el-button type="primary">全局设置</el-button> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="scale-container" :style="`transform: scale(${scaleValue / 100});`">
|
||||
<ProcessNodeTree v-if="processNodeTree" v-model:flow-node="processNodeTree" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-loading="loading" class="overflow-auto">
|
||||
<SimpleProcessModel
|
||||
ref="simpleProcessModelRef"
|
||||
v-if="processNodeTree"
|
||||
:flow-node="processNodeTree"
|
||||
:readonly="false"
|
||||
@save="saveSimpleFlowModel"
|
||||
/>
|
||||
<Dialog v-model="errorDialogVisible" title="保存失败" width="400" :fullscreen="false">
|
||||
<div class="mb-2">以下节点内容不完善,请修改后保存</div>
|
||||
<div
|
||||
@@ -35,7 +24,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ProcessNodeTree from './ProcessNodeTree.vue'
|
||||
import SimpleProcessModel from './SimpleProcessModel.vue'
|
||||
import { updateBpmSimpleModel, getBpmSimpleModel } from '@/api/bpm/simple'
|
||||
import { SimpleFlowNode, NodeType, NodeId, NODE_DEFAULT_TEXT } from './consts'
|
||||
import { getModel } from '@/api/bpm/model'
|
||||
@@ -50,14 +39,34 @@ import * as UserGroupApi from '@/api/bpm/userGroup'
|
||||
defineOptions({
|
||||
name: 'SimpleProcessDesigner'
|
||||
})
|
||||
const router = useRouter() // 路由
|
||||
|
||||
const emits = defineEmits(['success']) // 保存成功事件
|
||||
|
||||
const props = defineProps({
|
||||
modelId: {
|
||||
type: String,
|
||||
required: true
|
||||
required: false
|
||||
},
|
||||
modelKey: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
modelName: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
// 可发起流程的人员编号
|
||||
startUserIds : {
|
||||
type: Array,
|
||||
required: false
|
||||
},
|
||||
value: {
|
||||
type: [String, Object],
|
||||
required: false
|
||||
}
|
||||
})
|
||||
const loading = ref(true)
|
||||
|
||||
const loading = ref(false)
|
||||
const formFields = ref<string[]>([])
|
||||
const formType = ref(20)
|
||||
const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
|
||||
@@ -66,6 +75,10 @@ const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表
|
||||
const deptTreeOptions = ref()
|
||||
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
|
||||
|
||||
// 添加当前值的引用
|
||||
const currentValue = ref<SimpleFlowNode | undefined>()
|
||||
|
||||
provide('formFields', formFields)
|
||||
provide('formType', formType)
|
||||
provide('roleList', roleOptions)
|
||||
@@ -74,35 +87,101 @@ provide('userList', userOptions)
|
||||
provide('deptList', deptOptions)
|
||||
provide('userGroupList', userGroupOptions)
|
||||
provide('deptTree', deptTreeOptions)
|
||||
provide('startUserIds', props.startUserIds)
|
||||
|
||||
const message = useMessage() // 国际化
|
||||
const processNodeTree = ref<SimpleFlowNode | undefined>()
|
||||
const errorDialogVisible = ref(false)
|
||||
let errorNodes: SimpleFlowNode[] = []
|
||||
const saveSimpleFlowModel = async () => {
|
||||
if (!props.modelId) {
|
||||
message.error('缺少模型 modelId 编号')
|
||||
|
||||
// 添加更新模型的方法
|
||||
const updateModel = () => {
|
||||
if (!processNodeTree.value) {
|
||||
processNodeTree.value = {
|
||||
name: '发起人',
|
||||
type: NodeType.START_USER_NODE,
|
||||
id: NodeId.START_USER_NODE_ID,
|
||||
childNode: {
|
||||
id: NodeId.END_EVENT_NODE_ID,
|
||||
name: '结束',
|
||||
type: NodeType.END_EVENT_NODE
|
||||
}
|
||||
}
|
||||
// 初始化时也触发一次保存
|
||||
saveSimpleFlowModel(processNodeTree.value)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载流程数据
|
||||
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) => {
|
||||
if (!simpleModelNode) {
|
||||
return
|
||||
}
|
||||
|
||||
// 校验节点
|
||||
errorNodes = []
|
||||
validateNode(processNodeTree.value, errorNodes)
|
||||
validateNode(simpleModelNode, errorNodes)
|
||||
if (errorNodes.length > 0) {
|
||||
errorDialogVisible.value = true
|
||||
return
|
||||
}
|
||||
const data = {
|
||||
id: props.modelId,
|
||||
simpleModel: processNodeTree.value
|
||||
}
|
||||
|
||||
const result = await updateBpmSimpleModel(data)
|
||||
if (result) {
|
||||
message.success('修改成功')
|
||||
close()
|
||||
} else {
|
||||
message.alert('修改失败')
|
||||
try {
|
||||
if (props.modelId) {
|
||||
// 编辑模式
|
||||
const data = {
|
||||
id: props.modelId,
|
||||
simpleModel: simpleModelNode
|
||||
}
|
||||
await updateBpmSimpleModel(data)
|
||||
}
|
||||
// 无论是编辑还是新建模式,都更新当前值并触发事件
|
||||
currentValue.value = simpleModelNode
|
||||
emits('success', simpleModelNode)
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 校验节点设置。 暂时以 showText 为空 未节点错误配置
|
||||
const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => {
|
||||
if (node) {
|
||||
@@ -111,68 +190,49 @@ const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNo
|
||||
return
|
||||
}
|
||||
if (type == NodeType.START_USER_NODE) {
|
||||
// 发起人节点暂时不用校验,直接校验孩子节点
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
|
||||
if (type === NodeType.USER_TASK_NODE) {
|
||||
if (!showText) {
|
||||
errorNodes.push(node)
|
||||
}
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
if (type === NodeType.COPY_TASK_NODE) {
|
||||
if (!showText) {
|
||||
errorNodes.push(node)
|
||||
}
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
if (type === NodeType.CONDITION_NODE) {
|
||||
if (
|
||||
type === NodeType.USER_TASK_NODE ||
|
||||
type === NodeType.COPY_TASK_NODE ||
|
||||
type === NodeType.CONDITION_NODE
|
||||
) {
|
||||
if (!showText) {
|
||||
errorNodes.push(node)
|
||||
}
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
|
||||
if (type == NodeType.CONDITION_BRANCH_NODE) {
|
||||
if (
|
||||
type == NodeType.CONDITION_BRANCH_NODE ||
|
||||
type == NodeType.PARALLEL_BRANCH_NODE ||
|
||||
type == NodeType.INCLUSIVE_BRANCH_NODE
|
||||
) {
|
||||
// 分支节点
|
||||
// 1. 先校验各个分支
|
||||
conditionNodes?.forEach((item) => {
|
||||
validateNode(item, errorNodes)
|
||||
})
|
||||
// 2. 校验孩子节点
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
router.push({ path: '/bpm/manager/model' })
|
||||
}
|
||||
let scaleValue = ref(100)
|
||||
const MAX_SCALE_VALUE = 200
|
||||
const MIN_SCALE_VALUE = 50
|
||||
// 放大
|
||||
const zoomOut = () => {
|
||||
if (scaleValue.value == MAX_SCALE_VALUE) {
|
||||
return
|
||||
}
|
||||
scaleValue.value += 10
|
||||
}
|
||||
// 缩小
|
||||
const zoomIn = () => {
|
||||
if (scaleValue.value == MIN_SCALE_VALUE) {
|
||||
return
|
||||
}
|
||||
scaleValue.value -= 10
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
// 获取表单字段
|
||||
const bpmnModel = await getModel(props.modelId)
|
||||
if (bpmnModel) {
|
||||
formType.value = bpmnModel.formType
|
||||
if (formType.value === 10) {
|
||||
const bpmnForm = (await getForm(bpmnModel.formId)) as unknown as FormVO
|
||||
formFields.value = bpmnForm?.fields
|
||||
if (props.modelId) {
|
||||
const bpmnModel = await getModel(props.modelId)
|
||||
if (bpmnModel) {
|
||||
formType.value = bpmnModel.formType
|
||||
if (formType.value === 10) {
|
||||
const bpmnForm = (await getForm(bpmnModel.formId)) as unknown as FormVO
|
||||
formFields.value = bpmnForm?.fields
|
||||
}
|
||||
}
|
||||
}
|
||||
// 获得角色列表
|
||||
@@ -183,30 +243,63 @@ onMounted(async () => {
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
// 获得部门列表
|
||||
deptOptions.value = await DeptApi.getSimpleDeptList()
|
||||
|
||||
deptTreeOptions.value = handleTree(deptOptions.value as DeptApi.DeptVO[], 'id')
|
||||
// 获取用户组列表
|
||||
userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
|
||||
|
||||
// 获取 SIMPLE 设计器模型
|
||||
const result = await getBpmSimpleModel(props.modelId)
|
||||
if (result) {
|
||||
processNodeTree.value = result
|
||||
} else {
|
||||
// 初始值
|
||||
processNodeTree.value = {
|
||||
name: '发起人',
|
||||
type: NodeType.START_USER_NODE,
|
||||
id: NodeId.START_USER_NODE_ID,
|
||||
childNode: {
|
||||
id: NodeId.END_EVENT_NODE_ID,
|
||||
name: '结束',
|
||||
type: NodeType.END_EVENT_NODE
|
||||
}
|
||||
// 加载流程数据
|
||||
if (props.modelId) {
|
||||
// 获取 SIMPLE 设计器模型
|
||||
const result = await getBpmSimpleModel(props.modelId)
|
||||
if (result) {
|
||||
await loadProcessData(result)
|
||||
} else {
|
||||
updateModel()
|
||||
}
|
||||
} else if (props.value) {
|
||||
await loadProcessData(props.value)
|
||||
} else {
|
||||
updateModel()
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const simpleProcessModelRef = ref()
|
||||
|
||||
/** 获取当前流程数据 */
|
||||
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>
|
||||
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<div class="simple-process-model-container position-relative">
|
||||
<div class="position-absolute top-0px right-0px bg-#fff">
|
||||
<el-row type="flex" justify="end">
|
||||
<el-button-group key="scale-control" size="default">
|
||||
<el-button size="default" :icon="ScaleToOriginal" @click="processReZoom()" />
|
||||
<el-button size="default" :plain="true" :icon="ZoomOut" @click="zoomOut()" />
|
||||
<el-button size="default" class="w-80px"> {{ scaleValue }}% </el-button>
|
||||
<el-button size="default" :plain="true" :icon="ZoomIn" @click="zoomIn()" />
|
||||
</el-button-group>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="simple-process-model" :style="`transform: scale(${scaleValue / 100});`">
|
||||
<ProcessNodeTree v-if="processNodeTree" v-model:flow-node="processNodeTree" />
|
||||
</div>
|
||||
</div>
|
||||
<Dialog v-model="errorDialogVisible" title="保存失败" width="400" :fullscreen="false">
|
||||
<div class="mb-2">以下节点内容不完善,请修改后保存</div>
|
||||
<div
|
||||
class="mb-3 b-rounded-1 bg-gray-100 p-2 line-height-normal"
|
||||
v-for="(item, index) in errorNodes"
|
||||
:key="index"
|
||||
>
|
||||
{{ item.name }} : {{ NODE_DEFAULT_TEXT.get(item.type) }}
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="errorDialogVisible = false">知道了</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ProcessNodeTree from './ProcessNodeTree.vue'
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from './consts'
|
||||
import { useWatchNode } from './node'
|
||||
import { ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'SimpleProcessModel'
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
required: true
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
|
||||
const emits = defineEmits<{
|
||||
'save': [node: SimpleFlowNode | undefined]
|
||||
}>()
|
||||
|
||||
const processNodeTree = useWatchNode(props)
|
||||
|
||||
provide('readonly', props.readonly)
|
||||
let scaleValue = ref(100)
|
||||
const MAX_SCALE_VALUE = 200
|
||||
const MIN_SCALE_VALUE = 50
|
||||
|
||||
// 放大
|
||||
const zoomIn = () => {
|
||||
if (scaleValue.value == MAX_SCALE_VALUE) {
|
||||
return
|
||||
}
|
||||
scaleValue.value += 10
|
||||
}
|
||||
|
||||
// 缩小
|
||||
const zoomOut = () => {
|
||||
if (scaleValue.value == MIN_SCALE_VALUE) {
|
||||
return
|
||||
}
|
||||
scaleValue.value -= 10
|
||||
}
|
||||
|
||||
const processReZoom = () => {
|
||||
scaleValue.value = 100
|
||||
}
|
||||
|
||||
const errorDialogVisible = ref(false)
|
||||
let errorNodes: SimpleFlowNode[] = []
|
||||
|
||||
// 校验节点设置。 暂时以 showText 为空 未节点错误配置
|
||||
const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => {
|
||||
if (node) {
|
||||
const { type, showText, conditionNodes } = node
|
||||
if (type == NodeType.END_EVENT_NODE) {
|
||||
return
|
||||
}
|
||||
if (type == NodeType.START_USER_NODE) {
|
||||
// 发起人节点暂时不用校验,直接校验孩子节点
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
|
||||
if (
|
||||
type === NodeType.USER_TASK_NODE ||
|
||||
type === NodeType.COPY_TASK_NODE ||
|
||||
type === NodeType.CONDITION_NODE
|
||||
) {
|
||||
if (!showText) {
|
||||
errorNodes.push(node)
|
||||
}
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
|
||||
if (
|
||||
type == NodeType.CONDITION_BRANCH_NODE ||
|
||||
type == NodeType.PARALLEL_BRANCH_NODE ||
|
||||
type == NodeType.INCLUSIVE_BRANCH_NODE
|
||||
) {
|
||||
// 分支节点
|
||||
// 1. 先校验各个分支
|
||||
conditionNodes?.forEach((item) => {
|
||||
validateNode(item, errorNodes)
|
||||
})
|
||||
// 2. 校验孩子节点
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取当前流程数据 */
|
||||
const getCurrentFlowData = async () => {
|
||||
try {
|
||||
errorNodes = []
|
||||
validateNode(processNodeTree.value, errorNodes)
|
||||
if (errorNodes.length > 0) {
|
||||
errorDialogVisible.value = true
|
||||
return undefined
|
||||
}
|
||||
return processNodeTree.value
|
||||
} catch (error) {
|
||||
console.error('获取流程数据失败:', error)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
getCurrentFlowData
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<SimpleProcessModel :flow-node="simpleModel" :readonly="true" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useWatchNode } from './node'
|
||||
import { SimpleFlowNode } from './consts'
|
||||
|
||||
defineOptions({
|
||||
name: 'SimpleProcessViewer'
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
required: true
|
||||
},
|
||||
// 流程任务
|
||||
tasks: {
|
||||
type: Array,
|
||||
default: () => [] as any[]
|
||||
},
|
||||
// 流程实例
|
||||
processInstance: {
|
||||
type: Object,
|
||||
default: () => undefined
|
||||
}
|
||||
})
|
||||
const approveTasks = ref<any[]>(props.tasks)
|
||||
const currentProcessInstance = ref(props.processInstance)
|
||||
const simpleModel = useWatchNode(props)
|
||||
watch(
|
||||
() => props.tasks,
|
||||
(newValue) => {
|
||||
approveTasks.value = newValue
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => props.processInstance,
|
||||
(newValue) => {
|
||||
currentProcessInstance.value = newValue
|
||||
}
|
||||
)
|
||||
|
||||
provide('tasks', approveTasks)
|
||||
provide('processInstance', currentProcessInstance)
|
||||
</script>
|
||||
p
|
||||
@@ -1,6 +1,6 @@
|
||||
// @ts-ignore
|
||||
import { DictDataVO } from '@/api/system/dict/types'
|
||||
|
||||
import { TaskStatusEnum } from '@/api/bpm/task'
|
||||
/**
|
||||
* 节点类型
|
||||
*/
|
||||
@@ -79,7 +79,7 @@ export interface SimpleFlowNode {
|
||||
// 审批按钮设置
|
||||
buttonsSetting?: any[]
|
||||
// 表单权限
|
||||
fieldsPermission?: Array<Record<string, string>>
|
||||
fieldsPermission?: Array<Record<string, any>>
|
||||
// 审批任务超时处理
|
||||
timeoutHandler?: TimeoutHandler
|
||||
// 审批任务拒绝处理
|
||||
@@ -96,7 +96,8 @@ export interface SimpleFlowNode {
|
||||
conditionGroups?: ConditionGroup
|
||||
// 是否默认的条件
|
||||
defaultFlow?: boolean
|
||||
|
||||
// 活动的状态,用于前端节点状态展示
|
||||
activityStatus?: TaskStatusEnum
|
||||
}
|
||||
// 候选人策略枚举 ( 用于审批节点。抄送节点 )
|
||||
export enum CandidateStrategy {
|
||||
@@ -144,6 +145,14 @@ export enum CandidateStrategy {
|
||||
* 指定用户组
|
||||
*/
|
||||
USER_GROUP = 40,
|
||||
/**
|
||||
* 表单内用户字段
|
||||
*/
|
||||
FORM_USER = 50,
|
||||
/**
|
||||
* 表单内部门负责人
|
||||
*/
|
||||
FORM_DEPT_LEADER = 51,
|
||||
/**
|
||||
* 流程表达式
|
||||
*/
|
||||
@@ -178,7 +187,7 @@ export enum ApproveMethodType {
|
||||
export type RejectHandler = {
|
||||
// 审批拒绝类型
|
||||
type: RejectHandlerType
|
||||
// 回退节点 Id
|
||||
// 退回节点 Id
|
||||
returnNodeId?: string
|
||||
}
|
||||
|
||||
@@ -360,9 +369,13 @@ export enum OperationButtonType {
|
||||
*/
|
||||
ADD_SIGN = 5,
|
||||
/**
|
||||
* 回退
|
||||
* 退回
|
||||
*/
|
||||
RETURN = 6
|
||||
RETURN = 6,
|
||||
/**
|
||||
* 抄送
|
||||
*/
|
||||
COPY = 7
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -419,6 +432,8 @@ export const CANDIDATE_STRATEGY: DictDataVO[] = [
|
||||
{ label: '发起人部门负责人', value: CandidateStrategy.START_USER_DEPT_LEADER },
|
||||
{ label: '发起人连续部门负责人', value: CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER },
|
||||
{ label: '用户组', value: CandidateStrategy.USER_GROUP },
|
||||
{ label: '表单内用户字段', value: CandidateStrategy.FORM_USER },
|
||||
{ label: '表单内部门负责人', value: CandidateStrategy.FORM_DEPT_LEADER },
|
||||
{ label: '流程表达式', value: CandidateStrategy.EXPRESSION }
|
||||
]
|
||||
// 审批节点 的审批类型
|
||||
@@ -503,16 +518,17 @@ OPERATION_BUTTON_NAME.set(OperationButtonType.REJECT, '拒绝')
|
||||
OPERATION_BUTTON_NAME.set(OperationButtonType.TRANSFER, '转办')
|
||||
OPERATION_BUTTON_NAME.set(OperationButtonType.DELEGATE, '委派')
|
||||
OPERATION_BUTTON_NAME.set(OperationButtonType.ADD_SIGN, '加签')
|
||||
OPERATION_BUTTON_NAME.set(OperationButtonType.RETURN, '回退')
|
||||
OPERATION_BUTTON_NAME.set(OperationButtonType.RETURN, '退回')
|
||||
OPERATION_BUTTON_NAME.set(OperationButtonType.COPY, '抄送')
|
||||
|
||||
// 默认的按钮权限设置
|
||||
export const DEFAULT_BUTTON_SETTING: ButtonSetting[] = [
|
||||
{ id: OperationButtonType.APPROVE, displayName: '通过', enable: true },
|
||||
{ id: OperationButtonType.REJECT, displayName: '拒绝', enable: true },
|
||||
{ id: OperationButtonType.TRANSFER, displayName: '转办', enable: false },
|
||||
{ id: OperationButtonType.DELEGATE, displayName: '委派', enable: false },
|
||||
{ id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: false },
|
||||
{ id: OperationButtonType.RETURN, displayName: '回退', enable: false }
|
||||
{ id: OperationButtonType.TRANSFER, displayName: '转办', enable: true },
|
||||
{ id: OperationButtonType.DELEGATE, displayName: '委派', enable: true },
|
||||
{ id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: true },
|
||||
{ id: OperationButtonType.RETURN, displayName: '退回', enable: true }
|
||||
]
|
||||
|
||||
// 发起人的按钮权限。暂时定死,不可以编辑
|
||||
@@ -522,7 +538,7 @@ export const START_USER_BUTTON_SETTING: ButtonSetting[] = [
|
||||
{ id: OperationButtonType.TRANSFER, displayName: '转办', enable: false },
|
||||
{ id: OperationButtonType.DELEGATE, displayName: '委派', enable: false },
|
||||
{ id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: false },
|
||||
{ id: OperationButtonType.RETURN, displayName: '回退', enable: false }
|
||||
{ id: OperationButtonType.RETURN, displayName: '退回', enable: false }
|
||||
]
|
||||
|
||||
export const MULTI_LEVEL_DEPT: DictDataVO = [
|
||||
@@ -542,3 +558,13 @@ export const MULTI_LEVEL_DEPT: DictDataVO = [
|
||||
{ label: '第 14 级部门', value: 14 },
|
||||
{ label: '第 15 级部门', value: 15 }
|
||||
]
|
||||
|
||||
/**
|
||||
* 流程实例的变量枚举
|
||||
*/
|
||||
export enum ProcessVariableEnum {
|
||||
/**
|
||||
* 发起用户 ID
|
||||
*/
|
||||
START_USER_ID = 'PROCESS_START_USER_ID'
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import SimpleProcessDesigner from './SimpleProcessDesigner.vue'
|
||||
import SimpleProcessViewer from './SimpleProcessViewer.vue'
|
||||
import '../theme/simple-process-designer.scss'
|
||||
|
||||
export { SimpleProcessDesigner }
|
||||
export { SimpleProcessDesigner, SimpleProcessViewer}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { TaskStatusEnum } from '@/api/bpm/task'
|
||||
import * as RoleApi from '@/api/system/role'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import * as PostApi from '@/api/system/post'
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
AssignEmptyHandlerType,
|
||||
FieldPermissionType
|
||||
} from './consts'
|
||||
import { parseFormFields } from '@/components/FormCreate/src/utils/index'
|
||||
export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> {
|
||||
const node = ref<SimpleFlowNode>(props.flowNode)
|
||||
watch(
|
||||
@@ -26,12 +28,23 @@ export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlo
|
||||
return node
|
||||
}
|
||||
|
||||
// 解析 formCreate 所有表单字段, 并返回
|
||||
const parseFormCreateFields = (formFields?: string[]) => {
|
||||
const result: Array<Record<string, any>> = []
|
||||
if (formFields) {
|
||||
formFields.forEach((fieldStr: string) => {
|
||||
parseFormFields(JSON.parse(fieldStr), result)
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 表单数据权限配置,用于发起人节点 、审批节点、抄送节点
|
||||
*/
|
||||
export function useFormFieldsPermission(defaultPermission: FieldPermissionType) {
|
||||
// 字段权限配置. 需要有 field, title, permissioin 属性
|
||||
const fieldsPermissionConfig = ref<Array<Record<string, string>>>([])
|
||||
const fieldsPermissionConfig = ref<Array<Record<string, any>>>([])
|
||||
|
||||
const formType = inject<Ref<number>>('formType') // 表单类型
|
||||
|
||||
@@ -39,54 +52,55 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType)
|
||||
|
||||
const getNodeConfigFormFields = (nodeFormFields?: Array<Record<string, string>>) => {
|
||||
nodeFormFields = toRaw(nodeFormFields)
|
||||
fieldsPermissionConfig.value =
|
||||
cloneDeep(nodeFormFields) || getDefaultFieldsPermission(unref(formFields))
|
||||
if (!nodeFormFields || nodeFormFields.length === 0) {
|
||||
fieldsPermissionConfig.value = getDefaultFieldsPermission(unref(formFields))
|
||||
} else {
|
||||
fieldsPermissionConfig.value = mergeFieldsPermission(nodeFormFields, unref(formFields))
|
||||
}
|
||||
}
|
||||
// 合并已经设置的表单字段权限,当前流程表单字段 (可能新增,或删除了字段)
|
||||
const mergeFieldsPermission = (
|
||||
formFieldsPermisson: Array<Record<string, string>>,
|
||||
formFields?: string[]
|
||||
) => {
|
||||
let mergedFieldsPermission: Array<Record<string, any>> = []
|
||||
if (formFields) {
|
||||
mergedFieldsPermission = parseFormCreateFields(formFields).map((item) => {
|
||||
const found = formFieldsPermisson.find(
|
||||
(fieldPermission) => fieldPermission.field == item.field
|
||||
)
|
||||
return {
|
||||
field: item.field,
|
||||
title: item.title,
|
||||
permission: found ? found.permission : defaultPermission
|
||||
}
|
||||
})
|
||||
}
|
||||
return mergedFieldsPermission
|
||||
}
|
||||
|
||||
// 默认的表单权限: 获取表单的所有字段,设置字段默认权限为只读
|
||||
const getDefaultFieldsPermission = (formFields?: string[]) => {
|
||||
const defaultFieldsPermission: Array<Record<string, string>> = []
|
||||
let defaultFieldsPermission: Array<Record<string, any>> = []
|
||||
if (formFields) {
|
||||
formFields.forEach((fieldStr: string) => {
|
||||
parseFieldsSetDefaultPermission(JSON.parse(fieldStr), defaultFieldsPermission)
|
||||
defaultFieldsPermission = parseFormCreateFields(formFields).map((item) => {
|
||||
return {
|
||||
field: item.field,
|
||||
title: item.title,
|
||||
permission: defaultPermission
|
||||
}
|
||||
})
|
||||
}
|
||||
return defaultFieldsPermission
|
||||
}
|
||||
// 解析字段。赋给默认权限
|
||||
const parseFieldsSetDefaultPermission = (
|
||||
rule: Record<string, any>,
|
||||
fieldsPermission: Array<Record<string, string>>,
|
||||
parentTitle: string = ''
|
||||
) => {
|
||||
const { /**type,*/ field, title: tempTitle, children } = rule
|
||||
if (field && tempTitle) {
|
||||
let title = tempTitle
|
||||
if (parentTitle) {
|
||||
title = `${parentTitle}.${tempTitle}`
|
||||
}
|
||||
fieldsPermission.push({
|
||||
field,
|
||||
title,
|
||||
permission: defaultPermission
|
||||
})
|
||||
// TODO 子表单 需要处理子表单字段
|
||||
// if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
|
||||
// // 解析子表单的字段
|
||||
// rule.props.rule.forEach((item) => {
|
||||
// parseFieldsSetDefaultPermission(item, fieldsPermission, title)
|
||||
// })
|
||||
// }
|
||||
}
|
||||
if (children && Array.isArray(children)) {
|
||||
children.forEach((rule) => {
|
||||
parseFieldsSetDefaultPermission(rule, fieldsPermission)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 获取表单的所有字段,作为下拉框选项
|
||||
const formFieldOptions = parseFormCreateFields(unref(formFields))
|
||||
|
||||
return {
|
||||
formType,
|
||||
fieldsPermissionConfig,
|
||||
formFieldOptions,
|
||||
getNodeConfigFormFields
|
||||
}
|
||||
}
|
||||
@@ -94,50 +108,8 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType)
|
||||
* @description 获取表单的字段
|
||||
*/
|
||||
export function useFormFields() {
|
||||
// 解析后的表单字段
|
||||
const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
|
||||
const parseFormFields = () => {
|
||||
const parsedFormFields: Array<Record<string, string>> = []
|
||||
if (formFields) {
|
||||
formFields.value.forEach((fieldStr: string) => {
|
||||
parseField(JSON.parse(fieldStr), parsedFormFields)
|
||||
})
|
||||
}
|
||||
return parsedFormFields
|
||||
}
|
||||
// 解析字段。
|
||||
const parseField = (
|
||||
rule: Record<string, any>,
|
||||
parsedFormFields: Array<Record<string, string>>,
|
||||
parentTitle: string = ''
|
||||
) => {
|
||||
const { field, title: tempTitle, children, type } = rule
|
||||
if (field && tempTitle) {
|
||||
let title = tempTitle
|
||||
if (parentTitle) {
|
||||
title = `${parentTitle}.${tempTitle}`
|
||||
}
|
||||
parsedFormFields.push({
|
||||
field,
|
||||
title,
|
||||
type
|
||||
})
|
||||
// TODO 子表单 需要处理子表单字段
|
||||
// if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
|
||||
// // 解析子表单的字段
|
||||
// rule.props.rule.forEach((item) => {
|
||||
// parseFieldsSetDefaultPermission(item, fieldsPermission, title)
|
||||
// })
|
||||
// }
|
||||
}
|
||||
if (children && Array.isArray(children)) {
|
||||
children.forEach((rule) => {
|
||||
parseField(rule, parsedFormFields)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return parseFormFields()
|
||||
return parseFormCreateFields(unref(formFields))
|
||||
}
|
||||
|
||||
export type UserTaskFormType = {
|
||||
@@ -151,6 +123,8 @@ export type UserTaskFormType = {
|
||||
userGroups?: number[] // 用户组
|
||||
postIds?: number[] // 岗位
|
||||
expression?: string // 流程表达式
|
||||
formUser?: string // 表单内用户字段
|
||||
formDept?: string // 表单内部门字段
|
||||
approveRatio?: number
|
||||
rejectHandlerType?: RejectHandlerType
|
||||
returnNodeId?: string
|
||||
@@ -173,6 +147,8 @@ export type CopyTaskFormType = {
|
||||
userIds?: number[] // 用户
|
||||
userGroups?: number[] // 用户组
|
||||
postIds?: number[] // 岗位
|
||||
formUser?: string // 表单内用户字段
|
||||
formDept?: string // 表单内部门字段
|
||||
expression?: string // 流程表达式
|
||||
}
|
||||
|
||||
@@ -186,6 +162,7 @@ export function useNodeForm(nodeType: NodeType) {
|
||||
const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList') // 部门列表
|
||||
const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList') // 用户组列表
|
||||
const deptTreeOptions = inject('deptTree') // 部门树
|
||||
const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
|
||||
const configForm = ref<UserTaskFormType | CopyTaskFormType>()
|
||||
if (nodeType === NodeType.USER_TASK_NODE) {
|
||||
configForm.value = {
|
||||
@@ -281,6 +258,18 @@ export function useNodeForm(nodeType: NodeType) {
|
||||
}
|
||||
}
|
||||
|
||||
// 表单内用户字段
|
||||
if (configForm.value?.candidateStrategy === CandidateStrategy.FORM_USER) {
|
||||
const formFieldOptions = parseFormCreateFields(unref(formFields))
|
||||
const item = formFieldOptions.find((item) => item.field === configForm.value?.formUser)
|
||||
showText = `表单用户:${item?.title}`
|
||||
}
|
||||
|
||||
// 表单内部门负责人
|
||||
if (configForm.value?.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER) {
|
||||
showText = `表单内部门负责人`
|
||||
}
|
||||
|
||||
// 发起人自选
|
||||
if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_SELECT) {
|
||||
showText = `发起人自选`
|
||||
@@ -327,6 +316,9 @@ export function useNodeForm(nodeType: NodeType) {
|
||||
case CandidateStrategy.USER_GROUP:
|
||||
candidateParam = configForm.value.userGroups!.join(',')
|
||||
break
|
||||
case CandidateStrategy.FORM_USER:
|
||||
candidateParam = configForm.value.formUser!
|
||||
break
|
||||
case CandidateStrategy.EXPRESSION:
|
||||
candidateParam = configForm.value.expression!
|
||||
break
|
||||
@@ -346,6 +338,13 @@ export function useNodeForm(nodeType: NodeType) {
|
||||
candidateParam = deptIds.concat('|' + configForm.value.deptLevel + '')
|
||||
break
|
||||
}
|
||||
// 表单内部门的负责人
|
||||
case CandidateStrategy.FORM_DEPT_LEADER: {
|
||||
// 候选人参数格式: | 分隔 。左边为表单内部门字段。 右边为部门层级
|
||||
const deptFieldOnForm = configForm.value.formDept!
|
||||
candidateParam = deptFieldOnForm.concat('|' + configForm.value.deptLevel + '')
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -375,6 +374,9 @@ export function useNodeForm(nodeType: NodeType) {
|
||||
case CandidateStrategy.USER_GROUP:
|
||||
configForm.value.userGroups = candidateParam.split(',').map((item) => +item)
|
||||
break
|
||||
case CandidateStrategy.FORM_USER:
|
||||
configForm.value.formUser = candidateParam
|
||||
break
|
||||
case CandidateStrategy.EXPRESSION:
|
||||
configForm.value.expression = candidateParam
|
||||
break
|
||||
@@ -395,6 +397,14 @@ export function useNodeForm(nodeType: NodeType) {
|
||||
configForm.value.deptLevel = +paramArray[1]
|
||||
break
|
||||
}
|
||||
// 表单内的部门负责人
|
||||
case CandidateStrategy.FORM_DEPT_LEADER: {
|
||||
// 候选人参数格式: | 分隔 。左边为表单内的部门字段。 右边为部门层级
|
||||
const paramArray = candidateParam.split('|')
|
||||
configForm.value.formDept = paramArray[0]
|
||||
configForm.value.deptLevel = +paramArray[1]
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -476,3 +486,26 @@ export function useNodeName2(node: Ref<SimpleFlowNode>, nodeType: NodeType) {
|
||||
blurEvent
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 根据节点任务状态,获取节点任务状态样式
|
||||
*/
|
||||
export function useTaskStatusClass(taskStatus: TaskStatusEnum | undefined): string {
|
||||
if (!taskStatus) {
|
||||
return ''
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.APPROVE) {
|
||||
return 'status-pass'
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.RUNNING) {
|
||||
return 'status-running'
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.REJECT) {
|
||||
return 'status-reject'
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.CANCEL) {
|
||||
return 'status-cancel'
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
@@ -26,19 +26,13 @@
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<div class="mb-3 font-size-16px" v-if="currentNode.defaultFlow">其它条件不满足进入此分支(该分支不可编辑和删除)</div>
|
||||
<div class="mb-3 font-size-16px" v-if="currentNode.defaultFlow"
|
||||
>未满足其它条件时,将进入此分支(该分支不可编辑和删除)</div
|
||||
>
|
||||
<div v-else>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="currentNode"
|
||||
:rules="formRules"
|
||||
label-position="top"
|
||||
>
|
||||
<el-form ref="formRef" :model="currentNode" :rules="formRules" label-position="top">
|
||||
<el-form-item label="配置方式" prop="conditionType">
|
||||
<el-radio-group
|
||||
v-model="currentNode.conditionType"
|
||||
@change="changeConditionType"
|
||||
>
|
||||
<el-radio-group v-model="currentNode.conditionType" @change="changeConditionType">
|
||||
<el-radio
|
||||
v-for="(dict, index) in conditionConfigTypes"
|
||||
:key="index"
|
||||
@@ -108,10 +102,11 @@
|
||||
<div class="mr-2">
|
||||
<el-select style="width: 160px" v-model="rule.leftSide">
|
||||
<el-option
|
||||
v-for="(item, index) in fieldsInfo"
|
||||
v-for="(item, index) in fieldOptions"
|
||||
:key="index"
|
||||
:label="item.title"
|
||||
:value="item.field"
|
||||
:disabled="!item.required"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
@@ -165,10 +160,12 @@ import {
|
||||
COMPARISON_OPERATORS,
|
||||
ConditionGroup,
|
||||
Condition,
|
||||
ConditionRule
|
||||
ConditionRule,
|
||||
ProcessVariableEnum
|
||||
} from '../consts'
|
||||
import { getDefaultConditionNodeName } from '../utils'
|
||||
import { useFormFields } from '../node'
|
||||
import { BpmModelFormType } from '@/utils/constants'
|
||||
const message = useMessage() // 消息弹窗
|
||||
defineOptions({
|
||||
name: 'ConditionNodeConfig'
|
||||
@@ -177,8 +174,8 @@ const formType = inject<Ref<number>>('formType') // 表单类型
|
||||
const conditionConfigTypes = computed(() => {
|
||||
return CONDITION_CONFIG_TYPES.filter((item) => {
|
||||
// 业务表单暂时去掉条件规则选项
|
||||
if (formType?.value !== 10) {
|
||||
return item.value === ConditionType.RULE
|
||||
if (formType?.value === BpmModelFormType.CUSTOM && item.value === ConditionType.RULE) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
@@ -368,16 +365,29 @@ const addConditionRule = (condition: Condition, idx: number) => {
|
||||
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 item = fieldsInfo.find((item) => item.field === field)
|
||||
const item = fieldOptions.value.find((item) => item.field === field)
|
||||
return item?.title
|
||||
}
|
||||
|
||||
/** 获取操作符名称 */
|
||||
const getOpName = (opCode: string): string => {
|
||||
const opName = COMPARISON_OPERATORS.find((item) => item.value === opCode)
|
||||
const opName = COMPARISON_OPERATORS.find((item: any) => item.value === opCode)
|
||||
return opName?.label
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -60,7 +60,8 @@
|
||||
<el-form-item
|
||||
v-if="
|
||||
configForm.candidateStrategy == CandidateStrategy.DEPT_MEMBER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.DEPT_LEADER
|
||||
configForm.candidateStrategy == CandidateStrategy.DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
|
||||
"
|
||||
label="指定部门"
|
||||
prop="deptIds"
|
||||
@@ -122,7 +123,57 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy === CandidateStrategy.FORM_USER"
|
||||
label="表单内用户字段"
|
||||
prop="formUser"
|
||||
>
|
||||
<el-select v-model="configForm.formUser" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="(item, idx) in userFieldOnFormOptions"
|
||||
:key="idx"
|
||||
:label="item.title"
|
||||
:value="item.field"
|
||||
:disabled ="!item.required"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER"
|
||||
label="表单内部门字段"
|
||||
prop="formDept"
|
||||
>
|
||||
<el-select v-model="configForm.formDept" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="(item, idx) in deptFieldOnFormOptions"
|
||||
:key="idx"
|
||||
:label="item.title"
|
||||
:value="item.field"
|
||||
:disabled ="!item.required"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="
|
||||
configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
|
||||
configForm.candidateStrategy ==
|
||||
CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
|
||||
"
|
||||
:label="deptLevelLabel!"
|
||||
prop="deptLevel"
|
||||
span="24"
|
||||
>
|
||||
<el-select v-model="configForm.deptLevel" clearable>
|
||||
<el-option
|
||||
v-for="(item, index) in MULTI_LEVEL_DEPT"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy === CandidateStrategy.EXPRESSION"
|
||||
label="流程表达式"
|
||||
@@ -201,7 +252,8 @@ import {
|
||||
CandidateStrategy,
|
||||
NodeType,
|
||||
CANDIDATE_STRATEGY,
|
||||
FieldPermissionType
|
||||
FieldPermissionType,
|
||||
MULTI_LEVEL_DEPT
|
||||
} from '../consts'
|
||||
import {
|
||||
useWatchNode,
|
||||
@@ -221,6 +273,15 @@ const props = defineProps({
|
||||
required: true
|
||||
}
|
||||
})
|
||||
const deptLevelLabel = computed(() => {
|
||||
let label = '部门负责人来源'
|
||||
if (configForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) {
|
||||
label = label + '(指定部门向上)'
|
||||
} else {
|
||||
label = label + '(发起人部门向上)'
|
||||
}
|
||||
return label
|
||||
})
|
||||
// 抽屉配置
|
||||
const { settingVisible, closeDrawer, openDrawer } = useDrawer()
|
||||
// 当前节点
|
||||
@@ -230,9 +291,16 @@ const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.COPY_
|
||||
// 激活的 Tab 标签页
|
||||
const activeTabName = ref('user')
|
||||
// 表单字段权限配置
|
||||
const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission(
|
||||
FieldPermissionType.READ
|
||||
)
|
||||
const { formType, fieldsPermissionConfig, formFieldOptions, getNodeConfigFormFields } =
|
||||
useFormFieldsPermission(FieldPermissionType.READ)
|
||||
// 表单内用户字段选项, 必须是必填和用户选择器
|
||||
const userFieldOnFormOptions = computed(() => {
|
||||
return formFieldOptions.filter((item) => item.type === 'UserSelect')
|
||||
})
|
||||
// 表单内部门字段选项, 必须是必填和部门选择器
|
||||
const deptFieldOnFormOptions = computed(() => {
|
||||
return formFieldOptions.filter((item) => item.type === 'DeptSelect')
|
||||
})
|
||||
// 抄送人表单配置
|
||||
const formRef = ref() // 表单 Ref
|
||||
// 表单校验规则
|
||||
@@ -243,6 +311,8 @@ const formRules = reactive({
|
||||
deptIds: [{ required: true, message: '部门不能为空', trigger: 'change' }],
|
||||
userGroups: [{ required: true, message: '用户组不能为空', trigger: 'change' }],
|
||||
postIds: [{ required: true, message: '岗位不能为空', trigger: 'change' }],
|
||||
formUser: [{ required: true, message: '表单内用户字段不能为空', trigger: 'change' }],
|
||||
formDept: [{ required: true, message: '表单内部门字段不能为空', trigger: 'change' }],
|
||||
expression: [{ required: true, message: '流程表达式不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
@@ -260,11 +330,7 @@ const {
|
||||
const configForm = tempConfigForm as Ref<CopyTaskFormType>
|
||||
// 抄送人策略, 去掉发起人自选 和 发起人自己
|
||||
const copyUserStrategies = computed(() => {
|
||||
return CANDIDATE_STRATEGY.filter(
|
||||
(item) =>
|
||||
item.value !== CandidateStrategy.START_USER_SELECT &&
|
||||
item.value !== CandidateStrategy.START_USER
|
||||
)
|
||||
return CANDIDATE_STRATEGY.filter((item) => item.value !== CandidateStrategy.START_USER)
|
||||
})
|
||||
// 改变抄送人设置策略
|
||||
const changeCandidateStrategy = () => {
|
||||
@@ -274,6 +340,7 @@ const changeCandidateStrategy = () => {
|
||||
configForm.value.postIds = []
|
||||
configForm.value.userGroups = []
|
||||
configForm.value.deptLevel = 1
|
||||
configForm.value.formUser = ''
|
||||
}
|
||||
// 保存配置
|
||||
const saveConfig = async () => {
|
||||
|
||||
@@ -25,7 +25,20 @@
|
||||
</template>
|
||||
<el-tabs type="border-card" v-model="activeTabName">
|
||||
<el-tab-pane label="权限" name="user">
|
||||
<div> 待实现 </div>
|
||||
<el-text v-if="!startUserIds || startUserIds.length === 0"> 全部成员可以发起流程 </el-text>
|
||||
<el-text v-else-if="startUserIds.length == 1">
|
||||
{{ getUserNicknames(startUserIds) }} 可发起流程
|
||||
</el-text>
|
||||
<el-text v-else>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
placement="top"
|
||||
:content="getUserNicknames(startUserIds)"
|
||||
>
|
||||
{{ getUserNicknames(startUserIds.slice(0,2)) }} 等 {{ startUserIds.length }} 人可发起流程
|
||||
</el-tooltip>
|
||||
</el-text>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="表单字段权限" name="fields" v-if="formType === 10">
|
||||
<div class="field-setting-pane">
|
||||
@@ -86,7 +99,7 @@
|
||||
<script setup lang="ts">
|
||||
import { SimpleFlowNode, NodeType, FieldPermissionType, START_USER_BUTTON_SETTING } from '../consts'
|
||||
import { useWatchNode, useDrawer, useNodeName, useFormFieldsPermission } from '../node'
|
||||
|
||||
import * as UserApi from '@/api/system/user'
|
||||
defineOptions({
|
||||
name: 'StartUserNodeConfig'
|
||||
})
|
||||
@@ -96,6 +109,10 @@ const props = defineProps({
|
||||
required: true
|
||||
}
|
||||
})
|
||||
// 可发起流程的用户编号
|
||||
const startUserIds = inject<Ref<any[]>>('startUserIds')
|
||||
// 用户列表
|
||||
const userOptions = inject<Ref<UserApi.UserVO[]>>('userList')
|
||||
// 抽屉配置
|
||||
const { settingVisible, closeDrawer, openDrawer } = useDrawer()
|
||||
// 当前节点
|
||||
@@ -108,18 +125,28 @@ const activeTabName = ref('user')
|
||||
const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission(
|
||||
FieldPermissionType.WRITE
|
||||
)
|
||||
|
||||
const getUserNicknames = (userIds: number[]): string => {
|
||||
if (!userIds || userIds.length === 0) {
|
||||
return ''
|
||||
}
|
||||
const nicknames: string[] = []
|
||||
userIds.forEach((userId) => {
|
||||
const found = userOptions?.value.find((item) => item.id === userId)
|
||||
if (found && found.nickname) {
|
||||
nicknames.push(found.nickname)
|
||||
}
|
||||
})
|
||||
return nicknames.join(',')
|
||||
}
|
||||
// 保存配置
|
||||
const saveConfig = async () => {
|
||||
activeTabName.value = 'user'
|
||||
currentNode.value.name = nodeName.value!
|
||||
// TODO 暂时写死。后续可以显示谁有权限可以发起
|
||||
currentNode.value.showText = '已设置'
|
||||
// 设置表单权限
|
||||
currentNode.value.fieldsPermission = fieldsPermissionConfig.value
|
||||
// 设置发起人的按钮权限
|
||||
currentNode.value.buttonsSetting = START_USER_BUTTON_SETTING
|
||||
console.log('currentNode.value.buttonsSetting==>', currentNode.value.buttonsSetting)
|
||||
settingVisible.value = false
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -56,7 +56,6 @@
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy == CandidateStrategy.ROLE"
|
||||
label="指定角色"
|
||||
@@ -94,25 +93,6 @@
|
||||
show-checkbox
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="
|
||||
configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
|
||||
"
|
||||
:label="deptLevelLabel!"
|
||||
prop="deptLevel"
|
||||
span="24"
|
||||
>
|
||||
<el-select v-model="configForm.deptLevel" clearable>
|
||||
<el-option
|
||||
v-for="(item, index) in MULTI_LEVEL_DEPT"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy == CandidateStrategy.POST"
|
||||
label="指定岗位"
|
||||
@@ -134,13 +114,7 @@
|
||||
prop="userIds"
|
||||
span="24"
|
||||
>
|
||||
<el-select
|
||||
v-model="configForm.userIds"
|
||||
clearable
|
||||
multiple
|
||||
style="width: 100%"
|
||||
@change="changedCandidateUsers"
|
||||
>
|
||||
<el-select v-model="configForm.userIds" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
@@ -163,6 +137,57 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy === CandidateStrategy.FORM_USER"
|
||||
label="表单内用户字段"
|
||||
prop="formUser"
|
||||
>
|
||||
<el-select v-model="configForm.formUser" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="(item, idx) in userFieldOnFormOptions"
|
||||
:key="idx"
|
||||
:label="item.title"
|
||||
:value="item.field"
|
||||
:disabled ="!item.required"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER"
|
||||
label="表单内部门字段"
|
||||
prop="formDept"
|
||||
>
|
||||
<el-select v-model="configForm.formDept" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="(item, idx) in deptFieldOnFormOptions"
|
||||
:key="idx"
|
||||
:label="item.title"
|
||||
:value="item.field"
|
||||
:disabled ="!item.required"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="
|
||||
configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
|
||||
configForm.candidateStrategy ==
|
||||
CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
|
||||
"
|
||||
:label="deptLevelLabel!"
|
||||
prop="deptLevel"
|
||||
span="24"
|
||||
>
|
||||
<el-select v-model="configForm.deptLevel" clearable>
|
||||
<el-option
|
||||
v-for="(item, index) in MULTI_LEVEL_DEPT"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- TODO @jason:后续要支持选择已经存好的表达式 -->
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy === CandidateStrategy.EXPRESSION"
|
||||
@@ -184,14 +209,7 @@
|
||||
:key="index"
|
||||
class="flex items-center"
|
||||
>
|
||||
<el-radio
|
||||
:value="item.value"
|
||||
:label="item.value"
|
||||
:disabled="
|
||||
item.value !== ApproveMethodType.RANDOM_SELECT_ONE_APPROVE &&
|
||||
notAllowedMultiApprovers
|
||||
"
|
||||
>
|
||||
<el-radio :value="item.value" :label="item.value">
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
<el-form-item prop="approveRatio">
|
||||
@@ -451,7 +469,8 @@ import {
|
||||
TimeoutHandlerType,
|
||||
ASSIGN_EMPTY_HANDLER_TYPES,
|
||||
AssignEmptyHandlerType,
|
||||
FieldPermissionType
|
||||
FieldPermissionType,
|
||||
ProcessVariableEnum
|
||||
} from '../consts'
|
||||
|
||||
import {
|
||||
@@ -481,6 +500,8 @@ const deptLevelLabel = computed(() => {
|
||||
let label = '部门负责人来源'
|
||||
if (configForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) {
|
||||
label = label + '(指定部门向上)'
|
||||
} else if (configForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER) {
|
||||
label = label + '(表单内部门向上)'
|
||||
} else {
|
||||
label = label + '(发起人部门向上)'
|
||||
}
|
||||
@@ -495,9 +516,23 @@ const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.USER_
|
||||
// 激活的 Tab 标签页
|
||||
const activeTabName = ref('user')
|
||||
// 表单字段权限设置
|
||||
const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission(
|
||||
FieldPermissionType.READ
|
||||
)
|
||||
const { formType, fieldsPermissionConfig, formFieldOptions, getNodeConfigFormFields } =
|
||||
useFormFieldsPermission(FieldPermissionType.READ)
|
||||
// 表单内用户字段选项, 必须是必填和用户选择器
|
||||
const userFieldOnFormOptions = computed(() => {
|
||||
// 固定添加发起人 ID 字段
|
||||
formFieldOptions.unshift({
|
||||
field: ProcessVariableEnum.START_USER_ID,
|
||||
title: '发起人',
|
||||
type: 'UserSelect',
|
||||
required: true
|
||||
})
|
||||
return formFieldOptions.filter((item) => item.type === 'UserSelect')
|
||||
})
|
||||
// 表单内部门字段选项, 必须是必填和部门选择器
|
||||
const deptFieldOnFormOptions = computed(() => {
|
||||
return formFieldOptions.filter((item) => item.type === 'DeptSelect')
|
||||
})
|
||||
// 操作按钮设置
|
||||
const { buttonsSetting, btnDisplayNameEdit, changeBtnDisplayName, btnDisplayNameBlurEvent } =
|
||||
useButtonsSetting()
|
||||
@@ -511,6 +546,8 @@ const formRules = reactive({
|
||||
roleIds: [{ required: true, message: '角色不能为空', trigger: 'change' }],
|
||||
deptIds: [{ required: true, message: '部门不能为空', trigger: 'change' }],
|
||||
userGroups: [{ required: true, message: '用户组不能为空', trigger: 'change' }],
|
||||
formUser: [{ required: true, message: '表单内用户字段不能为空', trigger: 'change' }],
|
||||
formDept: [{ required: true, message: '表单内部门字段不能为空', trigger: 'change' }],
|
||||
postIds: [{ required: true, message: '岗位不能为空', trigger: 'change' }],
|
||||
expression: [{ required: true, message: '流程表达式不能为空', trigger: 'blur' }],
|
||||
approveMethod: [{ required: true, message: '多人审批方式不能为空', trigger: 'change' }],
|
||||
@@ -537,8 +574,7 @@ const {
|
||||
getShowText
|
||||
} = useNodeForm(NodeType.USER_TASK_NODE)
|
||||
const configForm = tempConfigForm as Ref<UserTaskFormType>
|
||||
// 不允许多人审批
|
||||
const notAllowedMultiApprovers = ref(false)
|
||||
|
||||
// 改变审批人设置策略
|
||||
const changeCandidateStrategy = () => {
|
||||
configForm.value.userIds = []
|
||||
@@ -547,30 +583,11 @@ const changeCandidateStrategy = () => {
|
||||
configForm.value.postIds = []
|
||||
configForm.value.userGroups = []
|
||||
configForm.value.deptLevel = 1
|
||||
configForm.value.formUser = ''
|
||||
configForm.value.formDept = ''
|
||||
configForm.value.approveMethod = ApproveMethodType.SEQUENTIAL_APPROVE
|
||||
if (
|
||||
configForm.value.candidateStrategy === CandidateStrategy.START_USER ||
|
||||
configForm.value.candidateStrategy === CandidateStrategy.USER
|
||||
) {
|
||||
notAllowedMultiApprovers.value = true
|
||||
} else {
|
||||
notAllowedMultiApprovers.value = false
|
||||
}
|
||||
}
|
||||
// 改变审批候选人
|
||||
const changedCandidateUsers = () => {
|
||||
if (
|
||||
configForm.value.userIds &&
|
||||
configForm.value.userIds?.length <= 1 &&
|
||||
configForm.value.candidateStrategy === CandidateStrategy.USER
|
||||
) {
|
||||
configForm.value.approveMethod = ApproveMethodType.RANDOM_SELECT_ONE_APPROVE
|
||||
configForm.value.rejectHandlerType = RejectHandlerType.FINISH_PROCESS
|
||||
notAllowedMultiApprovers.value = true
|
||||
} else {
|
||||
notAllowedMultiApprovers.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 审批方式改变
|
||||
const approveMethodChanged = () => {
|
||||
configForm.value.rejectHandlerType = RejectHandlerType.FINISH_PROCESS
|
||||
@@ -579,7 +596,7 @@ const approveMethodChanged = () => {
|
||||
}
|
||||
formRef.value.clearValidate('approveRatio')
|
||||
}
|
||||
// 审批拒绝 可回退的节点
|
||||
// 审批拒绝 可退回的节点
|
||||
const returnTaskList = ref<SimpleFlowNode[]>([])
|
||||
// 审批人超时未处理设置
|
||||
const {
|
||||
@@ -666,11 +683,6 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
|
||||
configForm.value.candidateStrategy = node.candidateStrategy!
|
||||
// 解析候选人参数
|
||||
parseCandidateParam(node.candidateStrategy!, node?.candidateParam)
|
||||
if (configForm.value.userIds && configForm.value.userIds.length > 1) {
|
||||
notAllowedMultiApprovers.value = true
|
||||
} else {
|
||||
notAllowedMultiApprovers.value = false
|
||||
}
|
||||
// 2.2 设置审批方式
|
||||
configForm.value.approveMethod = node.approveMethod!
|
||||
if (node.approveMethod == ApproveMethodType.APPROVE_BY_RATIO) {
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
<template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div class="node-box" :class="{ 'node-config-error': !currentNode.showText }">
|
||||
<div
|
||||
class="node-box"
|
||||
:class="[
|
||||
{ 'node-config-error': !currentNode.showText },
|
||||
`${useTaskStatusClass(currentNode?.activityStatus)}`
|
||||
]"
|
||||
>
|
||||
<div class="node-title-container">
|
||||
<div class="node-title-icon copy-task"><span class="iconfont icon-copy"></span></div>
|
||||
<input
|
||||
v-if="showInput"
|
||||
v-if="!readonly && showInput"
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent()"
|
||||
@@ -24,9 +30,9 @@
|
||||
<div class="node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.COPY_TASK_NODE) }}
|
||||
</div>
|
||||
<Icon icon="ep:arrow-right-bold" />
|
||||
<Icon v-if="!readonly" icon="ep:arrow-right-bold" />
|
||||
</div>
|
||||
<div class="node-toolbar">
|
||||
<div v-if="!readonly" class="node-toolbar">
|
||||
<div class="toolbar-icon"
|
||||
><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
|
||||
/></div>
|
||||
@@ -34,15 +40,23 @@
|
||||
</div>
|
||||
|
||||
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
|
||||
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||
<NodeHandler
|
||||
v-if="currentNode"
|
||||
v-model:child-node="currentNode.childNode"
|
||||
:current-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
<CopyTaskNodeConfig v-if="currentNode" ref="nodeSetting" :flow-node="currentNode" />
|
||||
<CopyTaskNodeConfig
|
||||
v-if="!readonly && currentNode"
|
||||
ref="nodeSetting"
|
||||
:flow-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||
import NodeHandler from '../NodeHandler.vue'
|
||||
import { useNodeName2, useWatchNode } from '../node'
|
||||
import { useNodeName2, useWatchNode, useTaskStatusClass } from '../node'
|
||||
import CopyTaskNodeConfig from '../nodes-config/CopyTaskNodeConfig.vue'
|
||||
defineOptions({
|
||||
name: 'CopyTaskNode'
|
||||
@@ -57,7 +71,8 @@ const props = defineProps({
|
||||
const emits = defineEmits<{
|
||||
'update:flowNode': [node: SimpleFlowNode | undefined]
|
||||
}>()
|
||||
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
// 监控节点的变化
|
||||
const currentNode = useWatchNode(props)
|
||||
// 节点名称编辑
|
||||
@@ -66,6 +81,9 @@ const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.
|
||||
const nodeSetting = ref()
|
||||
// 打开节点配置
|
||||
const openNodeConfig = () => {
|
||||
if (readonly) {
|
||||
return
|
||||
}
|
||||
nodeSetting.value.showCopyTaskNodeConfig(currentNode.value)
|
||||
nodeSetting.value.openDrawer()
|
||||
}
|
||||
|
||||
@@ -1,13 +1,102 @@
|
||||
<template>
|
||||
<div class="end-node-wrapper">
|
||||
<div class="end-node-box">
|
||||
<div class="end-node-box cursor-pointer" :class="`${useTaskStatusClass(currentNode?.activityStatus)}`" @click="nodeClick">
|
||||
<span class="node-fixed-name" title="结束">结束</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog title="审批信息" v-model="dialogVisible" width="1000px" append-to-body>
|
||||
<el-row>
|
||||
<el-table
|
||||
:data="processInstanceInfos"
|
||||
size="small"
|
||||
border
|
||||
header-cell-class-name="table-header-gray"
|
||||
>
|
||||
<el-table-column
|
||||
label="序号"
|
||||
header-align="center"
|
||||
align="center"
|
||||
type="index"
|
||||
width="50"
|
||||
/>
|
||||
<el-table-column
|
||||
label="发起人"
|
||||
prop="assigneeUser.nickname"
|
||||
min-width="100"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column label="部门" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.deptName || scope.row.ownerUser?.deptName }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="开始时间"
|
||||
prop="createTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="结束时间"
|
||||
prop="endTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column align="center" label="审批状态" prop="status" min-width="90">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="100">
|
||||
<template #default="scope">
|
||||
{{ formatPast2(scope.row.durationInMillis) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { SimpleFlowNode } from '../consts'
|
||||
import { useWatchNode, useTaskStatusClass } from '../node'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
defineOptions({
|
||||
name: 'EndEventNode'
|
||||
})
|
||||
const props = defineProps({
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
// 监控节点变化
|
||||
const currentNode = useWatchNode(props)
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
const processInstance = inject<Ref<any>>('processInstance')
|
||||
// 审批信息的弹窗显示,用于只读模式
|
||||
const dialogVisible = ref(false) // 弹窗可见性
|
||||
const processInstanceInfos = ref<any[]>([]) // 流程的审批信息
|
||||
|
||||
const nodeClick = () => {
|
||||
if (readonly) {
|
||||
if(processInstance && processInstance.value){
|
||||
processInstanceInfos.value = [
|
||||
{
|
||||
assigneeUser: processInstance.value.startUser,
|
||||
createTime: processInstance.value.startTime,
|
||||
endTime: processInstance.value.endTime,
|
||||
status: processInstance.value.status,
|
||||
durationInMillis: processInstance.value.durationInMillis
|
||||
}
|
||||
]
|
||||
dialogVisible.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -1,7 +1,17 @@
|
||||
<template>
|
||||
<div class="branch-node-wrapper">
|
||||
<div class="branch-node-container">
|
||||
<div class="branch-node-add" @click="addCondition">添加条件</div>
|
||||
<div
|
||||
v-if="readonly"
|
||||
class="branch-node-readonly"
|
||||
:class="`${useTaskStatusClass(currentNode?.activityStatus)}`"
|
||||
>
|
||||
<span class="iconfont icon-exclusive icon-size condition"></span>
|
||||
</div>
|
||||
<el-button v-else class="branch-node-add" color="#67c23a" @click="addCondition" plain
|
||||
>添加条件</el-button
|
||||
>
|
||||
|
||||
<div
|
||||
class="branch-node-item"
|
||||
v-for="(item, index) in currentNode.conditionNodes"
|
||||
@@ -17,9 +27,15 @@
|
||||
</template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div class="node-box" :class="{ 'node-config-error': !item.showText }">
|
||||
<div
|
||||
class="node-box"
|
||||
:class="[
|
||||
{ 'node-config-error': !item.showText },
|
||||
`${useTaskStatusClass(item.activityStatus)}`
|
||||
]"
|
||||
>
|
||||
<div class="branch-node-title-container">
|
||||
<div v-if="showInputs[index]">
|
||||
<div v-if="!readonly && showInputs[index]">
|
||||
<input
|
||||
type="text"
|
||||
class="input-max-width editable-title-input"
|
||||
@@ -39,7 +55,10 @@
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-toolbar" v-if="index + 1 !== currentNode.conditionNodes?.length">
|
||||
<div
|
||||
class="node-toolbar"
|
||||
v-if="!readonly && index + 1 !== currentNode.conditionNodes?.length"
|
||||
>
|
||||
<div class="toolbar-icon">
|
||||
<Icon
|
||||
color="#0089ff"
|
||||
@@ -65,7 +84,7 @@
|
||||
<Icon icon="ep:arrow-right" />
|
||||
</div>
|
||||
</div>
|
||||
<NodeHandler v-model:child-node="item.childNode" />
|
||||
<NodeHandler v-model:child-node="item.childNode" :current-node="item" />
|
||||
</div>
|
||||
</div>
|
||||
<ConditionNodeConfig :node-index="index" :condition-node="item" :ref="item.id" />
|
||||
@@ -78,7 +97,11 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||
<NodeHandler
|
||||
v-if="currentNode"
|
||||
v-model:child-node="currentNode.childNode"
|
||||
:current-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -87,6 +110,7 @@ import NodeHandler from '../NodeHandler.vue'
|
||||
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||
import { getDefaultConditionNodeName } from '../utils'
|
||||
import { useTaskStatusClass } from '../node'
|
||||
import { generateUUID } from '@/utils'
|
||||
import ConditionNodeConfig from '../nodes-config/ConditionNodeConfig.vue'
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
@@ -94,10 +118,6 @@ defineOptions({
|
||||
name: 'ExclusiveNode'
|
||||
})
|
||||
const props = defineProps({
|
||||
// parentNode : {
|
||||
// type: Object as () => SimpleFlowNode,
|
||||
// required: true
|
||||
// },
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
required: true
|
||||
@@ -113,10 +133,9 @@ const emits = defineEmits<{
|
||||
nodeType: number
|
||||
]
|
||||
}>()
|
||||
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
const currentNode = ref<SimpleFlowNode>(props.flowNode)
|
||||
// const conditionNodes = computed(() => currentNode.value.conditionNodes);
|
||||
|
||||
watch(
|
||||
() => props.flowNode,
|
||||
(newValue) => {
|
||||
@@ -139,6 +158,9 @@ const clickEvent = (index: number) => {
|
||||
}
|
||||
|
||||
const conditionNodeConfig = (nodeId: string) => {
|
||||
if (readonly) {
|
||||
return
|
||||
}
|
||||
const conditionNode = proxy.$refs[nodeId][0]
|
||||
conditionNode.open()
|
||||
}
|
||||
@@ -193,7 +215,7 @@ const recursiveFindParentNode = (
|
||||
node: SimpleFlowNode,
|
||||
nodeType: number
|
||||
) => {
|
||||
if (!node || node.type === NodeType.START_EVENT_NODE) {
|
||||
if (!node || node.type === NodeType.START_USER_NODE) {
|
||||
return
|
||||
}
|
||||
if (node.type === nodeType) {
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<div class="branch-node-wrapper">
|
||||
<div class="branch-node-container">
|
||||
<div
|
||||
v-if="readonly"
|
||||
class="branch-node-readonly"
|
||||
:class="`${useTaskStatusClass(currentNode?.activityStatus)}`"
|
||||
>
|
||||
<span class="iconfont icon-inclusive icon-size inclusive"></span>
|
||||
</div>
|
||||
<el-button v-else class="branch-node-add" color="#345da2" @click="addCondition" plain
|
||||
>添加条件</el-button
|
||||
>
|
||||
<div
|
||||
class="branch-node-item"
|
||||
v-for="(item, index) in currentNode.conditionNodes"
|
||||
:key="index"
|
||||
>
|
||||
<template v-if="index == 0">
|
||||
<div class="branch-line-first-top"> </div>
|
||||
<div class="branch-line-first-bottom"></div>
|
||||
</template>
|
||||
<template v-if="index + 1 == currentNode.conditionNodes?.length">
|
||||
<div class="branch-line-last-top"></div>
|
||||
<div class="branch-line-last-bottom"></div>
|
||||
</template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div
|
||||
class="node-box"
|
||||
:class="[
|
||||
{ 'node-config-error': !item.showText },
|
||||
`${useTaskStatusClass(item.activityStatus)}`
|
||||
]"
|
||||
>
|
||||
<div class="branch-node-title-container">
|
||||
<div v-if="showInputs[index]">
|
||||
<input
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent(index)"
|
||||
v-mountedFocus
|
||||
v-model="item.name"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="branch-title" @click="clickEvent(index)"> {{ item.name }} </div>
|
||||
</div>
|
||||
<div class="branch-node-content" @click="conditionNodeConfig(item.id)">
|
||||
<div class="branch-node-text" :title="item.showText" v-if="item.showText">
|
||||
{{ item.showText }}
|
||||
</div>
|
||||
<div class="branch-node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="node-toolbar"
|
||||
v-if="!readonly && index + 1 !== currentNode.conditionNodes?.length"
|
||||
>
|
||||
<div class="toolbar-icon">
|
||||
<Icon
|
||||
color="#0089ff"
|
||||
icon="ep:circle-close-filled"
|
||||
:size="18"
|
||||
@click="deleteCondition(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="branch-node-move move-node-left"
|
||||
v-if="!readonly && index != 0 && index + 1 !== currentNode.conditionNodes?.length"
|
||||
@click="moveNode(index, -1)"
|
||||
>
|
||||
<Icon icon="ep:arrow-left" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="branch-node-move move-node-right"
|
||||
v-if="
|
||||
!readonly &&
|
||||
currentNode.conditionNodes &&
|
||||
index < currentNode.conditionNodes.length - 2
|
||||
"
|
||||
@click="moveNode(index, 1)"
|
||||
>
|
||||
<Icon icon="ep:arrow-right" />
|
||||
</div>
|
||||
</div>
|
||||
<NodeHandler v-model:child-node="item.childNode" :current-node="item" />
|
||||
</div>
|
||||
</div>
|
||||
<ConditionNodeConfig :node-index="index" :condition-node="item" :ref="item.id" />
|
||||
<!-- 递归显示子节点 -->
|
||||
<ProcessNodeTree
|
||||
v-if="item && item.childNode"
|
||||
:parent-node="item"
|
||||
v-model:flow-node="item.childNode"
|
||||
@find:recursive-find-parent-node="recursiveFindParentNode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<NodeHandler
|
||||
v-if="currentNode"
|
||||
v-model:child-node="currentNode.childNode"
|
||||
:current-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import NodeHandler from '../NodeHandler.vue'
|
||||
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||
import { useTaskStatusClass } from '../node'
|
||||
import { getDefaultInclusiveConditionNodeName } from '../utils'
|
||||
import { generateUUID } from '@/utils'
|
||||
import ConditionNodeConfig from '../nodes-config/ConditionNodeConfig.vue'
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
defineOptions({
|
||||
name: 'InclusiveNode'
|
||||
})
|
||||
const props = defineProps({
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
// 定义事件,更新父组件
|
||||
const emits = defineEmits<{
|
||||
'update:modelValue': [node: SimpleFlowNode | undefined]
|
||||
'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: number]
|
||||
'find:recursiveFindParentNode': [
|
||||
nodeList: SimpleFlowNode[],
|
||||
curentNode: SimpleFlowNode,
|
||||
nodeType: number
|
||||
]
|
||||
}>()
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
|
||||
const currentNode = ref<SimpleFlowNode>(props.flowNode)
|
||||
|
||||
watch(
|
||||
() => props.flowNode,
|
||||
(newValue) => {
|
||||
currentNode.value = newValue
|
||||
}
|
||||
)
|
||||
|
||||
const showInputs = ref<boolean[]>([])
|
||||
// 失去焦点
|
||||
const blurEvent = (index: number) => {
|
||||
showInputs.value[index] = false
|
||||
const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
|
||||
conditionNode.name =
|
||||
conditionNode.name || getDefaultInclusiveConditionNodeName(index, conditionNode.defaultFlow)
|
||||
}
|
||||
|
||||
// 点击条件名称
|
||||
const clickEvent = (index: number) => {
|
||||
showInputs.value[index] = true
|
||||
}
|
||||
|
||||
const conditionNodeConfig = (nodeId: string) => {
|
||||
if (readonly) {
|
||||
return
|
||||
}
|
||||
const conditionNode = proxy.$refs[nodeId][0]
|
||||
conditionNode.open()
|
||||
}
|
||||
|
||||
// 新增条件
|
||||
const addCondition = () => {
|
||||
const conditionNodes = currentNode.value.conditionNodes
|
||||
if (conditionNodes) {
|
||||
const len = conditionNodes.length
|
||||
let lastIndex = len - 1
|
||||
const conditionData: SimpleFlowNode = {
|
||||
id: 'Flow_' + generateUUID(),
|
||||
name: '包容条件' + len,
|
||||
showText: '',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
conditionNodes: [],
|
||||
conditionType: 1,
|
||||
defaultFlow: false
|
||||
}
|
||||
conditionNodes.splice(lastIndex, 0, conditionData)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除条件
|
||||
const deleteCondition = (index: number) => {
|
||||
const conditionNodes = currentNode.value.conditionNodes
|
||||
if (conditionNodes) {
|
||||
conditionNodes.splice(index, 1)
|
||||
if (conditionNodes.length == 1) {
|
||||
const childNode = currentNode.value.childNode
|
||||
// 更新此节点为后续孩子节点
|
||||
emits('update:modelValue', childNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移动节点
|
||||
const moveNode = (index: number, to: number) => {
|
||||
// -1 :向左 1: 向右
|
||||
if (currentNode.value.conditionNodes) {
|
||||
currentNode.value.conditionNodes[index] = currentNode.value.conditionNodes.splice(
|
||||
index + to,
|
||||
1,
|
||||
currentNode.value.conditionNodes[index]
|
||||
)[0]
|
||||
}
|
||||
}
|
||||
// 递归从父节点中查询匹配的节点
|
||||
const recursiveFindParentNode = (
|
||||
nodeList: SimpleFlowNode[],
|
||||
node: SimpleFlowNode,
|
||||
nodeType: number
|
||||
) => {
|
||||
if (!node || node.type === NodeType.START_USER_NODE) {
|
||||
return
|
||||
}
|
||||
if (node.type === nodeType) {
|
||||
nodeList.push(node)
|
||||
}
|
||||
// 条件节点 (NodeType.CONDITION_NODE) 比较特殊。需要调用其父节点条件分支节点(NodeType.INCLUSIVE_BRANCH_NODE) 继续查找
|
||||
emits('find:parentNode', nodeList, nodeType)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -1,7 +1,16 @@
|
||||
<template>
|
||||
<div class="branch-node-wrapper">
|
||||
<div class="branch-node-container">
|
||||
<div class="branch-node-add" @click="addCondition">添加分支</div>
|
||||
<div
|
||||
v-if="readonly"
|
||||
class="branch-node-readonly"
|
||||
:class="`${useTaskStatusClass(currentNode?.activityStatus)}`"
|
||||
>
|
||||
<span class="iconfont icon-parallel icon-size parallel"></span>
|
||||
</div>
|
||||
<el-button v-else class="branch-node-add" color="#626aef" @click="addCondition" plain
|
||||
>添加分支</el-button
|
||||
>
|
||||
<div
|
||||
class="branch-node-item"
|
||||
v-for="(item, index) in currentNode.conditionNodes"
|
||||
@@ -17,7 +26,7 @@
|
||||
</template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div class="node-box">
|
||||
<div class="node-box" :class="`${useTaskStatusClass(item.activityStatus)}`">
|
||||
<div class="branch-node-title-container">
|
||||
<div v-if="showInputs[index]">
|
||||
<input
|
||||
@@ -39,7 +48,7 @@
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-toolbar">
|
||||
<div v-if="!readonly" class="node-toolbar">
|
||||
<div class="toolbar-icon">
|
||||
<Icon
|
||||
color="#0089ff"
|
||||
@@ -49,20 +58,8 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div
|
||||
class="branch-node-move move-node-left"
|
||||
v-if="index != 0 && index + 1 !== currentNode.conditionNodes?.length" @click="moveNode(index, -1)">
|
||||
<Icon icon="ep:arrow-left" />
|
||||
</div> -->
|
||||
|
||||
<!-- <div
|
||||
class="branch-node-move move-node-right"
|
||||
v-if="currentNode.conditionNodes && index < currentNode.conditionNodes.length - 2"
|
||||
@click="moveNode(index, 1)">
|
||||
<Icon icon="ep:arrow-right" />
|
||||
</div> -->
|
||||
</div>
|
||||
<NodeHandler v-model:child-node="item.childNode" />
|
||||
<NodeHandler v-model:child-node="item.childNode" :current-node="item" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 递归显示子节点 -->
|
||||
@@ -74,7 +71,11 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||
<NodeHandler
|
||||
v-if="currentNode"
|
||||
v-model:child-node="currentNode.childNode"
|
||||
:current-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -82,8 +83,8 @@
|
||||
import NodeHandler from '../NodeHandler.vue'
|
||||
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||
import { useTaskStatusClass } from '../node'
|
||||
import { generateUUID } from '@/utils'
|
||||
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
defineOptions({
|
||||
name: 'ParallelNode'
|
||||
@@ -106,6 +107,8 @@ const emits = defineEmits<{
|
||||
}>()
|
||||
|
||||
const currentNode = ref<SimpleFlowNode>(props.flowNode)
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
|
||||
watch(
|
||||
() => props.flowNode,
|
||||
@@ -169,7 +172,7 @@ const recursiveFindParentNode = (
|
||||
node: SimpleFlowNode,
|
||||
nodeType: number
|
||||
) => {
|
||||
if (!node || node.type === NodeType.START_EVENT_NODE) {
|
||||
if (!node || node.type === NodeType.START_USER_NODE) {
|
||||
return
|
||||
}
|
||||
if (node.type === nodeType) {
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
<template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div class="node-box" :class="{ 'node-config-error': !currentNode.showText }">
|
||||
<div
|
||||
class="node-box"
|
||||
:class="[
|
||||
{ 'node-config-error': !currentNode.showText },
|
||||
`${useTaskStatusClass(currentNode?.activityStatus)}`
|
||||
]"
|
||||
>
|
||||
<div class="node-title-container">
|
||||
<div class="node-title-icon start-user"
|
||||
><span class="iconfont icon-start-user"></span
|
||||
@@ -19,27 +25,88 @@
|
||||
{{ currentNode.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-content" @click="openNodeConfig">
|
||||
<div class="node-content" @click="nodeClick">
|
||||
<div class="node-text" :title="currentNode.showText" v-if="currentNode.showText">
|
||||
{{ currentNode.showText }}
|
||||
</div>
|
||||
<div class="node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.START_USER_NODE) }}
|
||||
</div>
|
||||
<Icon icon="ep:arrow-right-bold" />
|
||||
<Icon icon="ep:arrow-right-bold" v-if="!readonly" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
|
||||
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||
<NodeHandler
|
||||
v-if="currentNode"
|
||||
v-model:child-node="currentNode.childNode"
|
||||
:current-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<StartUserNodeConfig v-if="currentNode" ref="nodeSetting" :flow-node="currentNode" />
|
||||
<StartUserNodeConfig v-if="!readonly && currentNode" ref="nodeSetting" :flow-node="currentNode" />
|
||||
<!-- 审批记录 -->
|
||||
<el-dialog
|
||||
:title="dialogTitle || '审批记录'"
|
||||
v-model="dialogVisible"
|
||||
width="1000px"
|
||||
append-to-body
|
||||
>
|
||||
<el-row>
|
||||
<el-table :data="selectTasks" size="small" border header-cell-class-name="table-header-gray">
|
||||
<el-table-column
|
||||
label="序号"
|
||||
header-align="center"
|
||||
align="center"
|
||||
type="index"
|
||||
width="50"
|
||||
/>
|
||||
<el-table-column label="审批人" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.nickname || scope.row.ownerUser?.nickname }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="部门" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.deptName || scope.row.ownerUser?.deptName }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="开始时间"
|
||||
prop="createTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="结束时间"
|
||||
prop="endTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column align="center" label="审批状态" prop="status" min-width="90">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="审批建议" prop="reason" min-width="120" />
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="100">
|
||||
<template #default="scope">
|
||||
{{ formatPast2(scope.row.durationInMillis) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import NodeHandler from '../NodeHandler.vue'
|
||||
import { useWatchNode, useNodeName2 } from '../node'
|
||||
import { useWatchNode, useNodeName2, useTaskStatusClass } from '../node'
|
||||
import { SimpleFlowNode, NODE_DEFAULT_TEXT, NodeType } from '../consts'
|
||||
import StartUserNodeConfig from '../nodes-config/StartUserNodeConfig.vue'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
defineOptions({
|
||||
name: 'StartEventNode'
|
||||
})
|
||||
@@ -49,6 +116,8 @@ const props = defineProps({
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
const readonly = inject<Boolean>('readonly') // 是否只读
|
||||
const tasks = inject<Ref<any[]>>('tasks')
|
||||
// 定义事件,更新父组件。
|
||||
const emits = defineEmits<{
|
||||
'update:modelValue': [node: SimpleFlowNode | undefined]
|
||||
@@ -59,11 +128,27 @@ const currentNode = useWatchNode(props)
|
||||
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.START_USER_NODE)
|
||||
|
||||
const nodeSetting = ref()
|
||||
// 打开节点配置
|
||||
const openNodeConfig = () => {
|
||||
// 把当前节点传递给配置组件
|
||||
nodeSetting.value.showStartUserNodeConfig(currentNode.value)
|
||||
nodeSetting.value.openDrawer()
|
||||
//
|
||||
const nodeClick = () => {
|
||||
if (readonly) {
|
||||
// 只读模式,弹窗显示任务信息
|
||||
if (tasks && tasks.value) {
|
||||
dialogTitle.value = currentNode.value.name
|
||||
selectTasks.value = tasks.value.filter(
|
||||
(item: any) => item?.taskDefinitionKey === currentNode.value.id
|
||||
)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
} else {
|
||||
// 编辑模式,打开节点配置、把当前节点传递给配置组件
|
||||
nodeSetting.value.showStartUserNodeConfig(currentNode.value)
|
||||
nodeSetting.value.openDrawer()
|
||||
}
|
||||
}
|
||||
|
||||
// 任务的弹窗显示,用于只读模式
|
||||
const dialogVisible = ref(false) // 弹窗可见性
|
||||
const dialogTitle = ref<string | undefined>(undefined) // 弹窗标题
|
||||
const selectTasks = ref<any[] | undefined>([]) // 选中的任务数组
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -1,11 +1,17 @@
|
||||
<template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div class="node-box" :class="{ 'node-config-error': !currentNode.showText }">
|
||||
<div
|
||||
class="node-box"
|
||||
:class="[
|
||||
{ 'node-config-error': !currentNode.showText },
|
||||
`${useTaskStatusClass(currentNode?.activityStatus)}`
|
||||
]"
|
||||
>
|
||||
<div class="node-title-container">
|
||||
<div class="node-title-icon user-task"><span class="iconfont icon-approve"></span></div>
|
||||
<input
|
||||
v-if="showInput"
|
||||
v-if="!readonly && showInput"
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent()"
|
||||
@@ -17,23 +23,27 @@
|
||||
{{ currentNode.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-content" @click="openNodeConfig">
|
||||
<div class="node-content" @click="nodeClick">
|
||||
<div class="node-text" :title="currentNode.showText" v-if="currentNode.showText">
|
||||
{{ currentNode.showText }}
|
||||
</div>
|
||||
<div class="node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.USER_TASK_NODE) }}
|
||||
</div>
|
||||
<Icon icon="ep:arrow-right-bold" />
|
||||
<Icon icon="ep:arrow-right-bold" v-if="!readonly" />
|
||||
</div>
|
||||
<div class="node-toolbar">
|
||||
<div v-if="!readonly" class="node-toolbar">
|
||||
<div class="toolbar-icon"
|
||||
><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
|
||||
/></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
|
||||
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||
<NodeHandler
|
||||
v-if="currentNode"
|
||||
v-model:child-node="currentNode.childNode"
|
||||
:current-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<UserTaskNodeConfig
|
||||
@@ -42,12 +52,69 @@
|
||||
:flow-node="currentNode"
|
||||
@find:return-task-nodes="findReturnTaskNodes"
|
||||
/>
|
||||
<!-- 审批记录 -->
|
||||
<el-dialog
|
||||
:title="dialogTitle || '审批记录'"
|
||||
v-model="dialogVisible"
|
||||
width="1000px"
|
||||
append-to-body
|
||||
>
|
||||
<el-row>
|
||||
<el-table :data="selectTasks" size="small" border header-cell-class-name="table-header-gray">
|
||||
<el-table-column
|
||||
label="序号"
|
||||
header-align="center"
|
||||
align="center"
|
||||
type="index"
|
||||
width="50"
|
||||
/>
|
||||
<el-table-column label="审批人" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.nickname || scope.row.ownerUser?.nickname }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="部门" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.deptName || scope.row.ownerUser?.deptName }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="开始时间"
|
||||
prop="createTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="结束时间"
|
||||
prop="endTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column align="center" label="审批状态" prop="status" min-width="90">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="审批建议" prop="reason" min-width="120" />
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="100">
|
||||
<template #default="scope">
|
||||
{{ formatPast2(scope.row.durationInMillis) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||
import { useWatchNode, useNodeName2 } from '../node'
|
||||
import { useWatchNode, useNodeName2, useTaskStatusClass } from '../node'
|
||||
import NodeHandler from '../NodeHandler.vue'
|
||||
import UserTaskNodeConfig from '../nodes-config/UserTaskNodeConfig.vue'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
defineOptions({
|
||||
name: 'UserTaskNode'
|
||||
})
|
||||
@@ -61,22 +128,36 @@ const emits = defineEmits<{
|
||||
'update:flowNode': [node: SimpleFlowNode | undefined]
|
||||
'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: NodeType]
|
||||
}>()
|
||||
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
const tasks = inject<Ref<any[]>>('tasks')
|
||||
// 监控节点变化
|
||||
const currentNode = useWatchNode(props)
|
||||
// 节点名称编辑
|
||||
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.START_USER_NODE)
|
||||
const nodeSetting = ref()
|
||||
// 打开节点配置
|
||||
const openNodeConfig = () => {
|
||||
// 把当前节点传递给配置组件
|
||||
nodeSetting.value.showUserTaskNodeConfig(currentNode.value)
|
||||
nodeSetting.value.openDrawer()
|
||||
|
||||
const nodeClick = () => {
|
||||
if (readonly) {
|
||||
if (tasks && tasks.value) {
|
||||
dialogTitle.value = currentNode.value.name
|
||||
// 只读模式,弹窗显示任务信息
|
||||
selectTasks.value = tasks.value.filter(
|
||||
(item: any) => item?.taskDefinitionKey === currentNode.value.id
|
||||
)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
} else {
|
||||
// 编辑模式,打开节点配置、把当前节点传递给配置组件
|
||||
nodeSetting.value.showUserTaskNodeConfig(currentNode.value)
|
||||
nodeSetting.value.openDrawer()
|
||||
}
|
||||
}
|
||||
|
||||
const deleteNode = () => {
|
||||
emits('update:flowNode', currentNode.value.childNode)
|
||||
}
|
||||
|
||||
// 查找可以驳回用户节点
|
||||
const findReturnTaskNodes = (
|
||||
matchNodeList: SimpleFlowNode[] // 匹配的节点
|
||||
@@ -84,5 +165,10 @@ const findReturnTaskNodes = (
|
||||
// 从父节点查找
|
||||
emits('find:parentNode', matchNodeList, NodeType.USER_TASK_NODE)
|
||||
}
|
||||
|
||||
// 任务的弹窗显示,用于只读模式
|
||||
const dialogVisible = ref(false) // 弹窗可见性
|
||||
const dialogTitle = ref<string | undefined>(undefined) // 弹窗标题
|
||||
const selectTasks = ref<any[] | undefined>([]) // 选中的任务数组
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -8,6 +8,14 @@ export const getDefaultConditionNodeName = (index: number, defaultFlow: boolean
|
||||
return '条件' + (index + 1)
|
||||
}
|
||||
|
||||
// 获取包容分支条件节点默认的名称
|
||||
export const getDefaultInclusiveConditionNodeName = (index: number, defaultFlow: boolean | undefined): string => {
|
||||
if (defaultFlow) {
|
||||
return '其它情况'
|
||||
}
|
||||
return '包容条件' + (index + 1)
|
||||
}
|
||||
|
||||
export const convertTimeUnit = (strTimeUnit: string) => {
|
||||
if (strTimeUnit === 'M') {
|
||||
return TimeUnitType.MINUTE
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="人员选择" width="800">
|
||||
<el-row class="gap2" v-loading="formLoading">
|
||||
<el-col :span="6">
|
||||
<ContentWrap class="h-1/1">
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
:data="deptTree"
|
||||
:expand-on-click-node="false"
|
||||
:props="defaultProps"
|
||||
default-expand-all
|
||||
highlight-current
|
||||
node-key="id"
|
||||
@node-click="handleNodeClick"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</el-col>
|
||||
<el-col :span="17">
|
||||
<el-transfer
|
||||
v-model="selectedUserIdList"
|
||||
:titles="['未选', '已选']"
|
||||
filterable
|
||||
filter-placeholder="搜索成员"
|
||||
:data="transferUserList"
|
||||
:props="{ label: 'nickname', key: 'id' }"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template #footer>
|
||||
<el-button
|
||||
:disabled="formLoading || !selectedUserIdList?.length"
|
||||
type="primary"
|
||||
@click="submitForm"
|
||||
>
|
||||
确 定
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'UserSelectForm' })
|
||||
const emit = defineEmits<{
|
||||
confirm: [id: any, userList: any[]]
|
||||
}>()
|
||||
const { t } = useI18n() // 国际
|
||||
const message = useMessage() // 消息弹窗
|
||||
const deptTree = ref<Tree[]>([]) // 部门树形结构化
|
||||
const deptList = ref<any[]>([]) // 保存扁平化的部门列表数据
|
||||
const userList = ref<UserApi.UserVO[]>([]) // 所有用户列表
|
||||
const filteredUserList = ref<UserApi.UserVO[]>([]) // 当前部门过滤后的用户列表
|
||||
const selectedUserIdList: any = ref([]) // 选中的用户列表
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const activityId = ref()
|
||||
|
||||
/** 计算属性:合并已选择的用户和当前部门过滤后的用户 */
|
||||
const transferUserList = computed(() => {
|
||||
// 1.1 获取所有已选择的用户
|
||||
const selectedUsers = userList.value.filter((user: any) =>
|
||||
selectedUserIdList.value.includes(user.id)
|
||||
)
|
||||
|
||||
// 1.2 获取当前部门过滤后的未选择用户
|
||||
const filteredUnselectedUsers = filteredUserList.value.filter(
|
||||
(user: any) => !selectedUserIdList.value.includes(user.id)
|
||||
)
|
||||
|
||||
// 2. 合并并去重
|
||||
return [...selectedUsers, ...filteredUnselectedUsers]
|
||||
})
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: number, selectedList?: any[]) => {
|
||||
activityId.value = id
|
||||
resetForm()
|
||||
|
||||
// 加载部门、用户列表
|
||||
const deptData = await DeptApi.getSimpleDeptList()
|
||||
deptList.value = deptData // 保存扁平结构的部门数据
|
||||
deptTree.value = handleTree(deptData) // 转换成树形结构
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
|
||||
// 初始状态下,过滤列表等于所有用户列表
|
||||
filteredUserList.value = [...userList.value]
|
||||
selectedUserIdList.value = selectedList?.map((item: any) => item.id) || []
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
/** 获取指定部门及其所有子部门的ID列表 */
|
||||
const getChildDeptIds = (deptId: number, deptList: any[]): number[] => {
|
||||
const ids = [deptId]
|
||||
const children = deptList.filter((dept) => dept.parentId === deptId)
|
||||
children.forEach((child) => {
|
||||
ids.push(...getChildDeptIds(child.id, deptList))
|
||||
})
|
||||
return ids
|
||||
}
|
||||
|
||||
/** 获取部门过滤后的用户列表 */
|
||||
const filterUserList = async (deptId?: number) => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
if (!deptId) {
|
||||
// 如果没有选择部门,显示所有用户
|
||||
filteredUserList.value = [...userList.value]
|
||||
return
|
||||
}
|
||||
|
||||
// 直接使用已保存的部门列表数据进行过滤
|
||||
const deptIds = getChildDeptIds(deptId, deptList.value)
|
||||
|
||||
// 过滤出这些部门下的用户
|
||||
filteredUserList.value = userList.value.filter((user) => deptIds.includes(user.deptId))
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交选择 */
|
||||
const submitForm = async () => {
|
||||
try {
|
||||
message.success(t('common.updateSuccess'))
|
||||
dialogVisible.value = false
|
||||
// 从所有用户列表中筛选出已选择的用户
|
||||
const emitUserList = userList.value.filter((user: any) =>
|
||||
selectedUserIdList.value.includes(user.id)
|
||||
)
|
||||
// 发送操作成功的事件
|
||||
emit('confirm', activityId.value, emitUserList)
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
deptTree.value = []
|
||||
deptList.value = []
|
||||
userList.value = []
|
||||
filteredUserList.value = []
|
||||
selectedUserIdList.value = []
|
||||
}
|
||||
|
||||
/** 处理部门被点击 */
|
||||
const handleNodeClick = (row: { [key: string]: any }) => {
|
||||
filterUserList(row.id)
|
||||
}
|
||||
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep() {
|
||||
.el-transfer {
|
||||
display: flex;
|
||||
}
|
||||
.el-transfer__buttons {
|
||||
display: flex !important;
|
||||
flex-direction: column-reverse;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
.el-transfer__button:nth-child(2) {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -160,13 +160,6 @@
|
||||
<XButton preIcon="ep:refresh" @click="processRestart()" />
|
||||
</el-tooltip>
|
||||
</ElButtonGroup>
|
||||
<XButton
|
||||
preIcon="ep:plus"
|
||||
title="保存模型"
|
||||
@click="processSave"
|
||||
:type="props.headerButtonType"
|
||||
:disabled="simulationStatus"
|
||||
/>
|
||||
</template>
|
||||
<!-- 用于打开本地文件-->
|
||||
<input
|
||||
@@ -315,6 +308,28 @@ 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)
|
||||
let bpmnModeler: any = null
|
||||
const defaultZoom = ref(1)
|
||||
@@ -592,16 +607,6 @@ const processZoomOut = (zoomStep = 0.1) => {
|
||||
defaultZoom.value = newZoom
|
||||
bpmnModeler.get('canvas').zoom(defaultZoom.value)
|
||||
}
|
||||
// const processZoomTo = (newZoom = 1) => {
|
||||
// if (newZoom < 0.2) {
|
||||
// throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2')
|
||||
// }
|
||||
// if (newZoom > 4) {
|
||||
// throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4')
|
||||
// }
|
||||
// defaultZoom = newZoom
|
||||
// bpmnModeler.get('canvas').zoom(newZoom)
|
||||
// }
|
||||
const processReZoom = () => {
|
||||
defaultZoom.value = 1
|
||||
bpmnModeler.get('canvas').zoom('fit-viewport', 'auto')
|
||||
@@ -640,63 +645,19 @@ const previewProcessXML = () => {
|
||||
}
|
||||
const previewProcessJson = () => {
|
||||
bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
|
||||
// console.log(xml, 'xml')
|
||||
|
||||
// const rootNode = parseXmlString(xml)
|
||||
// console.log(rootNode, 'rootNoderootNode')
|
||||
const rootNodes = new XmlNode(XmlNodeType.Root, parseXmlString(xml))
|
||||
// console.log(rootNodes, 'rootNodesrootNodesrootNodes')
|
||||
// console.log(rootNodes.parent.toJsObject(), 'rootNodes.toJSON()')
|
||||
// console.log(JSON.stringify(rootNodes.parent.toJsObject()), 'rootNodes.toJSON()')
|
||||
// console.log(JSON.stringify(rootNodes.parent.toJSON()), 'rootNodes.toJSON()')
|
||||
|
||||
// const parser = new xml2js.XMLParser()
|
||||
// let jObj = parser.parse(xml)
|
||||
// console.log(jObj, 'jObjjObjjObjjObjjObj')
|
||||
// const builder = new xml2js.XMLBuilder(xml)
|
||||
// const xmlContent = builder
|
||||
// console.log(xmlContent, 'xmlContent')
|
||||
// console.log(xml2js, 'convertconvertconvert')
|
||||
previewResult.value = rootNodes.parent?.toJSON() as unknown as string
|
||||
// previewResult.value = jObj
|
||||
// previewResult.value = convert.xml2json(xml, {explicitArray : false},{ spaces: 2 })
|
||||
previewType.value = 'json'
|
||||
previewModelVisible.value = true
|
||||
})
|
||||
}
|
||||
|
||||
/* ------------------------------------------------ 芋道源码 methods ------------------------------------------------------ */
|
||||
const processSave = async () => {
|
||||
// console.log(bpmnModeler, 'bpmnModelerbpmnModelerbpmnModelerbpmnModeler')
|
||||
const { err, xml } = await bpmnModeler.saveXML()
|
||||
// console.log(err, 'errerrerrerrerr')
|
||||
// console.log(xml, 'xmlxmlxmlxmlxml')
|
||||
// 读取异常时抛出异常
|
||||
if (err) {
|
||||
// this.$modal.msgError('保存模型失败,请重试!')
|
||||
alert('保存模型失败,请重试!')
|
||||
return
|
||||
}
|
||||
// 触发 save 事件
|
||||
emit('save', xml)
|
||||
}
|
||||
/** 高亮显示 */
|
||||
// const highlightedCode = (previewType, previewResult) => {
|
||||
// console.log(previewType, 'previewType, previewResult')
|
||||
// console.log(previewResult, 'previewType, previewResult')
|
||||
// console.log(hljs.highlight, 'hljs.highlight')
|
||||
// const result = hljs.highlight(previewType, previewResult.value || '', true)
|
||||
// return result.value || ' '
|
||||
// }
|
||||
onBeforeMount(() => {
|
||||
console.log(props, 'propspropspropsprops')
|
||||
})
|
||||
onMounted(() => {
|
||||
initBpmnModeler()
|
||||
createNewDiagram(props.value)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
// this.$once('hook:beforeDestroy', () => {
|
||||
// })
|
||||
if (bpmnModeler) bpmnModeler.destroy()
|
||||
emit('destroy', bpmnModeler)
|
||||
bpmnModeler = null
|
||||
|
||||
@@ -406,6 +406,31 @@
|
||||
"name": "variableMappingDelegateExpression",
|
||||
"isAttr": true,
|
||||
"type": "String"
|
||||
},
|
||||
{
|
||||
"name": "calledElementType",
|
||||
"isAttr": true,
|
||||
"type": "String"
|
||||
},
|
||||
{
|
||||
"name": "processInstanceName",
|
||||
"isAttr": true,
|
||||
"type": "String"
|
||||
},
|
||||
{
|
||||
"name": "inheritBusinessKey",
|
||||
"isAttr": true,
|
||||
"type": "Boolean"
|
||||
},
|
||||
{
|
||||
"name": "businessKey",
|
||||
"isAttr": true,
|
||||
"type": "String"
|
||||
},
|
||||
{
|
||||
"name": "inheritVariables",
|
||||
"isAttr": true,
|
||||
"type": "Boolean"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1211,6 +1236,208 @@
|
||||
"isAttr": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AssignStartUserHandlerType",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:StartEvent", "bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "Integer",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "RejectHandlerType",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "Integer",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "RejectReturnTaskId",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "String",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AssignEmptyHandlerType",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "Integer",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AssignEmptyUserIds",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "String",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ButtonsSetting",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "flowable:id",
|
||||
"type": "Integer",
|
||||
"isAttr": true
|
||||
},
|
||||
{
|
||||
"name": "flowable:enable",
|
||||
"type": "Boolean",
|
||||
"isAttr": true
|
||||
},
|
||||
{
|
||||
"name": "flowable:displayName",
|
||||
"type": "String",
|
||||
"isAttr": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FieldsPermission",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "flowable:field",
|
||||
"type": "String",
|
||||
"isAttr": true
|
||||
},
|
||||
{
|
||||
"name": "flowable:title",
|
||||
"type": "String",
|
||||
"isAttr": true
|
||||
},
|
||||
{
|
||||
"name": "flowable:permission",
|
||||
"type": "String",
|
||||
"isAttr": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "BoundaryEventType",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:BoundaryEvent"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "Integer",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "TimeoutHandlerType",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:BoundaryEvent"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "Integer",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ApproveType",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "Integer",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ApproveMethod",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "Integer",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "CandidateStrategy",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "Integer",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "CandidateParam",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "String",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"emumerations": []
|
||||
|
||||
@@ -165,6 +165,18 @@ F.prototype.getPaletteEntries = function () {
|
||||
'bpmn-icon-user-task',
|
||||
translate('Create User Task')
|
||||
),
|
||||
'create.call-activity': createAction(
|
||||
'bpmn:CallActivity',
|
||||
'activity',
|
||||
'bpmn-icon-call-activity',
|
||||
translate('Create Call Activity')
|
||||
),
|
||||
'create.service-task': createAction(
|
||||
'bpmn:ServiceTask',
|
||||
'activity',
|
||||
'bpmn-icon-service',
|
||||
translate('Create Service Task')
|
||||
),
|
||||
'create.data-object': createAction(
|
||||
'bpmn:DataObjectReference',
|
||||
'data-object',
|
||||
|
||||
@@ -171,6 +171,12 @@ PaletteProvider.prototype.getPaletteEntries = function () {
|
||||
'bpmn-icon-user-task',
|
||||
translate('Create User Task')
|
||||
),
|
||||
'create.service-task': createAction(
|
||||
'bpmn:ServiceTask',
|
||||
'activity',
|
||||
'bpmn-icon-service',
|
||||
translate('Create Service Task')
|
||||
),
|
||||
'create.data-object': createAction(
|
||||
'bpmn:DataObjectReference',
|
||||
'data-object',
|
||||
|
||||
@@ -56,6 +56,8 @@ export default {
|
||||
'Create EndEvent': '创建结束事件',
|
||||
'Create Task': '创建任务',
|
||||
'Create User Task': '创建用户任务',
|
||||
'Create Call Activity': '创建调用活动',
|
||||
'Create Service Task': '创建服务任务',
|
||||
'Create Gateway': '创建网关',
|
||||
'Create DataObjectReference': '创建数据对象',
|
||||
'Create DataStoreReference': '创建数据存储',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="process-panel__container" :style="{ width: `${width}px` }">
|
||||
<el-collapse v-model="activeTab">
|
||||
<el-collapse v-model="activeTab" v-if="isReady">
|
||||
<el-collapse-item name="base">
|
||||
<!-- class="panel-tab__title" -->
|
||||
<template #title>
|
||||
@@ -26,8 +26,10 @@
|
||||
<template #title><Icon icon="ep:list" />表单</template>
|
||||
<element-form :id="elementId" :type="elementType" />
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="task" v-if="elementType.indexOf('Task') !== -1" key="task">
|
||||
<template #title><Icon icon="ep:checked" />任务(审批人)</template>
|
||||
<el-collapse-item name="task" v-if="isTaskCollapseItemShow(elementType)" key="task">
|
||||
<template #title
|
||||
><Icon icon="ep:checked" />{{ getTaskCollapseItemName(elementType) }}</template
|
||||
>
|
||||
<element-task :id="elementId" :type="elementType" />
|
||||
</el-collapse-item>
|
||||
<el-collapse-item
|
||||
@@ -35,8 +37,12 @@
|
||||
v-if="elementType.indexOf('Task') !== -1"
|
||||
key="multiInstance"
|
||||
>
|
||||
<template #title><Icon icon="ep:help-filled" />多实例(会签配置)</template>
|
||||
<element-multi-instance :business-object="elementBusinessObject" :type="elementType" />
|
||||
<template #title><Icon icon="ep:help-filled" />多人审批方式</template>
|
||||
<element-multi-instance
|
||||
:id="elementId"
|
||||
:business-object="elementBusinessObject"
|
||||
:type="elementType"
|
||||
/>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="listeners" key="listeners">
|
||||
<template #title><Icon icon="ep:bell-filled" />执行监听器</template>
|
||||
@@ -54,6 +60,14 @@
|
||||
<template #title><Icon icon="ep:promotion" />其他</template>
|
||||
<element-other-config :id="elementId" />
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="customConfig" key="customConfig">
|
||||
<template #title><Icon icon="ep:tools" />自定义配置</template>
|
||||
<element-custom-config
|
||||
:id="elementId"
|
||||
:type="elementType"
|
||||
:business-object="elementBusinessObject"
|
||||
/>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</template>
|
||||
@@ -68,6 +82,7 @@ import ElementListeners from './listeners/ElementListeners.vue'
|
||||
import ElementProperties from './properties/ElementProperties.vue'
|
||||
// import ElementForm from './form/ElementForm.vue'
|
||||
import UserTaskListeners from './listeners/UserTaskListeners.vue'
|
||||
import { getTaskCollapseItemName, isTaskCollapseItemShow } from './task/data'
|
||||
|
||||
defineOptions({ name: 'MyPropertiesPanel' })
|
||||
|
||||
@@ -104,24 +119,16 @@ const elementBusinessObject = ref<any>({}) // 元素 businessObject 镜像,提
|
||||
const conditionFormVisible = ref(false) // 流转条件设置
|
||||
const formVisible = ref(false) // 表单配置
|
||||
const bpmnElement = ref()
|
||||
const isReady = ref(false)
|
||||
|
||||
provide('prefix', props.prefix)
|
||||
provide('width', props.width)
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances
|
||||
|
||||
// 监听 props.bpmnModeler 然后 initModels
|
||||
const unwatchBpmn = watch(
|
||||
() => props.bpmnModeler,
|
||||
() => {
|
||||
// 避免加载时 流程图 并未加载完成
|
||||
if (!props.bpmnModeler) {
|
||||
console.log('缺少props.bpmnModeler')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('props.bpmnModeler 有值了!!!')
|
||||
const w = window as any
|
||||
w.bpmnInstances = {
|
||||
// 初始化 bpmnInstances
|
||||
const initBpmnInstances = () => {
|
||||
if (!props.bpmnModeler) return false
|
||||
try {
|
||||
const instances = {
|
||||
modeler: props.bpmnModeler,
|
||||
modeling: props.bpmnModeler.get('modeling'),
|
||||
moddle: props.bpmnModeler.get('moddle'),
|
||||
@@ -133,9 +140,45 @@ const unwatchBpmn = watch(
|
||||
selection: props.bpmnModeler.get('selection')
|
||||
}
|
||||
|
||||
console.log(bpmnInstances(), 'window.bpmnInstances')
|
||||
getActiveElement()
|
||||
unwatchBpmn()
|
||||
// 检查所有实例是否都存在
|
||||
const allInstancesExist = Object.values(instances).every(instance => instance)
|
||||
if (allInstancesExist) {
|
||||
const w = window as any
|
||||
w.bpmnInstances = instances
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} catch (error) {
|
||||
console.error('初始化 bpmnInstances 失败:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances
|
||||
|
||||
// 监听 props.bpmnModeler 然后 initModels
|
||||
const unwatchBpmn = watch(
|
||||
() => props.bpmnModeler,
|
||||
async () => {
|
||||
// 避免加载时 流程图 并未加载完成
|
||||
if (!props.bpmnModeler) {
|
||||
console.log('缺少props.bpmnModeler')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 等待 modeler 初始化完成
|
||||
await nextTick()
|
||||
if (initBpmnInstances()) {
|
||||
isReady.value = true
|
||||
await nextTick()
|
||||
getActiveElement()
|
||||
} else {
|
||||
console.error('modeler 实例未完全初始化')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('初始化失败:', error)
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
@@ -143,6 +186,8 @@ const unwatchBpmn = watch(
|
||||
)
|
||||
|
||||
const getActiveElement = () => {
|
||||
if (!isReady.value || !props.bpmnModeler) return
|
||||
|
||||
// 初始第一个选中元素 bpmn:Process
|
||||
initFormOnChanged(null)
|
||||
props.bpmnModeler.on('import.done', (e) => {
|
||||
@@ -160,8 +205,11 @@ const getActiveElement = () => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
const initFormOnChanged = (element) => {
|
||||
if (!isReady.value || !bpmnInstances()) return
|
||||
|
||||
let activatedElement = element
|
||||
if (!activatedElement) {
|
||||
activatedElement =
|
||||
@@ -169,32 +217,36 @@ const initFormOnChanged = (element) => {
|
||||
bpmnInstances().elementRegistry.find((el) => el.type === 'bpmn:Collaboration')
|
||||
}
|
||||
if (!activatedElement) return
|
||||
console.log(`
|
||||
----------
|
||||
select element changed:
|
||||
id: ${activatedElement.id}
|
||||
type: ${activatedElement.businessObject.$type}
|
||||
----------
|
||||
`)
|
||||
console.log('businessObject: ', activatedElement.businessObject)
|
||||
bpmnInstances().bpmnElement = activatedElement
|
||||
bpmnElement.value = activatedElement
|
||||
elementId.value = activatedElement.id
|
||||
elementType.value = activatedElement.type.split(':')[1] || ''
|
||||
elementBusinessObject.value = JSON.parse(JSON.stringify(activatedElement.businessObject))
|
||||
conditionFormVisible.value = !!(
|
||||
elementType.value === 'SequenceFlow' &&
|
||||
activatedElement.source &&
|
||||
activatedElement.source.type.indexOf('StartEvent') === -1
|
||||
)
|
||||
formVisible.value = elementType.value === 'UserTask' || elementType.value === 'StartEvent'
|
||||
|
||||
try {
|
||||
console.log(`
|
||||
----------
|
||||
select element changed:
|
||||
id: ${activatedElement.id}
|
||||
type: ${activatedElement.businessObject.$type}
|
||||
----------
|
||||
`)
|
||||
console.log('businessObject: ', activatedElement.businessObject)
|
||||
bpmnInstances().bpmnElement = activatedElement
|
||||
bpmnElement.value = activatedElement
|
||||
elementId.value = activatedElement.id
|
||||
elementType.value = activatedElement.type.split(':')[1] || ''
|
||||
elementBusinessObject.value = JSON.parse(JSON.stringify(activatedElement.businessObject))
|
||||
conditionFormVisible.value = !!(
|
||||
elementType.value === 'SequenceFlow' &&
|
||||
activatedElement.source &&
|
||||
activatedElement.source.type.indexOf('StartEvent') === -1
|
||||
)
|
||||
formVisible.value = elementType.value === 'UserTask' || elementType.value === 'StartEvent'
|
||||
} catch (error) {
|
||||
console.error('初始化表单数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
const w = window as any
|
||||
w.bpmnInstances = null
|
||||
console.log(props, 'props1')
|
||||
console.log(props.bpmnModeler, 'props.bpmnModeler1')
|
||||
isReady.value = false
|
||||
})
|
||||
|
||||
watch(
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<component :is="customConfigComponent" v-bind="$props" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { CustomConfigMap } from './data'
|
||||
|
||||
defineOptions({ name: 'ElementCustomConfig' })
|
||||
|
||||
const props = defineProps({
|
||||
id: String,
|
||||
type: String,
|
||||
businessObject: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances
|
||||
const customConfigComponent = ref<any>(null)
|
||||
|
||||
watch(
|
||||
() => props.businessObject,
|
||||
() => {
|
||||
if (props.type && props.businessObject) {
|
||||
let val = props.type
|
||||
if (props.businessObject.eventDefinitions) {
|
||||
val += props.businessObject.eventDefinitions[0]?.$type.split(':')[1] || ''
|
||||
}
|
||||
customConfigComponent.value = CustomConfigMap[val]?.componet
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,252 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-divider content-position="left">审批人超时未处理时</el-divider>
|
||||
<el-form-item label="启用开关" prop="timeoutHandlerEnable">
|
||||
<el-switch
|
||||
v-model="timeoutHandlerEnable"
|
||||
active-text="开启"
|
||||
inactive-text="关闭"
|
||||
@change="timeoutHandlerChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="执行动作" prop="timeoutHandlerType" v-if="timeoutHandlerEnable">
|
||||
<el-radio-group v-model="timeoutHandlerType.value" @change="onTimeoutHandlerTypeChanged">
|
||||
<el-radio-button
|
||||
v-for="item in TIMEOUT_HANDLER_TYPES"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
/>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="超时时间设置" v-if="timeoutHandlerEnable">
|
||||
<span class="mr-2">当超过</span>
|
||||
<el-form-item prop="timeDuration">
|
||||
<el-input-number
|
||||
class="mr-2"
|
||||
:style="{ width: '100px' }"
|
||||
v-model="timeDuration"
|
||||
:min="1"
|
||||
controls-position="right"
|
||||
@change="() => updateTimeModdle()"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-select
|
||||
v-model="timeUnit"
|
||||
class="mr-2"
|
||||
:style="{ width: '100px' }"
|
||||
@change="onTimeUnitChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in TIME_UNIT_TYPES"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
未处理
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="最大提醒次数"
|
||||
prop="maxRemindCount"
|
||||
v-if="timeoutHandlerEnable && timeoutHandlerType.value === 1"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="maxRemindCount"
|
||||
:min="1"
|
||||
:max="10"
|
||||
@change="() => updateTimeModdle()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
TimeUnitType,
|
||||
TIME_UNIT_TYPES,
|
||||
TIMEOUT_HANDLER_TYPES,
|
||||
} from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import { convertTimeUnit } from '@/components/SimpleProcessDesignerV2/src/utils'
|
||||
|
||||
defineOptions({ name: 'ElementCustomConfig4BoundaryEventTimer' })
|
||||
const props = defineProps({
|
||||
id: String,
|
||||
type: String
|
||||
})
|
||||
const prefix = inject('prefix')
|
||||
|
||||
const bpmnElement = ref()
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances
|
||||
|
||||
const timeoutHandlerEnable = ref(false)
|
||||
const boundaryEventType = ref()
|
||||
const timeoutHandlerType = ref({
|
||||
value: undefined
|
||||
})
|
||||
const timeModdle = ref()
|
||||
const timeDuration = ref(6)
|
||||
const timeUnit = ref(TimeUnitType.HOUR)
|
||||
const maxRemindCount = ref(1)
|
||||
|
||||
const elExtensionElements = ref()
|
||||
const otherExtensions = ref()
|
||||
const configExtensions = ref([])
|
||||
const eventDefinition = ref()
|
||||
|
||||
const resetElement = () => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement
|
||||
eventDefinition.value = bpmnElement.value.businessObject.eventDefinitions[0]
|
||||
|
||||
// 获取元素扩展属性 或者 创建扩展属性
|
||||
elExtensionElements.value =
|
||||
bpmnElement.value.businessObject?.extensionElements ??
|
||||
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
|
||||
|
||||
// 是否开启自定义用户任务超时处理
|
||||
boundaryEventType.value = elExtensionElements.value.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:BoundaryEventType`
|
||||
)?.[0]
|
||||
if (boundaryEventType.value && boundaryEventType.value.value === 1) {
|
||||
timeoutHandlerEnable.value = true
|
||||
configExtensions.value.push(boundaryEventType.value)
|
||||
}
|
||||
|
||||
// 执行动作
|
||||
timeoutHandlerType.value = elExtensionElements.value.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:TimeoutHandlerType`
|
||||
)?.[0]
|
||||
if (timeoutHandlerType.value) {
|
||||
configExtensions.value.push(timeoutHandlerType.value)
|
||||
if (eventDefinition.value.timeCycle) {
|
||||
const timeStr = eventDefinition.value.timeCycle.body
|
||||
const maxRemindCountStr = timeStr.split('/')[0]
|
||||
const timeDurationStr = timeStr.split('/')[1]
|
||||
console.log(maxRemindCountStr)
|
||||
maxRemindCount.value = parseInt(maxRemindCountStr.slice(1))
|
||||
timeDuration.value = parseInt(timeDurationStr.slice(2, timeDurationStr.length - 1))
|
||||
timeUnit.value = convertTimeUnit(timeDurationStr.slice(timeDurationStr.length - 1))
|
||||
timeModdle.value = eventDefinition.value.timeCycle
|
||||
}
|
||||
if (eventDefinition.value.timeDuration) {
|
||||
const timeDurationStr = eventDefinition.value.timeDuration.body
|
||||
timeDuration.value = parseInt(timeDurationStr.slice(2, timeDurationStr.length - 1))
|
||||
timeUnit.value = convertTimeUnit(timeDurationStr.slice(timeDurationStr.length - 1))
|
||||
timeModdle.value = eventDefinition.value.timeDuration
|
||||
}
|
||||
}
|
||||
|
||||
// 保留剩余扩展元素,便于后面更新该元素对应属性
|
||||
otherExtensions.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex) =>
|
||||
ex.$type !== `${prefix}:BoundaryEventType` && ex.$type !== `${prefix}:TimeoutHandlerType`
|
||||
) ?? []
|
||||
}
|
||||
|
||||
const timeoutHandlerChange = (val) => {
|
||||
timeoutHandlerEnable.value = val
|
||||
if (val) {
|
||||
// 启用自定义用户任务超时处理
|
||||
// 边界事件类型 --- 超时
|
||||
boundaryEventType.value = bpmnInstances().moddle.create(`${prefix}:BoundaryEventType`, {
|
||||
value: 1
|
||||
})
|
||||
configExtensions.value.push(boundaryEventType.value)
|
||||
// 超时处理类型
|
||||
timeoutHandlerType.value = bpmnInstances().moddle.create(`${prefix}:TimeoutHandlerType`, {
|
||||
value: 1
|
||||
})
|
||||
configExtensions.value.push(timeoutHandlerType.value)
|
||||
// 超时时间表达式
|
||||
timeDuration.value = 6
|
||||
timeUnit.value = 2
|
||||
maxRemindCount.value = 1
|
||||
timeModdle.value = bpmnInstances().moddle.create(`bpmn:Expression`, {
|
||||
body: 'PT6H'
|
||||
})
|
||||
eventDefinition.value.timeDuration = timeModdle.value
|
||||
} else {
|
||||
// 关闭自定义用户任务超时处理
|
||||
configExtensions.value = []
|
||||
delete eventDefinition.value.timeDuration
|
||||
delete eventDefinition.value.timeCycle
|
||||
}
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
||||
const onTimeoutHandlerTypeChanged = () => {
|
||||
maxRemindCount.value = 1
|
||||
updateElementExtensions()
|
||||
updateTimeModdle()
|
||||
}
|
||||
|
||||
const onTimeUnitChange = () => {
|
||||
// 分钟,默认是 60 分钟
|
||||
if (timeUnit.value === TimeUnitType.MINUTE) {
|
||||
timeDuration.value = 60
|
||||
}
|
||||
// 小时,默认是 6 个小时
|
||||
if (timeUnit.value === TimeUnitType.HOUR) {
|
||||
timeDuration.value = 6
|
||||
}
|
||||
// 天, 默认 1天
|
||||
if (timeUnit.value === TimeUnitType.DAY) {
|
||||
timeDuration.value = 1
|
||||
}
|
||||
updateTimeModdle()
|
||||
}
|
||||
|
||||
const updateTimeModdle = () => {
|
||||
if (maxRemindCount.value > 1) {
|
||||
timeModdle.value.body = 'R' + maxRemindCount.value + '/' + isoTimeDuration()
|
||||
if (!eventDefinition.value.timeCycle) {
|
||||
delete eventDefinition.value.timeDuration
|
||||
eventDefinition.value.timeCycle = timeModdle.value
|
||||
}
|
||||
} else {
|
||||
timeModdle.value.body = isoTimeDuration()
|
||||
if (!eventDefinition.value.timeDuration) {
|
||||
delete eventDefinition.value.timeCycle
|
||||
eventDefinition.value.timeDuration = timeModdle.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isoTimeDuration = () => {
|
||||
let strTimeDuration = 'PT'
|
||||
if (timeUnit.value === TimeUnitType.MINUTE) {
|
||||
strTimeDuration += timeDuration.value + 'M'
|
||||
}
|
||||
if (timeUnit.value === TimeUnitType.HOUR) {
|
||||
strTimeDuration += timeDuration.value + 'H'
|
||||
}
|
||||
if (timeUnit.value === TimeUnitType.DAY) {
|
||||
strTimeDuration += timeDuration.value + 'D'
|
||||
}
|
||||
return strTimeDuration
|
||||
}
|
||||
|
||||
const updateElementExtensions = () => {
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: [...otherExtensions.value, ...configExtensions.value]
|
||||
})
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
extensionElements: extensions
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val) => {
|
||||
val &&
|
||||
val.length &&
|
||||
nextTick(() => {
|
||||
resetElement()
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,623 @@
|
||||
<!-- UserTask 自定义配置:
|
||||
1. 审批人与提交人为同一人时
|
||||
2. 审批人拒绝时
|
||||
3. 审批人为空时
|
||||
4. 操作按钮
|
||||
5. 字段权限
|
||||
6. 审批类型
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<el-divider content-position="left">审批类型</el-divider>
|
||||
<el-form-item prop="approveType">
|
||||
<el-radio-group v-model="approveType.value">
|
||||
<el-radio
|
||||
v-for="(item, index) in APPROVE_TYPE"
|
||||
:key="index"
|
||||
:value="item.value"
|
||||
:label="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">审批人拒绝时</el-divider>
|
||||
<el-form-item prop="rejectHandlerType">
|
||||
<el-radio-group
|
||||
v-model="rejectHandlerType"
|
||||
:disabled="returnTaskList.length === 0"
|
||||
@change="updateRejectHandlerType"
|
||||
>
|
||||
<div class="flex-col">
|
||||
<div v-for="(item, index) in REJECT_HANDLER_TYPES" :key="index">
|
||||
<el-radio :key="item.value" :value="item.value" :label="item.label" />
|
||||
</div>
|
||||
</div>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="rejectHandlerType == RejectHandlerType.RETURN_USER_TASK"
|
||||
label="驳回节点"
|
||||
prop="returnNodeId"
|
||||
>
|
||||
<el-select v-model="returnNodeId" clearable style="width: 100%" @change="updateReturnNodeId">
|
||||
<el-option
|
||||
v-for="item in returnTaskList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">审批人为空时</el-divider>
|
||||
<el-form-item prop="assignEmptyHandlerType">
|
||||
<el-radio-group v-model="assignEmptyHandlerType" @change="updateAssignEmptyHandlerType">
|
||||
<div class="flex-col">
|
||||
<div v-for="(item, index) in ASSIGN_EMPTY_HANDLER_TYPES" :key="index">
|
||||
<el-radio :key="item.value" :value="item.value" :label="item.label" />
|
||||
</div>
|
||||
</div>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="assignEmptyHandlerType == AssignEmptyHandlerType.ASSIGN_USER"
|
||||
label="指定用户"
|
||||
prop="assignEmptyHandlerUserIds"
|
||||
span="24"
|
||||
>
|
||||
<el-select
|
||||
v-model="assignEmptyUserIds"
|
||||
clearable
|
||||
multiple
|
||||
style="width: 100%"
|
||||
@change="updateAssignEmptyUserIds"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">审批人与提交人为同一人时</el-divider>
|
||||
<el-radio-group v-model="assignStartUserHandlerType" @change="updateAssignStartUserHandlerType">
|
||||
<div class="flex-col">
|
||||
<div v-for="(item, index) in ASSIGN_START_USER_HANDLER_TYPES" :key="index">
|
||||
<el-radio :key="item.value" :value="item.value" :label="item.label" />
|
||||
</div>
|
||||
</div>
|
||||
</el-radio-group>
|
||||
|
||||
<el-divider content-position="left">操作按钮</el-divider>
|
||||
<div class="button-setting-pane">
|
||||
<div class="button-setting-title">
|
||||
<div class="button-title-label">操作按钮</div>
|
||||
<div class="pl-4 button-title-label">显示名称</div>
|
||||
<div class="button-title-label">启用</div>
|
||||
</div>
|
||||
<div class="button-setting-item" v-for="(item, index) in buttonsSettingEl" :key="index">
|
||||
<div class="button-setting-item-label"> {{ OPERATION_BUTTON_NAME.get(item.id) }} </div>
|
||||
<div class="button-setting-item-label">
|
||||
<input
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="btnDisplayNameBlurEvent(index)"
|
||||
v-mountedFocus
|
||||
v-model="item.displayName"
|
||||
:placeholder="item.displayName"
|
||||
v-if="btnDisplayNameEdit[index]"
|
||||
/>
|
||||
<el-button v-else text @click="changeBtnDisplayName(index)"
|
||||
>{{ item.displayName }} <Icon icon="ep:edit"
|
||||
/></el-button>
|
||||
</div>
|
||||
<div class="button-setting-item-label">
|
||||
<el-switch v-model="item.enable" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider content-position="left">字段权限</el-divider>
|
||||
<div class="field-setting-pane" v-if="formType === 10">
|
||||
<div class="field-permit-title">
|
||||
<div class="setting-title-label first-title"> 字段名称 </div>
|
||||
<div class="other-titles">
|
||||
<span class="setting-title-label">只读</span>
|
||||
<span class="setting-title-label">可编辑</span>
|
||||
<span class="setting-title-label">隐藏</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-setting-item" v-for="(item, index) in fieldsPermissionEl" :key="index">
|
||||
<div class="field-setting-item-label"> {{ item.title }} </div>
|
||||
<el-radio-group class="field-setting-item-group" v-model="item.permission">
|
||||
<div class="item-radio-wrap">
|
||||
<el-radio
|
||||
:value="FieldPermissionType.READ"
|
||||
size="large"
|
||||
:label="FieldPermissionType.READ"
|
||||
><span></span
|
||||
></el-radio>
|
||||
</div>
|
||||
<div class="item-radio-wrap">
|
||||
<el-radio
|
||||
:value="FieldPermissionType.WRITE"
|
||||
size="large"
|
||||
:label="FieldPermissionType.WRITE"
|
||||
><span></span
|
||||
></el-radio>
|
||||
</div>
|
||||
<div class="item-radio-wrap">
|
||||
<el-radio
|
||||
:value="FieldPermissionType.NONE"
|
||||
size="large"
|
||||
:label="FieldPermissionType.NONE"
|
||||
><span></span
|
||||
></el-radio>
|
||||
</div>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
ASSIGN_START_USER_HANDLER_TYPES,
|
||||
RejectHandlerType,
|
||||
REJECT_HANDLER_TYPES,
|
||||
ASSIGN_EMPTY_HANDLER_TYPES,
|
||||
AssignEmptyHandlerType,
|
||||
OPERATION_BUTTON_NAME,
|
||||
DEFAULT_BUTTON_SETTING,
|
||||
FieldPermissionType,
|
||||
APPROVE_TYPE,
|
||||
ApproveType,
|
||||
ButtonSetting
|
||||
} from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { useFormFieldsPermission } from '@/components/SimpleProcessDesignerV2/src/node'
|
||||
|
||||
defineOptions({ name: 'ElementCustomConfig4UserTask' })
|
||||
const props = defineProps({
|
||||
id: String,
|
||||
type: String
|
||||
})
|
||||
const prefix = inject('prefix')
|
||||
|
||||
// 审批人与提交人为同一人时
|
||||
const assignStartUserHandlerTypeEl = ref()
|
||||
const assignStartUserHandlerType = ref()
|
||||
|
||||
// 审批人拒绝时
|
||||
const rejectHandlerTypeEl = ref()
|
||||
const rejectHandlerType = ref()
|
||||
const returnNodeIdEl = ref()
|
||||
const returnNodeId = ref()
|
||||
const returnTaskList = ref([])
|
||||
|
||||
// 审批人为空时
|
||||
const assignEmptyHandlerTypeEl = ref()
|
||||
const assignEmptyHandlerType = ref()
|
||||
const assignEmptyUserIdsEl = ref()
|
||||
const assignEmptyUserIds = ref()
|
||||
|
||||
// 操作按钮
|
||||
const buttonsSettingEl = ref()
|
||||
const { btnDisplayNameEdit, changeBtnDisplayName, btnDisplayNameBlurEvent } = useButtonsSetting()
|
||||
|
||||
// 字段权限
|
||||
const fieldsPermissionEl = ref([])
|
||||
const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission(
|
||||
FieldPermissionType.READ
|
||||
)
|
||||
|
||||
// 审批类型
|
||||
const approveType = ref({ value: ApproveType.USER })
|
||||
|
||||
const elExtensionElements = ref()
|
||||
const otherExtensions = ref()
|
||||
const bpmnElement = ref()
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances
|
||||
|
||||
const resetCustomConfigList = () => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement
|
||||
|
||||
// 获取可回退的列表
|
||||
returnTaskList.value = findAllPredecessorsExcludingStart(
|
||||
bpmnElement.value.id,
|
||||
bpmnInstances().modeler
|
||||
)
|
||||
|
||||
// 获取元素扩展属性 或者 创建扩展属性
|
||||
elExtensionElements.value =
|
||||
bpmnElement.value.businessObject?.extensionElements ??
|
||||
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
|
||||
|
||||
// 审批类型
|
||||
approveType.value =
|
||||
elExtensionElements.value.values?.filter((ex) => ex.$type === `${prefix}:ApproveType`)?.[0] ||
|
||||
bpmnInstances().moddle.create(`${prefix}:ApproveType`, { value: ApproveType.USER })
|
||||
|
||||
// 审批人与提交人为同一人时
|
||||
assignStartUserHandlerTypeEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:AssignStartUserHandlerType`
|
||||
)?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignStartUserHandlerType`, { value: 1 })
|
||||
assignStartUserHandlerType.value = assignStartUserHandlerTypeEl.value.value
|
||||
|
||||
// 审批人拒绝时
|
||||
rejectHandlerTypeEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:RejectHandlerType`
|
||||
)?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectHandlerType`, { value: 1 })
|
||||
rejectHandlerType.value = rejectHandlerTypeEl.value.value
|
||||
returnNodeIdEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:RejectReturnTaskId`
|
||||
)?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectReturnTaskId`, { value: '' })
|
||||
returnNodeId.value = returnNodeIdEl.value.value
|
||||
|
||||
// 审批人为空时
|
||||
assignEmptyHandlerTypeEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:AssignEmptyHandlerType`
|
||||
)?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyHandlerType`, { value: 1 })
|
||||
assignEmptyHandlerType.value = assignEmptyHandlerTypeEl.value.value
|
||||
assignEmptyUserIdsEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:AssignEmptyUserIds`
|
||||
)?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyUserIds`, { value: '' })
|
||||
assignEmptyUserIds.value = assignEmptyUserIdsEl.value.value?.split(',').map((item) => {
|
||||
// 如果数字超出了最大安全整数范围,则将其作为字符串处理
|
||||
let num = Number(item)
|
||||
return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER ? item : num
|
||||
})
|
||||
|
||||
// 操作按钮
|
||||
buttonsSettingEl.value = elExtensionElements.value.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:ButtonsSetting`
|
||||
)
|
||||
if (buttonsSettingEl.value.length === 0) {
|
||||
DEFAULT_BUTTON_SETTING.forEach((item) => {
|
||||
buttonsSettingEl.value.push(
|
||||
bpmnInstances().moddle.create(`${prefix}:ButtonsSetting`, {
|
||||
'flowable:id': item.id,
|
||||
'flowable:displayName': item.displayName,
|
||||
'flowable:enable': item.enable
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// 字段权限
|
||||
if (formType.value === 10) {
|
||||
const fieldsPermissionList = elExtensionElements.value.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:FieldsPermission`
|
||||
)
|
||||
fieldsPermissionEl.value = []
|
||||
getNodeConfigFormFields()
|
||||
// 由于默认添加了发起人元素,这里需要删掉
|
||||
fieldsPermissionConfig.value = fieldsPermissionConfig.value.slice(1)
|
||||
fieldsPermissionConfig.value.forEach((element) => {
|
||||
element.permission =
|
||||
fieldsPermissionList?.find((obj) => obj.field === element.field)?.permission ?? '1'
|
||||
fieldsPermissionEl.value.push(
|
||||
bpmnInstances().moddle.create(`${prefix}:FieldsPermission`, element)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// 保留剩余扩展元素,便于后面更新该元素对应属性
|
||||
otherExtensions.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex) =>
|
||||
ex.$type !== `${prefix}:AssignStartUserHandlerType` &&
|
||||
ex.$type !== `${prefix}:RejectHandlerType` &&
|
||||
ex.$type !== `${prefix}:RejectReturnTaskId` &&
|
||||
ex.$type !== `${prefix}:AssignEmptyHandlerType` &&
|
||||
ex.$type !== `${prefix}:AssignEmptyUserIds` &&
|
||||
ex.$type !== `${prefix}:ButtonsSetting` &&
|
||||
ex.$type !== `${prefix}:FieldsPermission` &&
|
||||
ex.$type !== `${prefix}:ApproveType`
|
||||
) ?? []
|
||||
|
||||
// 更新元素扩展属性,避免后续报错
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
||||
const updateAssignStartUserHandlerType = () => {
|
||||
assignStartUserHandlerTypeEl.value.value = assignStartUserHandlerType.value
|
||||
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
||||
const updateRejectHandlerType = () => {
|
||||
rejectHandlerTypeEl.value.value = rejectHandlerType.value
|
||||
|
||||
returnNodeId.value = returnTaskList.value[0].id
|
||||
returnNodeIdEl.value.value = returnNodeId.value
|
||||
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
||||
const updateReturnNodeId = () => {
|
||||
returnNodeIdEl.value.value = returnNodeId.value
|
||||
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
||||
const updateAssignEmptyHandlerType = () => {
|
||||
assignEmptyHandlerTypeEl.value.value = assignEmptyHandlerType.value
|
||||
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
||||
const updateAssignEmptyUserIds = () => {
|
||||
assignEmptyUserIdsEl.value.value = assignEmptyUserIds.value.toString()
|
||||
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
||||
const updateElementExtensions = () => {
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: [
|
||||
...otherExtensions.value,
|
||||
assignStartUserHandlerTypeEl.value,
|
||||
rejectHandlerTypeEl.value,
|
||||
returnNodeIdEl.value,
|
||||
assignEmptyHandlerTypeEl.value,
|
||||
assignEmptyUserIdsEl.value,
|
||||
approveType.value,
|
||||
...buttonsSettingEl.value,
|
||||
...fieldsPermissionEl.value
|
||||
]
|
||||
})
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
extensionElements: extensions
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val) => {
|
||||
val &&
|
||||
val.length &&
|
||||
nextTick(() => {
|
||||
resetCustomConfigList()
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
function findAllPredecessorsExcludingStart(elementId, modeler) {
|
||||
const elementRegistry = modeler.get('elementRegistry')
|
||||
const allConnections = elementRegistry.filter((element) => element.type === 'bpmn:SequenceFlow')
|
||||
const predecessors = new Set() // 使用 Set 来避免重复节点
|
||||
const visited = new Set() // 用于记录已访问的节点
|
||||
|
||||
// 检查是否是开始事件节点
|
||||
function isStartEvent(element) {
|
||||
return element.type === 'bpmn:StartEvent'
|
||||
}
|
||||
|
||||
function findPredecessorsRecursively(element) {
|
||||
// 如果该节点已经访问过,直接返回,避免循环
|
||||
if (visited.has(element)) {
|
||||
return
|
||||
}
|
||||
|
||||
// 标记当前节点为已访问
|
||||
visited.add(element)
|
||||
|
||||
// 获取与当前节点相连的所有连接
|
||||
const incomingConnections = allConnections.filter((connection) => connection.target === element)
|
||||
|
||||
incomingConnections.forEach((connection) => {
|
||||
const source = connection.source // 获取前置节点
|
||||
|
||||
// 只添加不是开始事件的前置节点
|
||||
if (!isStartEvent(source)) {
|
||||
predecessors.add(source.businessObject)
|
||||
// 递归查找前置节点
|
||||
findPredecessorsRecursively(source)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const targetElement = elementRegistry.get(elementId)
|
||||
if (targetElement) {
|
||||
findPredecessorsRecursively(targetElement)
|
||||
}
|
||||
|
||||
return Array.from(predecessors) // 返回前置节点数组
|
||||
}
|
||||
|
||||
function useButtonsSetting() {
|
||||
const buttonsSetting = ref<ButtonSetting[]>()
|
||||
// 操作按钮显示名称可编辑
|
||||
const btnDisplayNameEdit = ref<boolean[]>([])
|
||||
const changeBtnDisplayName = (index: number) => {
|
||||
btnDisplayNameEdit.value[index] = true
|
||||
}
|
||||
const btnDisplayNameBlurEvent = (index: number) => {
|
||||
btnDisplayNameEdit.value[index] = false
|
||||
const buttonItem = buttonsSetting.value![index]
|
||||
buttonItem.displayName = buttonItem.displayName || OPERATION_BUTTON_NAME.get(buttonItem.id)!
|
||||
}
|
||||
return {
|
||||
buttonsSetting,
|
||||
btnDisplayNameEdit,
|
||||
changeBtnDisplayName,
|
||||
btnDisplayNameBlurEvent
|
||||
}
|
||||
}
|
||||
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
onMounted(async () => {
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.button-setting-pane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 14px;
|
||||
margin-top: 8px;
|
||||
|
||||
.button-setting-desc {
|
||||
padding-right: 8px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.button-setting-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 45px;
|
||||
padding-left: 12px;
|
||||
background-color: #f8fafc0a;
|
||||
border: 1px solid #1f38581a;
|
||||
|
||||
& > :first-child {
|
||||
width: 100px !important;
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
& > :last-child {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.button-title-label {
|
||||
width: 150px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #000;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.button-setting-item {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 38px;
|
||||
padding-left: 12px;
|
||||
border: 1px solid #1f38581a;
|
||||
border-top: 0;
|
||||
|
||||
& > :first-child {
|
||||
width: 100px !important;
|
||||
}
|
||||
|
||||
& > :last-child {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.button-setting-item-label {
|
||||
width: 150px;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.editable-title-input {
|
||||
height: 24px;
|
||||
max-width: 130px;
|
||||
margin-left: 4px;
|
||||
line-height: 24px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:focus {
|
||||
border-color: #40a9ff;
|
||||
outline: 0;
|
||||
box-shadow: 0 0 0 2px rgb(24 144 255 / 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.field-setting-pane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 14px;
|
||||
|
||||
.field-setting-desc {
|
||||
padding-right: 8px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.field-permit-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 45px;
|
||||
padding-left: 12px;
|
||||
line-height: 45px;
|
||||
background-color: #f8fafc0a;
|
||||
border: 1px solid #1f38581a;
|
||||
|
||||
.first-title {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.other-titles {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.setting-title-label {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
padding: 5px 0;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.field-setting-item {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 38px;
|
||||
padding-left: 12px;
|
||||
border: 1px solid #1f38581a;
|
||||
border-top: 0;
|
||||
|
||||
.field-setting-item-label {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
min-height: 16px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.field-setting-item-group {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.item-radio-wrap {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,13 @@
|
||||
import UserTaskCustomConfig from './components/UserTaskCustomConfig.vue'
|
||||
import BoundaryEventTimer from './components/BoundaryEventTimer.vue'
|
||||
|
||||
export const CustomConfigMap = {
|
||||
UserTask: {
|
||||
name: '用户任务',
|
||||
componet: UserTaskCustomConfig
|
||||
},
|
||||
BoundaryEventTimerEventDefinition: {
|
||||
name: '定时边界事件(非中断)',
|
||||
componet: BoundaryEventTimer
|
||||
}
|
||||
}
|
||||
@@ -268,9 +268,9 @@ const bpmnInstances = () => (window as any)?.bpmnInstances
|
||||
const resetFormList = () => {
|
||||
bpmnELement.value = bpmnInstances().bpmnElement
|
||||
formKey.value = bpmnELement.value.businessObject.formKey
|
||||
if (formKey.value?.length > 0) {
|
||||
formKey.value = parseInt(formKey.value)
|
||||
}
|
||||
// if (formKey.value?.length > 0) {
|
||||
// formKey.value = parseInt(formKey.value)
|
||||
// }
|
||||
// 获取元素扩展属性 或者 创建扩展属性
|
||||
elExtensionElements.value =
|
||||
bpmnELement.value.businessObject.get('extensionElements') ||
|
||||
|
||||
@@ -1,6 +1,30 @@
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<el-form label-width="90px">
|
||||
<el-radio-group v-model="approveMethod" @change="onApproveMethodChange">
|
||||
<div class="flex-col">
|
||||
<div v-for="(item, index) in APPROVE_METHODS" :key="index">
|
||||
<el-radio :value="item.value" :label="item.value">
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
<el-form-item prop="approveRatio">
|
||||
<el-input-number
|
||||
v-model="approveRatio"
|
||||
:min="10"
|
||||
:max="100"
|
||||
:step="10"
|
||||
size="small"
|
||||
v-if="
|
||||
item.value === ApproveMethodType.APPROVE_BY_RATIO &&
|
||||
approveMethod === ApproveMethodType.APPROVE_BY_RATIO
|
||||
"
|
||||
@change="onApproveRatioChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</el-radio-group>
|
||||
<!-- 与Simple设计器配置合并,保留以前的代码 -->
|
||||
<el-form label-width="90px" style="display: none">
|
||||
<el-form-item label="快捷配置">
|
||||
<el-button size="small" @click="changeConfig('依次审批')">依次审批</el-button>
|
||||
<el-button size="small" @click="changeConfig('会签')">会签</el-button>
|
||||
@@ -76,11 +100,14 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ApproveMethodType, APPROVE_METHODS } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
|
||||
defineOptions({ name: 'ElementMultiInstance' })
|
||||
|
||||
const props = defineProps({
|
||||
businessObject: Object,
|
||||
type: String
|
||||
type: String,
|
||||
id: String
|
||||
})
|
||||
const prefix = inject('prefix')
|
||||
const loopCharacteristics = ref('')
|
||||
@@ -267,16 +294,118 @@ const changeConfig = (config) => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* -----新版本多实例-----
|
||||
*/
|
||||
const approveMethod = ref()
|
||||
const approveRatio = ref(100)
|
||||
const otherExtensions = ref()
|
||||
const getElementLoopNew = () => {
|
||||
const extensionElements =
|
||||
bpmnElement.value.businessObject?.extensionElements ??
|
||||
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
|
||||
approveMethod.value = extensionElements.values.filter(
|
||||
(ex) => ex.$type === `${prefix}:ApproveMethod`
|
||||
)?.[0]?.value
|
||||
|
||||
otherExtensions.value =
|
||||
extensionElements.values.filter((ex) => ex.$type !== `${prefix}:ApproveMethod`) ?? []
|
||||
|
||||
if (!approveMethod.value) {
|
||||
approveMethod.value = ApproveMethodType.SEQUENTIAL_APPROVE
|
||||
updateLoopCharacteristics()
|
||||
}
|
||||
}
|
||||
const onApproveMethodChange = () => {
|
||||
approveRatio.value = 100
|
||||
updateLoopCharacteristics()
|
||||
}
|
||||
const onApproveRatioChange = () => {
|
||||
updateLoopCharacteristics()
|
||||
}
|
||||
const updateLoopCharacteristics = () => {
|
||||
// 根据ApproveMethod生成multiInstanceLoopCharacteristics节点
|
||||
if (approveMethod.value === ApproveMethodType.RANDOM_SELECT_ONE_APPROVE) {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
loopCharacteristics: null
|
||||
})
|
||||
} else {
|
||||
if (approveMethod.value === ApproveMethodType.APPROVE_BY_RATIO) {
|
||||
multiLoopInstance.value = bpmnInstances().moddle.create(
|
||||
'bpmn:MultiInstanceLoopCharacteristics',
|
||||
{ isSequential: false, collection: '${coll_userList}' }
|
||||
)
|
||||
multiLoopInstance.value.completionCondition = bpmnInstances().moddle.create(
|
||||
'bpmn:FormalExpression',
|
||||
{
|
||||
body: '${ nrOfCompletedInstances/nrOfInstances >= ' + approveRatio.value / 100 + '}'
|
||||
}
|
||||
)
|
||||
}
|
||||
if (approveMethod.value === ApproveMethodType.ANY_APPROVE) {
|
||||
multiLoopInstance.value = bpmnInstances().moddle.create(
|
||||
'bpmn:MultiInstanceLoopCharacteristics',
|
||||
{ isSequential: false, collection: '${coll_userList}' }
|
||||
)
|
||||
multiLoopInstance.value.completionCondition = bpmnInstances().moddle.create(
|
||||
'bpmn:FormalExpression',
|
||||
{
|
||||
body: '${ nrOfCompletedInstances > 0 }'
|
||||
}
|
||||
)
|
||||
}
|
||||
if (approveMethod.value === ApproveMethodType.SEQUENTIAL_APPROVE) {
|
||||
multiLoopInstance.value = bpmnInstances().moddle.create(
|
||||
'bpmn:MultiInstanceLoopCharacteristics',
|
||||
{ isSequential: true, collection: '${coll_userList}' }
|
||||
)
|
||||
multiLoopInstance.value.loopCardinality = bpmnInstances().moddle.create(
|
||||
'bpmn:FormalExpression',
|
||||
{
|
||||
body: '1'
|
||||
}
|
||||
)
|
||||
multiLoopInstance.value.completionCondition = bpmnInstances().moddle.create(
|
||||
'bpmn:FormalExpression',
|
||||
{
|
||||
body: '${ nrOfCompletedInstances >= nrOfInstances }'
|
||||
}
|
||||
)
|
||||
}
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
loopCharacteristics: toRaw(multiLoopInstance.value)
|
||||
})
|
||||
}
|
||||
|
||||
// 添加ApproveMethod到ExtensionElements
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: [
|
||||
...otherExtensions.value,
|
||||
bpmnInstances().moddle.create(`${prefix}:ApproveMethod`, {
|
||||
value: approveMethod.value
|
||||
})
|
||||
]
|
||||
})
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
extensionElements: extensions
|
||||
})
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
multiLoopInstance.value = null
|
||||
bpmnElement.value = null
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.businessObject,
|
||||
() => props.id,
|
||||
(val) => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement
|
||||
getElementLoop(val)
|
||||
if (val) {
|
||||
nextTick(() => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement
|
||||
// getElementLoop(val)
|
||||
getElementLoopNew()
|
||||
})
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
@@ -75,17 +75,16 @@ const attributeFormRef = ref()
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances
|
||||
|
||||
const resetAttributesList = () => {
|
||||
console.log(window, 'windowwindowwindowwindowwindowwindowwindow')
|
||||
bpmnElement.value = bpmnInstances().bpmnElement
|
||||
otherExtensionList.value = [] // 其他扩展配置
|
||||
bpmnElementProperties.value =
|
||||
// bpmnElement.value.businessObject?.extensionElements?.filter((ex) => {
|
||||
bpmnElement.value.businessObject?.extensionElements?.values.filter((ex) => {
|
||||
bpmnElement.value.businessObject?.extensionElements?.values?.filter((ex) => {
|
||||
if (ex.$type !== `${prefix}:Properties`) {
|
||||
otherExtensionList.value.push(ex)
|
||||
}
|
||||
return ex.$type === `${prefix}:Properties`
|
||||
}) ?? []
|
||||
}) ?? [];
|
||||
|
||||
// 保存所有的 扩展属性字段
|
||||
bpmnElementPropertyList.value = bpmnElementProperties.value.reduce(
|
||||
|
||||
@@ -29,9 +29,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import UserTask from './task-components/UserTask.vue'
|
||||
import ScriptTask from './task-components/ScriptTask.vue'
|
||||
import ReceiveTask from './task-components/ReceiveTask.vue'
|
||||
import { installedComponent } from './data'
|
||||
|
||||
defineOptions({ name: 'ElementTaskConfig' })
|
||||
|
||||
@@ -45,14 +43,7 @@ const taskConfigForm = ref({
|
||||
exclusive: false
|
||||
})
|
||||
const witchTaskComponent = ref()
|
||||
const installedComponent = ref({
|
||||
// 手工任务与普通任务一致,不需要其他配置
|
||||
// 接收消息任务,需要在全局下插入新的消息实例,并在该节点下的 messageRef 属性绑定该实例
|
||||
// 发送任务、服务任务、业务规则任务共用一个相同配置
|
||||
UserTask: 'UserTask', // 用户任务配置
|
||||
ScriptTask: 'ScriptTask', // 脚本任务配置
|
||||
ReceiveTask: 'ReceiveTask' // 消息接收任务
|
||||
})
|
||||
|
||||
const bpmnElement = ref()
|
||||
|
||||
const bpmnInstances = () => (window as any).bpmnInstances
|
||||
@@ -78,15 +69,8 @@ watch(
|
||||
watch(
|
||||
() => props.type,
|
||||
() => {
|
||||
// witchTaskComponent.value = installedComponent.value[props.type]
|
||||
if (props.type == installedComponent.value.UserTask) {
|
||||
witchTaskComponent.value = UserTask
|
||||
}
|
||||
if (props.type == installedComponent.value.ScriptTask) {
|
||||
witchTaskComponent.value = ScriptTask
|
||||
}
|
||||
if (props.type == installedComponent.value.ReceiveTask) {
|
||||
witchTaskComponent.value = ReceiveTask
|
||||
if (props.type) {
|
||||
witchTaskComponent.value = installedComponent[props.type].component
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import UserTask from './task-components/UserTask.vue'
|
||||
import ServiceTask from './task-components/ServiceTask.vue'
|
||||
import ScriptTask from './task-components/ScriptTask.vue'
|
||||
import ReceiveTask from './task-components/ReceiveTask.vue'
|
||||
import CallActivity from './task-components/CallActivity.vue'
|
||||
|
||||
export const installedComponent = {
|
||||
UserTask: {
|
||||
name: '用户任务',
|
||||
component: UserTask
|
||||
},
|
||||
ServiceTask: {
|
||||
name: '服务任务',
|
||||
component: ServiceTask
|
||||
},
|
||||
ScriptTask: {
|
||||
name: '脚本任务',
|
||||
component: ScriptTask
|
||||
},
|
||||
ReceiveTask: {
|
||||
name: '接收任务',
|
||||
component: ReceiveTask
|
||||
},
|
||||
CallActivity: {
|
||||
name: '调用活动',
|
||||
component: CallActivity
|
||||
}
|
||||
}
|
||||
|
||||
export const getTaskCollapseItemName = (elementType) => {
|
||||
return installedComponent[elementType].name
|
||||
}
|
||||
|
||||
export const isTaskCollapseItemShow = (elementType) => {
|
||||
return installedComponent[elementType]
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="实例名称" prop="processInstanceName">
|
||||
<el-input
|
||||
v-model="formData.processInstanceName"
|
||||
clearable
|
||||
placeholder="请输入实例名称"
|
||||
@change="updateCallActivityAttr('processInstanceName')"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- TODO 需要可选择已存在的流程 -->
|
||||
<el-form-item label="被调用流程" prop="calledElement">
|
||||
<el-input
|
||||
v-model="formData.calledElement"
|
||||
clearable
|
||||
placeholder="请输入被调用流程"
|
||||
@change="updateCallActivityAttr('calledElement')"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="继承变量" prop="inheritVariables">
|
||||
<el-switch
|
||||
v-model="formData.inheritVariables"
|
||||
@change="updateCallActivityAttr('inheritVariables')"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="继承业务键" prop="inheritBusinessKey">
|
||||
<el-switch
|
||||
v-model="formData.inheritBusinessKey"
|
||||
@change="updateCallActivityAttr('inheritBusinessKey')"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="!formData.inheritBusinessKey" label="业务键表达式" prop="businessKey">
|
||||
<el-input
|
||||
v-model="formData.businessKey"
|
||||
clearable
|
||||
placeholder="请输入业务键表达式"
|
||||
@change="updateCallActivityAttr('businessKey')"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider />
|
||||
<div>
|
||||
<div class="flex mb-10px">
|
||||
<el-text>输入参数</el-text>
|
||||
<XButton
|
||||
class="ml-auto"
|
||||
type="primary"
|
||||
preIcon="ep:plus"
|
||||
title="添加参数"
|
||||
size="small"
|
||||
@click="openVariableForm('in', null, -1)"
|
||||
/>
|
||||
</div>
|
||||
<el-table :data="inVariableList" max-height="240" fit border>
|
||||
<el-table-column label="源" prop="source" min-width="100px" show-overflow-tooltip />
|
||||
<el-table-column label="目标" prop="target" min-width="100px" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="110px">
|
||||
<template #default="scope">
|
||||
<el-button link @click="openVariableForm('in', scope.row, scope.$index)" size="small">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button
|
||||
link
|
||||
size="small"
|
||||
style="color: #ff4d4f"
|
||||
@click="removeVariable('in', scope.$index)"
|
||||
>
|
||||
移除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<el-divider />
|
||||
<div>
|
||||
<div class="flex mb-10px">
|
||||
<el-text>输出参数</el-text>
|
||||
<XButton
|
||||
class="ml-auto"
|
||||
type="primary"
|
||||
preIcon="ep:plus"
|
||||
title="添加参数"
|
||||
size="small"
|
||||
@click="openVariableForm('out', null, -1)"
|
||||
/>
|
||||
</div>
|
||||
<el-table :data="outVariableList" max-height="240" fit border>
|
||||
<el-table-column label="源" prop="source" min-width="100px" show-overflow-tooltip />
|
||||
<el-table-column label="目标" prop="target" min-width="100px" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="110px">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
@click="openVariableForm('out', scope.row, scope.$index)"
|
||||
size="small"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button
|
||||
link
|
||||
size="small"
|
||||
style="color: #ff4d4f"
|
||||
@click="removeVariable('out', scope.$index)"
|
||||
>
|
||||
移除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<!-- 添加或修改参数 -->
|
||||
<el-dialog
|
||||
v-model="variableDialogVisible"
|
||||
title="参数配置"
|
||||
width="600px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
>
|
||||
<el-form :model="varialbeFormData" label-width="80px" ref="varialbeFormRef">
|
||||
<el-form-item label="源:" prop="source">
|
||||
<el-input v-model="varialbeFormData.source" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="目标:" prop="target">
|
||||
<el-input v-model="varialbeFormData.target" clearable />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="variableDialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="saveVariable">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'CallActivity' })
|
||||
const props = defineProps({
|
||||
id: String,
|
||||
type: String
|
||||
})
|
||||
const prefix = inject('prefix')
|
||||
const message = useMessage()
|
||||
|
||||
const formData = ref({
|
||||
processInstanceName: '',
|
||||
calledElement: '',
|
||||
inheritVariables: false,
|
||||
businessKey: '',
|
||||
inheritBusinessKey: false,
|
||||
calledElementType: 'key'
|
||||
})
|
||||
const inVariableList = ref()
|
||||
const outVariableList = ref()
|
||||
const variableType = ref() // 参数类型
|
||||
const editingVariableIndex = ref(-1) // 编辑参数下标
|
||||
const variableDialogVisible = ref(false)
|
||||
const varialbeFormRef = ref()
|
||||
const varialbeFormData = ref({
|
||||
source: '',
|
||||
target: ''
|
||||
})
|
||||
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances
|
||||
const bpmnElement = ref()
|
||||
const otherExtensionList = ref()
|
||||
|
||||
const initCallActivity = () => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement
|
||||
console.log(bpmnElement.value.businessObject, 'callActivity')
|
||||
|
||||
// 初始化所有配置项
|
||||
Object.keys(formData.value).forEach((key) => {
|
||||
formData.value[key] = bpmnElement.value.businessObject[key] ?? formData.value[key]
|
||||
})
|
||||
|
||||
otherExtensionList.value = [] // 其他扩展配置
|
||||
inVariableList.value = []
|
||||
outVariableList.value = []
|
||||
// 初始化输入参数
|
||||
bpmnElement.value.businessObject?.extensionElements?.values?.forEach((ex) => {
|
||||
if (ex.$type === `${prefix}:In`) {
|
||||
inVariableList.value.push(ex)
|
||||
} else if (ex.$type === `${prefix}:Out`) {
|
||||
outVariableList.value.push(ex)
|
||||
} else {
|
||||
otherExtensionList.value.push(ex)
|
||||
}
|
||||
})
|
||||
|
||||
// 默认添加
|
||||
// bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
// calledElementType: 'key'
|
||||
// })
|
||||
}
|
||||
|
||||
const updateCallActivityAttr = (attr) => {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
[attr]: formData.value[attr]
|
||||
})
|
||||
}
|
||||
|
||||
const openVariableForm = (type, data, index) => {
|
||||
editingVariableIndex.value = index
|
||||
variableType.value = type
|
||||
varialbeFormData.value = index === -1 ? {} : { ...data }
|
||||
variableDialogVisible.value = true
|
||||
}
|
||||
|
||||
const removeVariable = async (type, index) => {
|
||||
try {
|
||||
await message.delConfirm()
|
||||
if (type === 'in') {
|
||||
inVariableList.value.splice(index, 1)
|
||||
}
|
||||
if (type === 'out') {
|
||||
outVariableList.value.splice(index, 1)
|
||||
}
|
||||
updateElementExtensions()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const saveVariable = () => {
|
||||
if (editingVariableIndex.value === -1) {
|
||||
if (variableType.value === 'in') {
|
||||
inVariableList.value.push(
|
||||
bpmnInstances().moddle.create(`${prefix}:In`, { ...varialbeFormData.value })
|
||||
)
|
||||
}
|
||||
if (variableType.value === 'out') {
|
||||
outVariableList.value.push(
|
||||
bpmnInstances().moddle.create(`${prefix}:Out`, { ...varialbeFormData.value })
|
||||
)
|
||||
}
|
||||
updateElementExtensions()
|
||||
} else {
|
||||
if (variableType.value === 'in') {
|
||||
inVariableList.value[editingVariableIndex.value].source = varialbeFormData.value.source
|
||||
inVariableList.value[editingVariableIndex.value].target = varialbeFormData.value.target
|
||||
}
|
||||
if (variableType.value === 'out') {
|
||||
outVariableList.value[editingVariableIndex.value].source = varialbeFormData.value.source
|
||||
outVariableList.value[editingVariableIndex.value].target = varialbeFormData.value.target
|
||||
}
|
||||
}
|
||||
variableDialogVisible.value = false
|
||||
}
|
||||
|
||||
const updateElementExtensions = () => {
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: [...inVariableList.value, ...outVariableList.value, ...otherExtensionList.value]
|
||||
})
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
extensionElements: extensions
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val) => {
|
||||
val &&
|
||||
val.length &&
|
||||
nextTick(() => {
|
||||
initCallActivity()
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form-item label="执行类型" key="executeType">
|
||||
<el-select v-model="serviceTaskForm.executeType">
|
||||
<el-option label="Java类" value="class" />
|
||||
<el-option label="表达式" value="expression" />
|
||||
<el-option label="代理表达式" value="delegateExpression" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="serviceTaskForm.executeType === 'class'"
|
||||
label="Java类"
|
||||
prop="class"
|
||||
key="execute-class"
|
||||
>
|
||||
<el-input v-model="serviceTaskForm.class" clearable @change="updateElementTask" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="serviceTaskForm.executeType === 'expression'"
|
||||
label="表达式"
|
||||
prop="expression"
|
||||
key="execute-expression"
|
||||
>
|
||||
<el-input v-model="serviceTaskForm.expression" clearable @change="updateElementTask" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="serviceTaskForm.executeType === 'delegateExpression'"
|
||||
label="代理表达式"
|
||||
prop="delegateExpression"
|
||||
key="execute-delegate"
|
||||
>
|
||||
<el-input v-model="serviceTaskForm.delegateExpression" clearable @change="updateElementTask" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'ServiceTask' })
|
||||
const props = defineProps({
|
||||
id: String,
|
||||
type: String
|
||||
})
|
||||
|
||||
const defaultTaskForm = ref({
|
||||
executeType: '',
|
||||
class: '',
|
||||
expression: '',
|
||||
delegateExpression: ''
|
||||
})
|
||||
|
||||
const serviceTaskForm = ref<any>({})
|
||||
const bpmnElement = ref()
|
||||
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances
|
||||
|
||||
const resetTaskForm = () => {
|
||||
for (let key in defaultTaskForm.value) {
|
||||
let value = bpmnElement.value?.businessObject[key] || defaultTaskForm.value[key]
|
||||
serviceTaskForm.value[key] = value
|
||||
if (value) {
|
||||
serviceTaskForm.value.executeType = key
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updateElementTask = () => {
|
||||
let taskAttr = Object.create(null);
|
||||
const type = serviceTaskForm.value.executeType;
|
||||
for (let key in serviceTaskForm.value) {
|
||||
if (key !== 'executeType' && key !== type) taskAttr[key] = null;
|
||||
}
|
||||
taskAttr[type] = serviceTaskForm.value[type] || "";
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), taskAttr)
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
bpmnElement.value = null
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
() => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement
|
||||
nextTick(() => {
|
||||
resetTaskForm()
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-form label-width="100px">
|
||||
<el-form label-width="120px">
|
||||
<el-form-item label="规则类型" prop="candidateStrategy">
|
||||
<el-select
|
||||
v-model="userTaskForm.candidateStrategy"
|
||||
@@ -8,15 +8,15 @@
|
||||
@change="changeCandidateStrategy"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_TASK_CANDIDATE_STRATEGY)"
|
||||
:key="dict.value"
|
||||
v-for="(dict, index) in CANDIDATE_STRATEGY"
|
||||
:key="index"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="userTaskForm.candidateStrategy == 10"
|
||||
v-if="userTaskForm.candidateStrategy == CandidateStrategy.ROLE"
|
||||
label="指定角色"
|
||||
prop="candidateParam"
|
||||
>
|
||||
@@ -31,7 +31,11 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="userTaskForm.candidateStrategy == 20 || userTaskForm.candidateStrategy == 21"
|
||||
v-if="
|
||||
userTaskForm.candidateStrategy == CandidateStrategy.DEPT_MEMBER ||
|
||||
userTaskForm.candidateStrategy == CandidateStrategy.DEPT_LEADER ||
|
||||
userTaskForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
|
||||
"
|
||||
label="指定部门"
|
||||
prop="candidateParam"
|
||||
span="24"
|
||||
@@ -49,7 +53,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="userTaskForm.candidateStrategy == 22"
|
||||
v-if="userTaskForm.candidateStrategy == CandidateStrategy.POST"
|
||||
label="指定岗位"
|
||||
prop="candidateParam"
|
||||
span="24"
|
||||
@@ -65,7 +69,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="userTaskForm.candidateStrategy == 30"
|
||||
v-if="userTaskForm.candidateStrategy == CandidateStrategy.USER"
|
||||
label="指定用户"
|
||||
prop="candidateParam"
|
||||
span="24"
|
||||
@@ -86,7 +90,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="userTaskForm.candidateStrategy === 40"
|
||||
v-if="userTaskForm.candidateStrategy === CandidateStrategy.USER_GROUP"
|
||||
label="指定用户组"
|
||||
prop="candidateParam"
|
||||
>
|
||||
@@ -106,7 +110,67 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="userTaskForm.candidateStrategy === 60"
|
||||
v-if="userTaskForm.candidateStrategy === CandidateStrategy.FORM_USER"
|
||||
label="表单内用户字段"
|
||||
prop="formUser"
|
||||
>
|
||||
<el-select
|
||||
v-model="userTaskForm.candidateParam"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@change="handleFormUserChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, idx) in userFieldOnFormOptions"
|
||||
:key="idx"
|
||||
:label="item.title"
|
||||
:value="item.field"
|
||||
:disabled="!item.required"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="userTaskForm.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER"
|
||||
label="表单内部门字段"
|
||||
prop="formDept"
|
||||
>
|
||||
<el-select
|
||||
v-model="userTaskForm.candidateParam"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@change="updateElementTask"
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, idx) in deptFieldOnFormOptions"
|
||||
:key="idx"
|
||||
:label="item.title"
|
||||
:value="item.field"
|
||||
:disabled="!item.required"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="
|
||||
userTaskForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
|
||||
userTaskForm.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
|
||||
userTaskForm.candidateStrategy == CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER ||
|
||||
userTaskForm.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
|
||||
"
|
||||
:label="deptLevelLabel!"
|
||||
prop="deptLevel"
|
||||
span="24"
|
||||
>
|
||||
<el-select v-model="deptLevel" clearable @change="updateElementTask">
|
||||
<el-option
|
||||
v-for="(item, index) in MULTI_LEVEL_DEPT"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="userTaskForm.candidateStrategy === CandidateStrategy.EXPRESSION"
|
||||
label="流程表达式"
|
||||
prop="candidateParam"
|
||||
>
|
||||
@@ -114,12 +178,17 @@
|
||||
type="textarea"
|
||||
v-model="userTaskForm.candidateParam[0]"
|
||||
clearable
|
||||
style="width: 72%"
|
||||
style="width: 100%"
|
||||
@change="updateElementTask"
|
||||
/>
|
||||
<el-button class="ml-5px" size="small" type="success" @click="openProcessExpressionDialog"
|
||||
>选择表达式</el-button
|
||||
>
|
||||
<XButton
|
||||
class="!w-1/1 mt-5px"
|
||||
type="success"
|
||||
preIcon="ep:select"
|
||||
title="选择表达式"
|
||||
size="small"
|
||||
@click="openProcessExpressionDialog"
|
||||
/>
|
||||
<!-- 选择弹窗 -->
|
||||
<ProcessExpressionDialog ref="processExpressionDialogRef" @select="selectProcessExpression" />
|
||||
</el-form-item>
|
||||
@@ -127,7 +196,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import {
|
||||
CANDIDATE_STRATEGY,
|
||||
CandidateStrategy,
|
||||
FieldPermissionType,
|
||||
MULTI_LEVEL_DEPT
|
||||
} from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import * as RoleApi from '@/api/system/role'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
@@ -136,12 +210,14 @@ import * as UserApi from '@/api/system/user'
|
||||
import * as UserGroupApi from '@/api/bpm/userGroup'
|
||||
import ProcessExpressionDialog from './ProcessExpressionDialog.vue'
|
||||
import { ProcessExpressionVO } from '@/api/bpm/processExpression'
|
||||
import { useFormFieldsPermission } from '@/components/SimpleProcessDesignerV2/src/node'
|
||||
|
||||
defineOptions({ name: 'UserTask' })
|
||||
const props = defineProps({
|
||||
id: String,
|
||||
type: String
|
||||
})
|
||||
const prefix = inject('prefix')
|
||||
const userTaskForm = ref({
|
||||
candidateStrategy: undefined, // 分配规则
|
||||
candidateParam: [] // 分配选项
|
||||
@@ -155,11 +231,88 @@ const postOptions = ref<PostApi.PostVO[]>([]) // 岗位列表
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
|
||||
|
||||
const { formFieldOptions } = useFormFieldsPermission(FieldPermissionType.READ)
|
||||
// 表单内用户字段选项, 必须是必填和用户选择器
|
||||
const userFieldOnFormOptions = computed(() => {
|
||||
return formFieldOptions.filter((item) => item.type === 'UserSelect')
|
||||
})
|
||||
// 表单内部门字段选项, 必须是必填和部门选择器
|
||||
const deptFieldOnFormOptions = computed(() => {
|
||||
return formFieldOptions.filter((item) => item.type === 'DeptSelect')
|
||||
})
|
||||
|
||||
const deptLevel = ref(1)
|
||||
const deptLevelLabel = computed(() => {
|
||||
let label = '部门负责人来源'
|
||||
if (userTaskForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) {
|
||||
label = label + '(指定部门向上)'
|
||||
} else if (userTaskForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER) {
|
||||
label = label + '(表单内部门向上)'
|
||||
} else {
|
||||
label = label + '(发起人部门向上)'
|
||||
}
|
||||
return label
|
||||
})
|
||||
|
||||
const otherExtensions = ref()
|
||||
|
||||
const resetTaskForm = () => {
|
||||
const businessObject = bpmnElement.value.businessObject
|
||||
if (!businessObject) {
|
||||
return
|
||||
}
|
||||
|
||||
const extensionElements =
|
||||
businessObject?.extensionElements ??
|
||||
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
|
||||
userTaskForm.value.candidateStrategy = extensionElements.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:CandidateStrategy`
|
||||
)?.[0]?.value
|
||||
const candidateParamStr = extensionElements.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:CandidateParam`
|
||||
)?.[0]?.value
|
||||
if (candidateParamStr && candidateParamStr.length > 0) {
|
||||
if (userTaskForm.value.candidateStrategy === CandidateStrategy.EXPRESSION) {
|
||||
// 特殊:流程表达式,只有一个 input 输入框
|
||||
userTaskForm.value.candidateParam = [candidateParamStr]
|
||||
} else if (userTaskForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) {
|
||||
// 特殊:多级不部门负责人,需要通过'|'分割
|
||||
userTaskForm.value.candidateParam = candidateParamStr
|
||||
.split('|')[0]
|
||||
.split(',')
|
||||
.map((item) => {
|
||||
// 如果数字超出了最大安全整数范围,则将其作为字符串处理
|
||||
let num = Number(item)
|
||||
return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER ? item : num
|
||||
})
|
||||
deptLevel.value = +candidateParamStr.split('|')[1]
|
||||
} else if (
|
||||
userTaskForm.value.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
|
||||
userTaskForm.value.candidateStrategy == CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
|
||||
) {
|
||||
userTaskForm.value.candidateParam = +candidateParamStr
|
||||
deptLevel.value = +candidateParamStr
|
||||
} else if (userTaskForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER) {
|
||||
userTaskForm.value.candidateParam = candidateParamStr.split('|')[0]
|
||||
deptLevel.value = +candidateParamStr.split('|')[1]
|
||||
} else {
|
||||
userTaskForm.value.candidateParam = candidateParamStr.split(',').map((item) => {
|
||||
// 如果数字超出了最大安全整数范围,则将其作为字符串处理
|
||||
let num = Number(item)
|
||||
return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER ? item : num
|
||||
})
|
||||
}
|
||||
} else {
|
||||
userTaskForm.value.candidateParam = []
|
||||
}
|
||||
|
||||
otherExtensions.value =
|
||||
extensionElements.values?.filter(
|
||||
(ex) => ex.$type !== `${prefix}:CandidateStrategy` && ex.$type !== `${prefix}:CandidateParam`
|
||||
) ?? []
|
||||
|
||||
// 改用通过extensionElements来存储数据
|
||||
return
|
||||
if (businessObject.candidateStrategy != undefined) {
|
||||
userTaskForm.value.candidateStrategy = parseInt(businessObject.candidateStrategy) as any
|
||||
} else {
|
||||
@@ -172,7 +325,7 @@ const resetTaskForm = () => {
|
||||
} else {
|
||||
userTaskForm.value.candidateParam = businessObject.candidateParam
|
||||
.split(',')
|
||||
.map((item) => +item)
|
||||
.map((item) => item)
|
||||
}
|
||||
} else {
|
||||
userTaskForm.value.candidateParam = []
|
||||
@@ -182,11 +335,55 @@ const resetTaskForm = () => {
|
||||
/** 更新 candidateStrategy 字段时,需要清空 candidateParam,并触发 bpmn 图更新 */
|
||||
const changeCandidateStrategy = () => {
|
||||
userTaskForm.value.candidateParam = []
|
||||
deptLevel.value = 1
|
||||
if (userTaskForm.value.candidateStrategy === CandidateStrategy.FORM_USER) {
|
||||
// 特殊处理表单内用户字段,当只有发起人选项时应选中发起人
|
||||
if (!userFieldOnFormOptions.value || userFieldOnFormOptions.value.length <= 1) {
|
||||
userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER
|
||||
}
|
||||
}
|
||||
updateElementTask()
|
||||
}
|
||||
|
||||
/** 选中某个 options 时候,更新 bpmn 图 */
|
||||
const updateElementTask = () => {
|
||||
let candidateParam =
|
||||
userTaskForm.value.candidateParam instanceof Array
|
||||
? userTaskForm.value.candidateParam.join(',')
|
||||
: userTaskForm.value.candidateParam
|
||||
|
||||
// 特殊处理多级部门情况
|
||||
if (
|
||||
userTaskForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
|
||||
userTaskForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
|
||||
) {
|
||||
candidateParam += '|' + deptLevel.value
|
||||
}
|
||||
// 特殊处理发起人部门负责人、发起人连续部门负责人
|
||||
if (
|
||||
userTaskForm.value.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
|
||||
userTaskForm.value.candidateStrategy == CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
|
||||
) {
|
||||
candidateParam = deptLevel.value + ''
|
||||
}
|
||||
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: [
|
||||
...otherExtensions.value,
|
||||
bpmnInstances().moddle.create(`${prefix}:CandidateStrategy`, {
|
||||
value: userTaskForm.value.candidateStrategy
|
||||
}),
|
||||
bpmnInstances().moddle.create(`${prefix}:CandidateParam`, {
|
||||
value: candidateParam
|
||||
})
|
||||
]
|
||||
})
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
extensionElements: extensions
|
||||
})
|
||||
|
||||
// 改用通过extensionElements来存储数据
|
||||
return
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
candidateStrategy: userTaskForm.value.candidateStrategy,
|
||||
candidateParam: userTaskForm.value.candidateParam.join(',')
|
||||
@@ -203,6 +400,14 @@ const selectProcessExpression = (expression: ProcessExpressionVO) => {
|
||||
updateElementTask()
|
||||
}
|
||||
|
||||
const handleFormUserChange = (e) => {
|
||||
if (e === 'PROCESS_START_USER_ID') {
|
||||
userTaskForm.value.candidateParam = []
|
||||
userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER
|
||||
}
|
||||
updateElementTask()
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
() => {
|
||||
|
||||
@@ -5,7 +5,7 @@ $--color-danger: #ff4d4f;
|
||||
/* 改变 icon 字体路径变量,必需 */
|
||||
$--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||
|
||||
@import '~element-ui/packages/theme-chalk/src/index';
|
||||
@use '~element-ui/packages/theme-chalk/src/index';
|
||||
|
||||
.el-table td,
|
||||
.el-table th {
|
||||
|
||||
@@ -1,2 +1,117 @@
|
||||
@import './process-designer.scss';
|
||||
@import './process-panel.scss';
|
||||
@use './process-designer.scss';
|
||||
@use './process-panel.scss';
|
||||
|
||||
$success-color: #4eb819;
|
||||
$primary-color: #409EFF;
|
||||
$danger-color: #F56C6C;
|
||||
$cancel-color: #909399;
|
||||
|
||||
.process-viewer {
|
||||
position: relative;
|
||||
border: 1px solid #EFEFEF;
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+') repeat!important;
|
||||
|
||||
.success-arrow {
|
||||
fill: $success-color;
|
||||
stroke: $success-color;
|
||||
}
|
||||
|
||||
.success-conditional {
|
||||
fill: white;
|
||||
stroke: $success-color;
|
||||
}
|
||||
|
||||
.success.djs-connection {
|
||||
.djs-visual path {
|
||||
stroke: $success-color!important;
|
||||
//marker-end: url(#sequenceflow-end-white-success)!important;
|
||||
}
|
||||
}
|
||||
|
||||
.success.djs-connection.condition-expression {
|
||||
.djs-visual path {
|
||||
//marker-start: url(#conditional-flow-marker-white-success)!important;
|
||||
}
|
||||
}
|
||||
|
||||
.success.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $success-color!important;
|
||||
fill: $success-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $success-color!important;
|
||||
}
|
||||
|
||||
.djs-visual path:nth-child(2) {
|
||||
stroke: $success-color!important;
|
||||
fill: $success-color!important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $success-color!important;
|
||||
fill: $success-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
}
|
||||
|
||||
.primary.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $primary-color!important;
|
||||
fill: $primary-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $primary-color!important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $primary-color!important;
|
||||
fill: $primary-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
}
|
||||
|
||||
.danger.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $danger-color!important;
|
||||
fill: $danger-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $danger-color!important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $danger-color!important;
|
||||
fill: $danger-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $cancel-color!important;
|
||||
fill: $cancel-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $cancel-color!important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $cancel-color!important;
|
||||
fill: $cancel-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.process-viewer .djs-tooltip-container, .process-viewer .djs-overlay-container, .process-viewer .djs-palette {
|
||||
display: none;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
@import 'bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css';
|
||||
@import 'bpmn-js-token-simulation/assets/css/font-awesome.min.css';
|
||||
@import 'bpmn-js-token-simulation/assets/css/normalize.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 样式覆盖了
|
||||
.djs-palette {
|
||||
|
||||
@@ -5,16 +5,12 @@ import { config } from './config'
|
||||
const { default_headers } = config
|
||||
|
||||
const request = (option: any) => {
|
||||
const { url, method, params, data, headersType, responseType, ...config } = option
|
||||
const { headersType, headers, ...otherOption } = option
|
||||
return service({
|
||||
url: url,
|
||||
method,
|
||||
params,
|
||||
data,
|
||||
...config,
|
||||
responseType: responseType,
|
||||
...otherOption,
|
||||
headers: {
|
||||
'Content-Type': headersType || default_headers
|
||||
'Content-Type': headersType || default_headers,
|
||||
...headers
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import axios, {
|
||||
AxiosError,
|
||||
AxiosInstance,
|
||||
AxiosRequestHeaders,
|
||||
AxiosResponse,
|
||||
InternalAxiosRequestConfig
|
||||
} from 'axios'
|
||||
import axios, { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
|
||||
|
||||
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
|
||||
import qs from 'qs'
|
||||
@@ -37,7 +31,11 @@ const whiteList: string[] = ['/login', '/refresh-token']
|
||||
const service: AxiosInstance = axios.create({
|
||||
baseURL: base_url, // api 的 base_url
|
||||
timeout: request_timeout, // 请求超时时间
|
||||
withCredentials: false // 禁用 Cookie 等信息
|
||||
withCredentials: false, // 禁用 Cookie 等信息
|
||||
// 自定义参数序列化函数
|
||||
paramsSerializer: (params) => {
|
||||
return qs.stringify(params, { allowDots: true })
|
||||
}
|
||||
})
|
||||
|
||||
// request拦截器
|
||||
@@ -46,34 +44,31 @@ service.interceptors.request.use(
|
||||
// 是否需要设置 token
|
||||
let isToken = (config!.headers || {}).isToken === false
|
||||
whiteList.some((v) => {
|
||||
if (config.url) {
|
||||
config.url.indexOf(v) > -1
|
||||
if (config.url && config.url.indexOf(v) > -1) {
|
||||
return (isToken = false)
|
||||
}
|
||||
})
|
||||
if (getAccessToken() && !isToken) {
|
||||
;(config as Recordable).headers.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token
|
||||
config.headers.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token
|
||||
}
|
||||
// 设置租户
|
||||
if (tenantEnable && tenantEnable === 'true') {
|
||||
const tenantId = getTenantId()
|
||||
if (tenantId) (config as Recordable).headers['tenant-id'] = tenantId
|
||||
if (tenantId) config.headers['tenant-id'] = tenantId
|
||||
}
|
||||
const params = config.params || {}
|
||||
const data = config.data || false
|
||||
if (
|
||||
config.method?.toUpperCase() === 'POST' &&
|
||||
(config.headers as AxiosRequestHeaders)['Content-Type'] ===
|
||||
'application/x-www-form-urlencoded'
|
||||
) {
|
||||
config.data = qs.stringify(data)
|
||||
const method = config.method?.toUpperCase()
|
||||
// 防止 GET 请求缓存
|
||||
if (method === 'GET') {
|
||||
config.headers['Cache-Control'] = 'no-cache'
|
||||
config.headers['Pragma'] = 'no-cache'
|
||||
}
|
||||
// get参数编码
|
||||
if (config.method?.toUpperCase() === 'GET' && params) {
|
||||
config.params = {}
|
||||
const paramsStr = qs.stringify(params, { allowDots: true })
|
||||
if (paramsStr) {
|
||||
config.url = config.url + '?' + paramsStr
|
||||
// 自定义参数序列化函数
|
||||
else if (method === 'POST') {
|
||||
const contentType = config.headers['Content-Type'] || config.headers['content-type']
|
||||
if (contentType === 'application/x-www-form-urlencoded') {
|
||||
if (config.data && typeof config.data !== 'string') {
|
||||
config.data = qs.stringify(config.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
return config
|
||||
|
||||
@@ -8,7 +8,8 @@ export function hasPermi(app: App<Element>) {
|
||||
const { wsCache } = useCache()
|
||||
const { value } = binding
|
||||
const all_permission = '*:*:*'
|
||||
const permissions = wsCache.get(CACHE_KEY.USER).permissions
|
||||
const userInfo = wsCache.get(CACHE_KEY.USER)
|
||||
const permissions = userInfo?.permissions || []
|
||||
|
||||
if (value && value instanceof Array && value.length > 0) {
|
||||
const permissionFlag = value
|
||||
|
||||
@@ -7,8 +7,9 @@ export function hasRole(app: App<Element>) {
|
||||
app.directive('hasRole', (el, binding) => {
|
||||
const { wsCache } = useCache()
|
||||
const { value } = binding
|
||||
const super_admin = 'admin'
|
||||
const roles = wsCache.get(CACHE_KEY.USER).roles
|
||||
const super_admin = 'super_admin'
|
||||
const userInfo = wsCache.get(CACHE_KEY.USER)
|
||||
const roles = userInfo?.roles || []
|
||||
|
||||
if (value && value instanceof Array && value.length > 0) {
|
||||
const roleFlag = value
|
||||
|
||||
@@ -92,7 +92,7 @@ export default defineComponent({
|
||||
$prefix-cls: #{$elNamespace}-breadcrumb;
|
||||
|
||||
.#{$prefix-cls} {
|
||||
:deep(&__item) {
|
||||
:deep(.#{$prefix-cls}__item) {
|
||||
display: flex;
|
||||
.#{$prefix-cls}__inner {
|
||||
display: flex;
|
||||
@@ -105,7 +105,7 @@ $prefix-cls: #{$elNamespace}-breadcrumb;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(&__item):not(:last-child) {
|
||||
:deep(.#{$prefix-cls}__item):not(:last-child) {
|
||||
.#{$prefix-cls}__inner {
|
||||
color: var(--top-header-text-color);
|
||||
|
||||
@@ -115,7 +115,7 @@ $prefix-cls: #{$elNamespace}-breadcrumb;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(&__item):last-child {
|
||||
:deep(.#{$prefix-cls}__item):last-child {
|
||||
.#{$prefix-cls}__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as NotifyMessageApi from '@/api/system/notify/message'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
|
||||
defineOptions({ name: 'Message' })
|
||||
|
||||
const { push } = useRouter()
|
||||
const userStore = useUserStoreWithOut()
|
||||
const activeName = ref('notice')
|
||||
const unreadCount = ref(0) // 未读消息数量
|
||||
const list = ref<any[]>([]) // 消息列表
|
||||
@@ -37,7 +39,11 @@ onMounted(() => {
|
||||
// 轮询刷新小红点
|
||||
setInterval(
|
||||
() => {
|
||||
getUnreadCount()
|
||||
if (userStore.getIsSetUser) {
|
||||
getUnreadCount()
|
||||
} else {
|
||||
unreadCount.value = 0
|
||||
}
|
||||
},
|
||||
1000 * 60 * 2
|
||||
)
|
||||
|
||||
@@ -297,5 +297,6 @@ $prefix-cls: #{$namespace}-setting;
|
||||
|
||||
.#{$prefix-cls} {
|
||||
border-radius: 6px 0 0 6px;
|
||||
z-index: 1200;/*修正没有z-index会被表格层覆盖,值不要超过4000*/
|
||||
}
|
||||
</style>
|
||||
@@ -127,12 +127,8 @@ const toLastView = () => {
|
||||
const moveToCurrentTag = async () => {
|
||||
await nextTick()
|
||||
for (const v of unref(visitedViews)) {
|
||||
if (v.fullPath === unref(currentRoute).path) {
|
||||
if (v.fullPath === unref(currentRoute).fullPath) {
|
||||
moveToTarget(v)
|
||||
if (v.fullPath !== unref(currentRoute).fullPath) {
|
||||
tagsViewStore.updateVisitedView(unref(currentRoute))
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -207,7 +203,7 @@ const moveToTarget = (currentTag: RouteLocationNormalizedLoaded) => {
|
||||
|
||||
// 是否是当前tag
|
||||
const isActive = (route: RouteLocationNormalizedLoaded): boolean => {
|
||||
return route.path === unref(currentRoute).path
|
||||
return route.fullPath === unref(currentRoute).fullPath
|
||||
}
|
||||
|
||||
// 所有右键菜单组件的元素
|
||||
@@ -373,7 +369,10 @@ watch(
|
||||
:size="12"
|
||||
class="mr-5px"
|
||||
/>
|
||||
{{ t(item?.meta?.title as string) }}
|
||||
{{
|
||||
t(item?.meta?.title as string) +
|
||||
(item?.meta?.titleSuffix ? ` (${item?.meta?.titleSuffix})` : '')
|
||||
}}
|
||||
<Icon
|
||||
:class="`${prefixCls}__item--close`"
|
||||
:size="12"
|
||||
|
||||
@@ -1,7 +1,37 @@
|
||||
import type { App } from 'vue'
|
||||
// 👇使用 form-create 需额外全局引入 element plus 组件
|
||||
import {
|
||||
// ElAutocomplete,
|
||||
// ElButton,
|
||||
// ElCascader,
|
||||
// ElCheckbox,
|
||||
// ElCheckboxButton,
|
||||
// ElCheckboxGroup,
|
||||
// ElCol,
|
||||
// ElColorPicker,
|
||||
// ElDatePicker,
|
||||
// ElDialog,
|
||||
// ElForm,
|
||||
// ElInput,
|
||||
// ElInputNumber,
|
||||
// ElPopover,
|
||||
// ElRadio,
|
||||
// ElRadioButton,
|
||||
// ElRadioGroup,
|
||||
// ElRate,
|
||||
// ElRow,
|
||||
// ElSelect,
|
||||
// ElSlider,
|
||||
// ElSwitch,
|
||||
// ElTimePicker,
|
||||
// ElTooltip,
|
||||
// ElTree,
|
||||
// ElUpload,
|
||||
// ElIcon,
|
||||
// ElProgress,
|
||||
// 以上会由 @form-create/element-ui/auto-import 自动引入
|
||||
ElAlert,
|
||||
ElTransfer,
|
||||
ElAside,
|
||||
ElContainer,
|
||||
ElDivider,
|
||||
@@ -12,7 +42,21 @@ import {
|
||||
ElTableColumn,
|
||||
ElTabPane,
|
||||
ElTabs,
|
||||
ElTransfer
|
||||
ElDropdown,
|
||||
ElDropdownMenu,
|
||||
ElDropdownItem,
|
||||
ElBadge,
|
||||
ElTag,
|
||||
ElText,
|
||||
ElMenu,
|
||||
ElMenuItem,
|
||||
ElFooter,
|
||||
ElMessage,
|
||||
ElCollapse,
|
||||
ElCollapseItem,
|
||||
ElCard,
|
||||
// ElFormItem,
|
||||
// ElOption
|
||||
} from 'element-plus'
|
||||
import FcDesigner from '@form-create/designer'
|
||||
import formCreate from '@form-create/element-ui'
|
||||
@@ -41,18 +85,30 @@ const ApiSelect = useApiSelect({
|
||||
})
|
||||
|
||||
const components = [
|
||||
ElAlert,
|
||||
ElTransfer,
|
||||
ElAside,
|
||||
ElPopconfirm,
|
||||
ElHeader,
|
||||
ElMain,
|
||||
ElContainer,
|
||||
ElDivider,
|
||||
ElTransfer,
|
||||
ElAlert,
|
||||
ElTabs,
|
||||
ElHeader,
|
||||
ElMain,
|
||||
ElPopconfirm,
|
||||
ElTable,
|
||||
ElTableColumn,
|
||||
ElTabPane,
|
||||
ElTabs,
|
||||
ElDropdown,
|
||||
ElDropdownMenu,
|
||||
ElDropdownItem,
|
||||
ElBadge,
|
||||
ElTag,
|
||||
ElText,
|
||||
ElMenu,
|
||||
ElMenuItem,
|
||||
ElFooter,
|
||||
ElMessage,
|
||||
// ElFormItem,
|
||||
// ElOption,
|
||||
UploadImg,
|
||||
UploadImgs,
|
||||
UploadFile,
|
||||
@@ -60,7 +116,10 @@ const components = [
|
||||
UserSelect,
|
||||
DeptSelect,
|
||||
ApiSelect,
|
||||
Editor
|
||||
Editor,
|
||||
ElCollapse,
|
||||
ElCollapseItem,
|
||||
ElCard,
|
||||
]
|
||||
|
||||
// 参考 http://www.form-create.com/v3/element-ui/auto-import.html 文档
|
||||
|
||||
@@ -267,9 +267,9 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'manager/simple/workflow/model/edit',
|
||||
component: () => import('@/views/bpm/simpleWorkflow/index.vue'),
|
||||
name: 'SimpleWorkflowDesignEditor',
|
||||
path: 'manager/simple/model',
|
||||
component: () => import('@/views/bpm/simple/SimpleModelDesign.vue'),
|
||||
name: 'SimpleModelDesign',
|
||||
meta: {
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
@@ -292,7 +292,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||
},
|
||||
{
|
||||
path: 'process-instance/detail',
|
||||
// component: () => import('@/views/bpm/processInstance/detail/index_new.vue'), // TODO 芋艿:新审批界面,已适配 simple 模式,未来会适配 bpmn 模式
|
||||
component: () => import('@/views/bpm/processInstance/detail/index.vue'),
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
meta: {
|
||||
@@ -331,6 +330,30 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||
title: '查看 OA 请假',
|
||||
activeMenu: '/bpm/oa/leave'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'manager/model/create',
|
||||
component: () => import('@/views/bpm/model/form/index.vue'),
|
||||
name: 'BpmModelCreate',
|
||||
meta: {
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
canTo: true,
|
||||
title: '创建流程',
|
||||
activeMenu: '/bpm/manager/model'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'manager/model/update/:id',
|
||||
component: () => import('@/views/bpm/model/form/index.vue'),
|
||||
name: 'BpmModelUpdate',
|
||||
meta: {
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
canTo: true,
|
||||
title: '修改流程',
|
||||
activeMenu: '/bpm/manager/model'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { store } from '@/store'
|
||||
import { defineStore } from 'pinia'
|
||||
import { KeFuConversationApi, KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
|
||||
import { KeFuMessageRespVO } from '@/api/mall/promotion/kefu/message'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
|
||||
interface MallKefuInfoVO {
|
||||
conversationList: KeFuConversationRespVO[] // 会话列表
|
||||
conversationMessageList: Map<number, KeFuMessageRespVO[]> // 会话消息
|
||||
}
|
||||
|
||||
export const useMallKefuStore = defineStore('mall-kefu', {
|
||||
state: (): MallKefuInfoVO => ({
|
||||
conversationList: [],
|
||||
conversationMessageList: new Map<number, KeFuMessageRespVO[]>() // key 会话,value 会话消息列表
|
||||
}),
|
||||
getters: {
|
||||
getConversationList(): KeFuConversationRespVO[] {
|
||||
return this.conversationList
|
||||
},
|
||||
getConversationMessageList(): (conversationId: number) => KeFuMessageRespVO[] | undefined {
|
||||
return (conversationId: number) => this.conversationMessageList.get(conversationId)
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
// ======================= 会话消息相关 =======================
|
||||
/** 缓存历史消息 */
|
||||
saveMessageList(conversationId: number, messageList: KeFuMessageRespVO[]) {
|
||||
this.conversationMessageList.set(conversationId, messageList)
|
||||
},
|
||||
|
||||
// ======================= 会话相关 =======================
|
||||
/** 加载会话缓存列表 */
|
||||
async setConversationList() {
|
||||
this.conversationList = await KeFuConversationApi.getConversationList()
|
||||
this.conversationSort()
|
||||
},
|
||||
/** 更新会话缓存已读 */
|
||||
async updateConversationStatus(conversationId: number) {
|
||||
if (isEmpty(this.conversationList)) {
|
||||
return
|
||||
}
|
||||
const conversation = this.conversationList.find((item) => item.id === conversationId)
|
||||
conversation && (conversation.adminUnreadMessageCount = 0)
|
||||
},
|
||||
/** 更新会话缓存 */
|
||||
async updateConversation(conversationId: number) {
|
||||
if (isEmpty(this.conversationList)) {
|
||||
return
|
||||
}
|
||||
|
||||
const conversation = await KeFuConversationApi.getConversation(conversationId)
|
||||
this.deleteConversation(conversationId)
|
||||
conversation && this.conversationList.push(conversation)
|
||||
this.conversationSort()
|
||||
},
|
||||
/** 删除会话缓存 */
|
||||
deleteConversation(conversationId: number) {
|
||||
const index = this.conversationList.findIndex((item) => item.id === conversationId)
|
||||
// 存在则删除
|
||||
if (index > -1) {
|
||||
this.conversationList.splice(index, 1)
|
||||
}
|
||||
},
|
||||
conversationSort() {
|
||||
// 按置顶属性和最后消息时间排序
|
||||
this.conversationList.sort((a, b) => {
|
||||
// 按照置顶排序,置顶的会在前面
|
||||
if (a.adminPinned !== b.adminPinned) {
|
||||
return a.adminPinned ? -1 : 1
|
||||
}
|
||||
// 按照最后消息时间排序,最近的会在前面
|
||||
return (b.lastMessageTime as unknown as number) - (a.lastMessageTime as unknown as number)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const useMallKefuStoreWithOut = () => {
|
||||
return useMallKefuStore(store)
|
||||
}
|
||||
@@ -35,8 +35,9 @@ export const usePermissionStore = defineStore('permission', {
|
||||
return new Promise<void>(async (resolve) => {
|
||||
// 获得菜单列表,它在登录的时候,setUserInfoAction 方法中已经进行获取
|
||||
let res: AppCustomRouteRecordRaw[] = []
|
||||
if (wsCache.get(CACHE_KEY.ROLE_ROUTERS)) {
|
||||
res = wsCache.get(CACHE_KEY.ROLE_ROUTERS) as AppCustomRouteRecordRaw[]
|
||||
const roleRouters = wsCache.get(CACHE_KEY.ROLE_ROUTERS)
|
||||
if (roleRouters) {
|
||||
res = roleRouters as AppCustomRouteRecordRaw[]
|
||||
}
|
||||
const routerMap: AppRouteRecordRaw[] = generateRoute(res)
|
||||
// 动态路由,404一定要放到最后面
|
||||
|
||||
@@ -31,13 +31,27 @@ export const useTagsViewStore = defineStore('tagsView', {
|
||||
},
|
||||
// 新增tag
|
||||
addVisitedView(view: RouteLocationNormalizedLoaded) {
|
||||
if (this.visitedViews.some((v) => v.path === view.path)) return
|
||||
if (this.visitedViews.some((v) => v.fullPath === view.fullPath)) return
|
||||
if (view.meta?.noTagsView) return
|
||||
this.visitedViews.push(
|
||||
Object.assign({}, view, {
|
||||
title: view.meta?.title || 'no-name'
|
||||
const visitedView = Object.assign({}, view, { title: view.meta?.title || 'no-name' })
|
||||
|
||||
if (visitedView.meta) {
|
||||
const titleSuffixList: string[] = []
|
||||
this.visitedViews.forEach((v) => {
|
||||
if (v.path === visitedView.path && v.meta?.title === visitedView.meta?.title) {
|
||||
titleSuffixList.push(v.meta?.titleSuffix || '1')
|
||||
}
|
||||
})
|
||||
)
|
||||
if (titleSuffixList.length) {
|
||||
let titleSuffix = 1
|
||||
while (titleSuffixList.includes(`${titleSuffix}`)) {
|
||||
titleSuffix += 1
|
||||
}
|
||||
visitedView.meta.titleSuffix = titleSuffix === 1 ? undefined : `${titleSuffix}`
|
||||
}
|
||||
}
|
||||
|
||||
this.visitedViews.push(visitedView)
|
||||
},
|
||||
// 新增缓存
|
||||
addCachedView() {
|
||||
@@ -63,7 +77,7 @@ export const useTagsViewStore = defineStore('tagsView', {
|
||||
// 删除tag
|
||||
delVisitedView(view: RouteLocationNormalizedLoaded) {
|
||||
for (const [i, v] of this.visitedViews.entries()) {
|
||||
if (v.path === view.path) {
|
||||
if (v.fullPath === view.fullPath) {
|
||||
this.visitedViews.splice(i, 1)
|
||||
break
|
||||
}
|
||||
@@ -95,18 +109,18 @@ export const useTagsViewStore = defineStore('tagsView', {
|
||||
// 删除其他tag
|
||||
delOthersVisitedViews(view: RouteLocationNormalizedLoaded) {
|
||||
this.visitedViews = this.visitedViews.filter((v) => {
|
||||
return v?.meta?.affix || v.path === view.path
|
||||
return v?.meta?.affix || v.fullPath === view.fullPath
|
||||
})
|
||||
},
|
||||
// 删除左侧
|
||||
delLeftViews(view: RouteLocationNormalizedLoaded) {
|
||||
const index = findIndex<RouteLocationNormalizedLoaded>(
|
||||
this.visitedViews,
|
||||
(v) => v.path === view.path
|
||||
(v) => v.fullPath === view.fullPath
|
||||
)
|
||||
if (index > -1) {
|
||||
this.visitedViews = this.visitedViews.filter((v, i) => {
|
||||
return v?.meta?.affix || v.path === view.path || i > index
|
||||
return v?.meta?.affix || v.fullPath === view.fullPath || i > index
|
||||
})
|
||||
this.addCachedView()
|
||||
}
|
||||
@@ -115,18 +129,18 @@ export const useTagsViewStore = defineStore('tagsView', {
|
||||
delRightViews(view: RouteLocationNormalizedLoaded) {
|
||||
const index = findIndex<RouteLocationNormalizedLoaded>(
|
||||
this.visitedViews,
|
||||
(v) => v.path === view.path
|
||||
(v) => v.fullPath === view.fullPath
|
||||
)
|
||||
if (index > -1) {
|
||||
this.visitedViews = this.visitedViews.filter((v, i) => {
|
||||
return v?.meta?.affix || v.path === view.path || i < index
|
||||
return v?.meta?.affix || v.fullPath === view.fullPath || i < index
|
||||
})
|
||||
this.addCachedView()
|
||||
}
|
||||
},
|
||||
updateVisitedView(view: RouteLocationNormalizedLoaded) {
|
||||
for (let v of this.visitedViews) {
|
||||
if (v.path === view.path) {
|
||||
if (v.fullPath === view.fullPath) {
|
||||
v = Object.assign(v, view)
|
||||
break
|
||||
}
|
||||
|
||||