Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into feature/iot

This commit is contained in:
YunaiV 2025-01-04 17:05:30 +08:00
commit 7639f34e12
94 changed files with 6132 additions and 1747 deletions

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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,
@ -47,7 +47,7 @@
"driver.js": "^1.3.1",
"echarts": "^5.5.0",
"echarts-wordcloud": "^2.1.0",
"element-plus": "2.8.4",
"element-plus": "2.9.1",
"fast-xml-parser": "^4.3.2",
"highlight.js": "^11.9.0",
"jsencrypt": "^3.3.2",
@ -96,8 +96,8 @@
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"autoprefixer": "^10.4.17",
"bpmn-js": "8.10.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",

439
pnpm-lock.yaml generated
View File

@ -72,8 +72,8 @@ dependencies:
specifier: ^2.1.0
version: 2.1.0(echarts@5.5.1)
element-plus:
specifier: 2.8.4
version: 2.8.4(vue@3.5.12)
specifier: 2.9.1
version: 2.9.1(vue@3.5.12)
fast-xml-parser:
specifier: ^4.3.2
version: 4.5.0
@ -215,11 +215,11 @@ devDependencies:
specifier: ^10.4.17
version: 10.4.20(postcss@8.4.49)
bpmn-js:
specifier: 8.10.0
version: 8.10.0
specifier: ^17.9.2
version: 17.11.1
bpmn-js-properties-panel:
specifier: 0.46.0
version: 0.46.0(bpmn-js@8.10.0)
specifier: 5.23.0
version: 5.23.0(@bpmn-io/properties-panel@3.25.0)(bpmn-js@17.11.1)(camunda-bpmn-js-behaviors@1.7.2)(diagram-js@12.8.1)
consola:
specifier: ^3.2.3
version: 3.2.3
@ -1465,29 +1465,117 @@ packages:
'@babel/helper-string-parser': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
/@bpmn-io/cm-theme@0.1.0-alpha.2:
resolution: {integrity: sha512-ZILgiYzxk3KMvxplUXmdRFQo45/JehDPg5k9tWfehmzUOSE13ssyLPil8uCloMQnb3yyzyOWTjb/wzKXTHlFQw==, tarball: https://registry.npmmirror.com/@bpmn-io/cm-theme/-/cm-theme-0.1.0-alpha.2.tgz}
dependencies:
'@codemirror/language': 6.10.6
'@codemirror/view': 6.35.0
'@lezer/highlight': 1.2.1
dev: true
/@bpmn-io/diagram-js-ui@0.2.3:
resolution: {integrity: sha512-OGyjZKvGK8tHSZ0l7RfeKhilGoOGtFDcoqSGYkX0uhFlo99OVZ9Jn1K7TJGzcE9BdKwvA5Y5kGqHEhdTxHvFfw==, tarball: https://registry.npmmirror.com/@bpmn-io/diagram-js-ui/-/diagram-js-ui-0.2.3.tgz}
dependencies:
htm: 3.1.1
preact: 10.25.0
dev: false
/@bpmn-io/element-templates-validator@0.2.0:
resolution: {integrity: sha512-/ogp0+6zUFdoiY09NYaHL5JtapB8zN1spG8hpML96qetXDCODRxnsqlHTvSwxtZHUDcgun+lxcK8b4wgtCP+6Q==, tarball: https://registry.npmmirror.com/@bpmn-io/element-templates-validator/-/element-templates-validator-0.2.0.tgz}
/@bpmn-io/extract-process-variables@0.8.0:
resolution: {integrity: sha512-yAS7ZYX+D56K+luC36u96eRMLb4VHcPUwTUqMZ/Z/Je2gou2DJLRbuBTHAB4jjKt4wFCHSG4B8Y+TrBciEYf4w==, tarball: https://registry.npmmirror.com/@bpmn-io/extract-process-variables/-/extract-process-variables-0.8.0.tgz}
dependencies:
'@camunda/element-templates-json-schema': 0.4.0
json-source-map: 0.6.1
min-dash: 3.8.1
min-dash: 4.2.2
dev: true
/@bpmn-io/extract-process-variables@0.4.5:
resolution: {integrity: sha512-LtHx5b9xqS8avRLrq/uTlKhWzMeV3bWQKIdDic2bdo5n9roitX13GRb01u2S0hSsKDWEhXQtydFYN2b6G7bqfw==, tarball: https://registry.npmmirror.com/@bpmn-io/extract-process-variables/-/extract-process-variables-0.4.5.tgz}
/@bpmn-io/feel-editor@1.9.1(@lezer/common@1.2.3):
resolution: {integrity: sha512-UxSORdh5cwKM4fib4f9ov6J1/BHGpQVNtA+wPyEdKQyCyz3wqwE2/xe5wneVR1j5QFC5m2Na8nTy4a1TDFvZTw==, tarball: https://registry.npmmirror.com/@bpmn-io/feel-editor/-/feel-editor-1.9.1.tgz}
engines: {node: '>= 16'}
dependencies:
min-dash: 3.8.1
'@bpmn-io/feel-lint': 1.3.1
'@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3)
'@codemirror/commands': 6.7.1
'@codemirror/language': 6.10.6
'@codemirror/lint': 6.8.4
'@codemirror/state': 6.4.1
'@codemirror/view': 6.35.0
'@lezer/highlight': 1.2.1
lang-feel: 2.2.0
min-dom: 4.2.1
transitivePeerDependencies:
- '@lezer/common'
dev: true
/@camunda/element-templates-json-schema@0.4.0:
resolution: {integrity: sha512-M5xW61ba7z2maBxfoT4c1bjuLD8OIL7863et/hULiNG6+R/B9CZ4Qze1juuIfXv4zpF2fYSuUsTPkTtiZrcspQ==, tarball: https://registry.npmmirror.com/@camunda/element-templates-json-schema/-/element-templates-json-schema-0.4.0.tgz}
/@bpmn-io/feel-lint@1.3.1:
resolution: {integrity: sha512-wcFkJKhOm/iqCt5bzkKvxL5Dr9wKwUD+t164bQYbJsTYouAqmkkxiGsoqck42hXwdIhMSguZ+vqQ3hj5QdiYCA==, tarball: https://registry.npmmirror.com/@bpmn-io/feel-lint/-/feel-lint-1.3.1.tgz}
dependencies:
'@codemirror/language': 6.10.6
lezer-feel: 1.4.0
dev: true
/@bpmn-io/properties-panel@3.25.0(@lezer/common@1.2.3):
resolution: {integrity: sha512-SRGgj8uJc1Yyjcht2g36Q+xKR7sTx5VZXvcwDrdmQKlx5Y3nRmvmMjDGzeGDJDb7pNU1DSlaBJic84uISDBMWg==, tarball: https://registry.npmmirror.com/@bpmn-io/properties-panel/-/properties-panel-3.25.0.tgz}
dependencies:
'@bpmn-io/feel-editor': 1.9.1(@lezer/common@1.2.3)
'@codemirror/view': 6.35.0
classnames: 2.5.1
feelers: 1.4.0
focus-trap: 7.6.2
min-dash: 4.2.2
min-dom: 4.2.1
transitivePeerDependencies:
- '@lezer/common'
dev: true
/@codemirror/autocomplete@6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3):
resolution: {integrity: sha512-1dNIOmiM0z4BIBwxmxEfA1yoxh1MF/6KPBbh20a5vphGV0ictKlgQsbJs6D6SkR6iJpGbpwRsa6PFMNlg9T9pQ==, tarball: https://registry.npmmirror.com/@codemirror/autocomplete/-/autocomplete-6.18.3.tgz}
peerDependencies:
'@codemirror/language': ^6.0.0
'@codemirror/state': ^6.0.0
'@codemirror/view': ^6.0.0
'@lezer/common': ^1.0.0
dependencies:
'@codemirror/language': 6.10.6
'@codemirror/state': 6.4.1
'@codemirror/view': 6.35.0
'@lezer/common': 1.2.3
dev: true
/@codemirror/commands@6.7.1:
resolution: {integrity: sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==, tarball: https://registry.npmmirror.com/@codemirror/commands/-/commands-6.7.1.tgz}
dependencies:
'@codemirror/language': 6.10.6
'@codemirror/state': 6.4.1
'@codemirror/view': 6.35.0
'@lezer/common': 1.2.3
dev: true
/@codemirror/language@6.10.6:
resolution: {integrity: sha512-KrsbdCnxEztLVbB5PycWXFxas4EOyk/fPAfruSOnDDppevQgid2XZ+KbJ9u+fDikP/e7MW7HPBTvTb8JlZK9vA==, tarball: https://registry.npmmirror.com/@codemirror/language/-/language-6.10.6.tgz}
dependencies:
'@codemirror/state': 6.4.1
'@codemirror/view': 6.35.0
'@lezer/common': 1.2.3
'@lezer/highlight': 1.2.1
'@lezer/lr': 1.4.2
style-mod: 4.1.2
dev: true
/@codemirror/lint@6.8.4:
resolution: {integrity: sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==, tarball: https://registry.npmmirror.com/@codemirror/lint/-/lint-6.8.4.tgz}
dependencies:
'@codemirror/state': 6.4.1
'@codemirror/view': 6.35.0
crelt: 1.0.6
dev: true
/@codemirror/state@6.4.1:
resolution: {integrity: sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==, tarball: https://registry.npmmirror.com/@codemirror/state/-/state-6.4.1.tgz}
dev: true
/@codemirror/view@6.35.0:
resolution: {integrity: sha512-I0tYy63q5XkaWsJ8QRv5h6ves7kvtrBWjBcnf/bzohFJQc5c14a1AQRdE8QpPF9eMp5Mq2FMm59TCj1gDfE7kw==, tarball: https://registry.npmmirror.com/@codemirror/view/-/view-6.35.0.tgz}
dependencies:
'@codemirror/state': 6.4.1
style-mod: 4.1.2
w3c-keyname: 2.2.8
dev: true
/@commitlint/cli@19.6.0(@types/node@20.17.9)(typescript@5.3.3):
@ -2034,7 +2122,7 @@ packages:
'@form-create/element-ui': 3.2.14(vue@3.5.12)
'@form-create/utils': 3.2.14
codemirror: 6.65.7
element-plus: 2.8.4(vue@3.5.12)
element-plus: 2.9.1(vue@3.5.12)
vue: 3.5.12(typescript@5.3.3)
vuedraggable: 4.1.0(vue@3.5.12)
transitivePeerDependencies:
@ -2274,6 +2362,29 @@ packages:
'@jridgewell/sourcemap-codec': 1.5.0
dev: true
/@lezer/common@1.2.3:
resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==, tarball: https://registry.npmmirror.com/@lezer/common/-/common-1.2.3.tgz}
dev: true
/@lezer/highlight@1.2.1:
resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==, tarball: https://registry.npmmirror.com/@lezer/highlight/-/highlight-1.2.1.tgz}
dependencies:
'@lezer/common': 1.2.3
dev: true
/@lezer/lr@1.4.2:
resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==, tarball: https://registry.npmmirror.com/@lezer/lr/-/lr-1.4.2.tgz}
dependencies:
'@lezer/common': 1.2.3
dev: true
/@lezer/markdown@1.3.2:
resolution: {integrity: sha512-Wu7B6VnrKTbBEohqa63h5vxXjiC4pO5ZQJ/TDbhJxPQaaIoRD/6UVDhSDtVsCwVZV12vvN9KxuLL3ATMnlG0oQ==, tarball: https://registry.npmmirror.com/@lezer/markdown/-/markdown-1.3.2.tgz}
dependencies:
'@lezer/common': 1.2.3
'@lezer/highlight': 1.2.1
dev: true
/@microsoft/fetch-event-source@2.0.1:
resolution: {integrity: sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==, tarball: https://registry.npmmirror.com/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz}
dev: false
@ -4264,6 +4375,11 @@ packages:
resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==, tarball: https://registry.npmmirror.com/array-ify/-/array-ify-1.0.0.tgz}
dev: true
/array-move@4.0.0:
resolution: {integrity: sha512-+RY54S8OuVvg94THpneQvFRmqWdAHeqtMzgMW6JNurHxe8rsS07cHQdfGkXnTUXiBcyZ0j3SiDIxxj0RPiqCkQ==, tarball: https://registry.npmmirror.com/array-move/-/array-move-4.0.0.tgz}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: true
/array-union@2.1.0:
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==, tarball: https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz}
engines: {node: '>=8'}
@ -4446,21 +4562,23 @@ packages:
/boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==, tarball: https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz}
/bpmn-js-properties-panel@0.46.0(bpmn-js@8.10.0):
resolution: {integrity: sha512-8MlNvHklIZZQH9vtoKf0A0A1v0sHO4Iz19jGhHeX15czOOiCfdavjo+q23GHWNKzQA9347F91XYFcrnM6FO8zw==, tarball: https://registry.npmmirror.com/bpmn-js-properties-panel/-/bpmn-js-properties-panel-0.46.0.tgz}
/bpmn-js-properties-panel@5.23.0(@bpmn-io/properties-panel@3.25.0)(bpmn-js@17.11.1)(camunda-bpmn-js-behaviors@1.7.2)(diagram-js@12.8.1):
resolution: {integrity: sha512-4B27LM8oV14A2QWRvazV17h4NxbkNERcqU+AGJmxKImMlLhu9893MWR+pCdTQCTphBdBkuD8ksWm+1wVCedJ7g==, tarball: https://registry.npmmirror.com/bpmn-js-properties-panel/-/bpmn-js-properties-panel-5.23.0.tgz}
peerDependencies:
bpmn-js: ^3.x || ^4.x || ^5.x || ^6.x || ^7.x || ^8.x
'@bpmn-io/properties-panel': '>= 3.7'
bpmn-js: '>= 11.5'
camunda-bpmn-js-behaviors: '>= 0.4'
diagram-js: '>= 11.9'
dependencies:
'@bpmn-io/element-templates-validator': 0.2.0
'@bpmn-io/extract-process-variables': 0.4.5
bpmn-js: 8.10.0
'@bpmn-io/extract-process-variables': 0.8.0
'@bpmn-io/properties-panel': 3.25.0(@lezer/common@1.2.3)
array-move: 4.0.0
bpmn-js: 17.11.1
camunda-bpmn-js-behaviors: 1.7.2(bpmn-js@17.11.1)(camunda-bpmn-moddle@7.0.1)(zeebe-bpmn-moddle@1.7.0)
diagram-js: 12.8.1
ids: 1.0.5
inherits: 2.0.4
lodash: 4.17.21
min-dom: 3.2.1
scroll-tabs: 1.0.1
selection-update: 0.1.2
semver: 6.3.1
min-dash: 4.2.2
min-dom: 4.2.1
dev: true
/bpmn-js-token-simulation@0.10.0:
@ -4471,27 +4589,25 @@ packages:
svg.js: 2.7.1
dev: false
/bpmn-js@8.10.0:
resolution: {integrity: sha512-NozeOi01qL0ZdVq8+5hWZcikyEvgrP1yzCBqlhSufJdHFsnEMBCwn2bJJ0B/6JgX+IBwy1sk/Uw+Ds8rQ8vfrw==, tarball: https://registry.npmmirror.com/bpmn-js/-/bpmn-js-8.10.0.tgz}
/bpmn-js@17.11.1:
resolution: {integrity: sha512-ywCeTg5kvN8lYkU+fHE+YXTGlfKc55lRBn7zW3k1//toeMNPy/PS/uQiujRWdFhMrH5dbtDvlwWukNw2pjWw8Q==, tarball: https://registry.npmmirror.com/bpmn-js/-/bpmn-js-17.11.1.tgz}
dependencies:
bpmn-moddle: 7.1.3
css.escape: 1.5.1
diagram-js: 7.9.0
diagram-js-direct-editing: 1.8.0(diagram-js@7.9.0)
bpmn-moddle: 8.1.0
diagram-js: 14.11.3
diagram-js-direct-editing: 3.2.0(diagram-js@14.11.3)
ids: 1.0.5
inherits: 2.0.4
min-dash: 3.8.1
min-dom: 3.2.1
object-refs: 0.3.0
tiny-svg: 2.2.4
inherits-browser: 0.1.0
min-dash: 4.2.2
min-dom: 4.2.1
tiny-svg: 3.1.3
dev: true
/bpmn-moddle@7.1.3:
resolution: {integrity: sha512-ZcBfw0NSOdYTSXFKEn7MOXHItz7VfLZTrFYKO8cK6V8ZzGjCcdiLIOiw7Lctw1PJsihhLiZQS8Htj2xKf+NwCg==, tarball: https://registry.npmmirror.com/bpmn-moddle/-/bpmn-moddle-7.1.3.tgz}
/bpmn-moddle@8.1.0:
resolution: {integrity: sha512-yI5OAFfYVJwViKTsTsonVfCBPtB3MlefADUORwNIxxBOMp21vnoxuxsdgUWlPH/dvAEZh/+mr8UtqOBNu8NC5Q==, tarball: https://registry.npmmirror.com/bpmn-moddle/-/bpmn-moddle-8.1.0.tgz}
dependencies:
min-dash: 3.8.1
moddle: 5.0.4
moddle-xml: 9.0.6
min-dash: 4.2.2
moddle: 6.2.3
moddle-xml: 10.1.0
dev: true
/brace-expansion@1.1.11:
@ -4598,9 +4714,22 @@ packages:
engines: {node: '>=6'}
dev: false
/camunda-bpmn-js-behaviors@1.7.2(bpmn-js@17.11.1)(camunda-bpmn-moddle@7.0.1)(zeebe-bpmn-moddle@1.7.0):
resolution: {integrity: sha512-xjLJHc18T40tcYu4JCeYDo1wR5i9+ZqcVnXVP6c4ooAe2gKISbBvFc07gqGpqiwm7TpEBvUfDj3PrRr+ofaf4w==, tarball: https://registry.npmmirror.com/camunda-bpmn-js-behaviors/-/camunda-bpmn-js-behaviors-1.7.2.tgz}
peerDependencies:
bpmn-js: '>= 9'
camunda-bpmn-moddle: '>= 7'
zeebe-bpmn-moddle: '>= 0.18'
dependencies:
bpmn-js: 17.11.1
camunda-bpmn-moddle: 7.0.1
ids: 1.0.5
min-dash: 4.2.2
zeebe-bpmn-moddle: 1.7.0
dev: true
/camunda-bpmn-moddle@7.0.1:
resolution: {integrity: sha512-Br8Diu6roMpziHdpl66Dhnm0DTnCFMrSD9zwLV08LpD52QA0UsXxU87XfHf08HjuB7ly0Hd1bvajZRpf9hbmYQ==, tarball: https://registry.npmmirror.com/camunda-bpmn-moddle/-/camunda-bpmn-moddle-7.0.1.tgz}
dev: false
/caniuse-lite@1.0.30001684:
resolution: {integrity: sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==, tarball: https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz}
@ -4686,6 +4815,10 @@ packages:
static-extend: 0.1.2
dev: true
/classnames@2.5.1:
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==, tarball: https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz}
dev: true
/cli-cursor@5.0.0:
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==, tarball: https://registry.npmmirror.com/cli-cursor/-/cli-cursor-5.0.0.tgz}
engines: {node: '>=18'}
@ -4726,7 +4859,6 @@ packages:
/clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==, tarball: https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz}
engines: {node: '>=6'}
dev: false
/codemirror@6.65.7:
resolution: {integrity: sha512-HcfnUFJwI2FvH73YWVbbMh7ObWxZiHIycEhv9ZEXy6e8ZKDjtZKbbYFUtsLN46HFXPvU5V2Uvc2d55Z//oFW5A==, tarball: https://registry.npmmirror.com/codemirror/-/codemirror-6.65.7.tgz}
@ -4820,10 +4952,10 @@ packages:
/component-event@0.1.4:
resolution: {integrity: sha512-GMwOG8MnUHP1l8DZx1ztFO0SJTFnIzZnBDkXAj8RM2ntV2A6ALlDxgbMY1Fvxlg6WPQ+5IM/a6vg4PEYbjg/Rw==, tarball: https://registry.npmmirror.com/component-event/-/component-event-0.1.4.tgz}
dev: false
/component-event@0.2.1:
resolution: {integrity: sha512-wGA++isMqiDq1jPYeyv2as/Bt/u+3iLW0rEa+8NQ82jAv3TgqMiCM+B2SaBdn2DfLilLjjq736YcezihRYhfxw==, tarball: https://registry.npmmirror.com/component-event/-/component-event-0.2.1.tgz}
dev: false
/component-indexof@0.0.3:
resolution: {integrity: sha512-puDQKvx/64HZXb4hBwIcvQLaLgux8o1CbWl39s41hrIIZDl1lJiD5jc22gj3RBeGK0ovxALDYpIbyjqDUUl0rw==, tarball: https://registry.npmmirror.com/component-indexof/-/component-indexof-0.0.3.tgz}
@ -4949,6 +5081,10 @@ packages:
typescript: 5.3.3
dev: true
/crelt@1.0.6:
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==, tarball: https://registry.npmmirror.com/crelt/-/crelt-1.0.6.tgz}
dev: true
/cropperjs@1.6.2:
resolution: {integrity: sha512-nhymn9GdnV3CqiEHJVai54TULFAE3VshJTXSqSJKa8yXAKyBKDWdhHarnlIPrshJ0WMFTGuFvG02YjLXfPiuOA==, tarball: https://registry.npmmirror.com/cropperjs/-/cropperjs-1.6.2.tgz}
dev: false
@ -5027,10 +5163,6 @@ packages:
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==, tarball: https://registry.npmmirror.com/css-what/-/css-what-6.1.0.tgz}
engines: {node: '>= 6'}
/css.escape@1.5.1:
resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==, tarball: https://registry.npmmirror.com/css.escape/-/css.escape-1.5.1.tgz}
dev: true
/cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==, tarball: https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz}
engines: {node: '>=4'}
@ -5455,14 +5587,14 @@ packages:
dev: true
optional: true
/diagram-js-direct-editing@1.8.0(diagram-js@7.9.0):
resolution: {integrity: sha512-B4Xj+PJfgBjbPEzT3uZQEkZI5xHFB0Izc+7BhDFuHidzrEMzQKZrFGdA3PqfWhReHf3dp+iB6Tt11G9eGNjKMw==, tarball: https://registry.npmmirror.com/diagram-js-direct-editing/-/diagram-js-direct-editing-1.8.0.tgz}
/diagram-js-direct-editing@3.2.0(diagram-js@14.11.3):
resolution: {integrity: sha512-+pyxeQGBSdLiZX0/tmmsm2qZSvm9YtVzod5W3RMHSTR7VrkUMD6E7EX/W9JQv3ebxO7oIdqFmytmNDDpSHnYEw==, tarball: https://registry.npmmirror.com/diagram-js-direct-editing/-/diagram-js-direct-editing-3.2.0.tgz}
peerDependencies:
diagram-js: '*'
dependencies:
diagram-js: 7.9.0
min-dash: 3.8.1
min-dom: 3.2.1
diagram-js: 14.11.3
min-dash: 4.2.2
min-dom: 4.2.1
dev: true
/diagram-js@12.8.1:
@ -5478,29 +5610,28 @@ packages:
object-refs: 0.3.0
path-intersection: 2.2.1
tiny-svg: 3.1.3
dev: false
/diagram-js@7.9.0:
resolution: {integrity: sha512-o1yUtX5TXV1pmpevP55gxU/AEG6nCidOXGs/HLuxNXG0zMZ3jQta7kMqRxTK93rNw/XuHmP1eMOwdvdJ2RP5qA==, tarball: https://registry.npmmirror.com/diagram-js/-/diagram-js-7.9.0.tgz}
/diagram-js@14.11.3:
resolution: {integrity: sha512-Seq9BHAXfzKS60L4v4Gvgvv72wOtvrfJQAyyPm9pntSZDMzjoodPSXnEUPud1G2zVCMGEUUW++s0reEdaWgkXA==, tarball: https://registry.npmmirror.com/diagram-js/-/diagram-js-14.11.3.tgz}
dependencies:
css.escape: 1.5.1
didi: 5.2.1
hammerjs: 2.0.8
inherits: 2.0.4
min-dash: 3.8.1
min-dom: 3.2.1
object-refs: 0.3.0
path-intersection: 2.2.1
tiny-svg: 2.2.4
'@bpmn-io/diagram-js-ui': 0.2.3
clsx: 2.1.1
didi: 10.2.2
inherits-browser: 0.1.0
min-dash: 4.2.2
min-dom: 4.2.1
object-refs: 0.4.0
path-intersection: 3.1.0
tiny-svg: 3.1.3
dev: true
/didi@5.2.1:
resolution: {integrity: sha512-IKNnajUlD4lWMy/Q9Emkk7H1qnzREgY4UyE3IhmOi/9IKua0JYtYldk928bOdt1yNxN8EiOy1sqtSozEYsmjCg==, tarball: https://registry.npmmirror.com/didi/-/didi-5.2.1.tgz}
/didi@10.2.2:
resolution: {integrity: sha512-l8NYkYFXV1izHI65EyT8EXOjUZtKmQkHLTT89cSP7HU5J/G7AOj0dXKtLc04EXYlga99PBY18IPjOeZ+c3DI4w==, tarball: https://registry.npmmirror.com/didi/-/didi-10.2.2.tgz}
engines: {node: '>= 16'}
dev: true
/didi@9.0.2:
resolution: {integrity: sha512-q2+aj+lnJcUweV7A9pdUrwFr4LHVmRPwTmQLtHPFz4aT7IBoryN6Iy+jmFku+oIzr5ebBkvtBCOb87+dJhb7bg==, tarball: https://registry.npmmirror.com/didi/-/didi-9.0.2.tgz}
dev: false
/dijkstrajs@1.0.3:
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==, tarball: https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz}
@ -5585,6 +5716,11 @@ packages:
/domify@1.4.2:
resolution: {integrity: sha512-m4yreHcUWHBncGVV7U+yQzc12vIlq0jMrtHZ5mW6dQMiL/7skSYNVX9wqKwOtyO9SGCgevrAFEgOCAHmamHTUA==, tarball: https://registry.npmmirror.com/domify/-/domify-1.4.2.tgz}
/domify@2.0.0:
resolution: {integrity: sha512-rmvrrmWQPD/X1A/nPBfIVg4r05792QdG9Z4Prk6oQG0F9zBMDkr0GKAdds1wjb2dq1rTz/ywc4ZxpZbgz0tttg==, tarball: https://registry.npmmirror.com/domify/-/domify-2.0.0.tgz}
engines: {node: '>=18'}
dev: true
/dompurify@3.2.1:
resolution: {integrity: sha512-NBHEsc0/kzRYQd+AY6HR6B/IgsqzBABrqJbpCDQII/OK6h7B7LXzweZTDsqSW2LkTRpoxf18YUP+YjGySk6B3w==, tarball: https://registry.npmmirror.com/dompurify/-/dompurify-3.2.1.tgz}
optionalDependencies:
@ -5659,8 +5795,8 @@ packages:
resolution: {integrity: sha512-nz88NNBsD7kQSAGGJyp8hS6xSPtWwqNogA0mjtc2nUYeEf3nURK9qpV18TuBdDmEDgVWotS8Wkzf+V52dSQ/LQ==, tarball: https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.67.tgz}
dev: true
/element-plus@2.8.4(vue@3.5.12):
resolution: {integrity: sha512-ZlVAdUOoJliv4kW3ntWnnSHMT+u/Os7mXJjk2xzOlqNeHaI2/ozlF+R58ZCEak8ZnDi6+5A2viWEYRsq64IuiA==, tarball: https://registry.npmmirror.com/element-plus/-/element-plus-2.8.4.tgz}
/element-plus@2.9.1(vue@3.5.12):
resolution: {integrity: sha512-9Agqf/jt4Ugk7EZ6C5LME71sgkvauPCsnvJN12Xid2XVobjufxMGpRE4L7pS4luJMOmFAH3J0NgYEGZT5r+NDg==, tarball: https://registry.npmmirror.com/element-plus/-/element-plus-2.9.1.tgz}
peerDependencies:
vue: ^3.2.0
dependencies:
@ -6228,6 +6364,34 @@ packages:
picomatch: 4.0.2
dev: true
/feelers@1.4.0:
resolution: {integrity: sha512-CGa/7ILuqoqTaeYeoKsg/4tzu2es9sEEJTmSjdu0lousZBw4V9gcYhHYFNmbrSrKmbAVfOzj6/DsymGJWFIOeg==, tarball: https://registry.npmmirror.com/feelers/-/feelers-1.4.0.tgz}
dependencies:
'@bpmn-io/cm-theme': 0.1.0-alpha.2
'@bpmn-io/feel-lint': 1.3.1
'@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3)
'@codemirror/commands': 6.7.1
'@codemirror/language': 6.10.6
'@codemirror/lint': 6.8.4
'@codemirror/state': 6.4.1
'@codemirror/view': 6.35.0
'@lezer/common': 1.2.3
'@lezer/highlight': 1.2.1
'@lezer/lr': 1.4.2
'@lezer/markdown': 1.3.2
feelin: 3.2.0
lezer-feel: 1.4.0
min-dom: 5.1.1
dev: true
/feelin@3.2.0:
resolution: {integrity: sha512-GFDbHsTYk7YXO1tyw1dOjb7IODeAZvNIosdGZThUwPx5XcD/XhO0hnPZXsIbAzSsIdrgGlTEEdby9fZ2gixysA==, tarball: https://registry.npmmirror.com/feelin/-/feelin-3.2.0.tgz}
dependencies:
'@lezer/lr': 1.4.2
lezer-feel: 1.4.0
luxon: 3.5.0
dev: true
/file-entry-cache@6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==, tarball: https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz}
engines: {node: ^10.12.0 || >=12.0.0}
@ -6311,6 +6475,12 @@ packages:
resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==, tarball: https://registry.npmmirror.com/flatted/-/flatted-3.3.2.tgz}
dev: true
/focus-trap@7.6.2:
resolution: {integrity: sha512-9FhUxK1hVju2+AiQIDJ5Dd//9R2n2RAfJ0qfhF4IHGHgcoEUTMpbTeG/zbEuwaiYXfuAH6XE0/aCyxDdRM+W5w==, tarball: https://registry.npmmirror.com/focus-trap/-/focus-trap-7.6.2.tgz}
dependencies:
tabbable: 6.2.0
dev: true
/follow-redirects@1.15.9(debug@4.3.7):
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==, tarball: https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz}
engines: {node: '>=4.0'}
@ -6674,7 +6844,6 @@ packages:
/htm@3.1.1:
resolution: {integrity: sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==, tarball: https://registry.npmmirror.com/htm/-/htm-3.1.1.tgz}
dev: false
/html-tags@3.3.1:
resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==, tarball: https://registry.npmmirror.com/html-tags/-/html-tags-3.3.1.tgz}
@ -6772,10 +6941,6 @@ packages:
engines: {node: '>=8'}
dev: true
/indexof@0.0.1:
resolution: {integrity: sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==, tarball: https://registry.npmmirror.com/indexof/-/indexof-0.0.1.tgz}
dev: true
/individual@2.0.0:
resolution: {integrity: sha512-pWt8hBCqJsUWI/HtcfWod7+N9SgAqyPEaF7JQjwzjn5vGrpg6aQ5qeAFQ7dx//UH4J1O+7xqew+gCeeFt6xN/g==, tarball: https://registry.npmmirror.com/individual/-/individual-2.0.0.tgz}
dev: false
@ -6790,7 +6955,6 @@ packages:
/inherits-browser@0.1.0:
resolution: {integrity: sha512-CJHHvW3jQ6q7lzsXPpapLdMx5hDpSF3FSh45pwsj6bKxJJ8Nl8v43i5yXnr3BdfOimGHKyniewQtnAIp3vyJJw==, tarball: https://registry.npmmirror.com/inherits-browser/-/inherits-browser-0.1.0.tgz}
dev: false
/inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==, tarball: https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz}
@ -7209,10 +7373,6 @@ packages:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==, tarball: https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz}
dev: true
/json-source-map@0.6.1:
resolution: {integrity: sha512-1QoztHPsMQqhDq0hlXY5ZqcEdUzxQEIxgFkKl4WUp2pgShObl+9ovi4kRh2TfvAfxAoHOJ9vIMEqk3k4iex7tg==, tarball: https://registry.npmmirror.com/json-source-map/-/json-source-map-0.6.1.tgz}
dev: true
/json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, tarball: https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz}
dev: true
@ -7302,6 +7462,17 @@ packages:
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==, tarball: https://registry.npmmirror.com/kolorist/-/kolorist-1.8.0.tgz}
dev: true
/lang-feel@2.2.0:
resolution: {integrity: sha512-Ebo5nftYsMfJzB3Ny8Oy4oaDXZXb5x61qtVVmKv6aImvAZUbT76mD60ZbEilizjZQzsR2CcU1iMK5sacIa1NVA==, tarball: https://registry.npmmirror.com/lang-feel/-/lang-feel-2.2.0.tgz}
dependencies:
'@codemirror/autocomplete': 6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.4.1)(@codemirror/view@6.35.0)(@lezer/common@1.2.3)
'@codemirror/language': 6.10.6
'@codemirror/state': 6.4.1
'@codemirror/view': 6.35.0
'@lezer/common': 1.2.3
lezer-feel: 1.4.0
dev: true
/levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, tarball: https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz}
engines: {node: '>= 0.8.0'}
@ -7310,6 +7481,14 @@ packages:
type-check: 0.4.0
dev: true
/lezer-feel@1.4.0:
resolution: {integrity: sha512-kNxG7O38gwpuYy+C3JCRxQNTCE2qu9uTuH5dE3EGVnRhIQMe6rPDz0S8t3urLEOsMud6HI795m6zX2ujfUaqTw==, tarball: https://registry.npmmirror.com/lezer-feel/-/lezer-feel-1.4.0.tgz}
dependencies:
'@lezer/highlight': 1.2.1
'@lezer/lr': 1.4.2
min-dash: 4.2.2
dev: true
/lilconfig@3.1.2:
resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==, tarball: https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.2.tgz}
engines: {node: '>=14'}
@ -7513,6 +7692,11 @@ packages:
yallist: 3.1.1
dev: true
/luxon@3.5.0:
resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==, tarball: https://registry.npmmirror.com/luxon/-/luxon-3.5.0.tgz}
engines: {node: '>=12'}
dev: true
/m3u8-parser@4.8.0:
resolution: {integrity: sha512-UqA2a/Pw3liR6Df3gwxrqghCP17OpPlQj6RBPLYygf/ZSQ4MoSgvdvhvt35qV+3NaaA0FSZx93Ix+2brT1U7cA==, tarball: https://registry.npmmirror.com/m3u8-parser/-/m3u8-parser-4.8.0.tgz}
dependencies:
@ -7608,10 +7792,6 @@ packages:
markmap-common: 0.16.0
dev: false
/matches-selector@1.2.0:
resolution: {integrity: sha512-c4vLwYWyl+Ji+U43eU/G5FwxWd4ZH0ePUsFs5y0uwD9HUEFBXUQ1zUUan+78IpRD+y4pUfG0nAzNM292K7ItvA==, tarball: https://registry.npmmirror.com/matches-selector/-/matches-selector-1.2.0.tgz}
dev: true
/mathml-tag-names@2.1.3:
resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==, tarball: https://registry.npmmirror.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz}
dev: true
@ -7721,10 +7901,10 @@ packages:
/min-dash@3.8.1:
resolution: {integrity: sha512-evumdlmIlg9mbRVPbC4F5FuRhNmcMS5pvuBUbqb1G9v09Ro0ImPEgz5n3khir83lFok1inKqVDjnKEg3GpDxQg==, tarball: https://registry.npmmirror.com/min-dash/-/min-dash-3.8.1.tgz}
dev: false
/min-dash@4.2.2:
resolution: {integrity: sha512-qbhSYUxk6mBaF096B3JOQSumXbKWHenmT97cSpdNzgkWwGjhjhE/KZODCoDNhI2I4C9Cb6R/Q13S4BYkUSXoXQ==, tarball: https://registry.npmmirror.com/min-dash/-/min-dash-4.2.2.tgz}
dev: false
/min-document@2.19.0:
resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==, tarball: https://registry.npmmirror.com/min-document/-/min-document-2.19.0.tgz}
@ -7744,23 +7924,19 @@ packages:
domify: 1.4.2
dev: false
/min-dom@3.2.1:
resolution: {integrity: sha512-v6YCmnDzxk4rRJntWTUiwggLupPw/8ZSRqUq0PDaBwVZEO/wYzCH4SKVBV+KkEvf3u0XaWHly5JEosPtqRATZA==, tarball: https://registry.npmmirror.com/min-dom/-/min-dom-3.2.1.tgz}
dependencies:
component-event: 0.1.4
domify: 1.4.2
indexof: 0.0.1
matches-selector: 1.2.0
min-dash: 3.8.1
dev: true
/min-dom@4.2.1:
resolution: {integrity: sha512-TMoL8SEEIhUWYgkj7XMSgxmwSyGI+4fP2KFFGnN3FbHfbGHVdsLYSz8LoIsgPhz4dWRmLvxWWSMgzZMJW5sZuA==, tarball: https://registry.npmmirror.com/min-dom/-/min-dom-4.2.1.tgz}
dependencies:
component-event: 0.2.1
domify: 1.4.2
min-dash: 4.2.2
dev: false
/min-dom@5.1.1:
resolution: {integrity: sha512-GaKUlguMAofd3OJsB0OkP17i5kucKqErgVCJxPawO9l5NwIPnr28SAr99zzlzMCWWljISBYrnZVWdE2Q92YGFQ==, tarball: https://registry.npmmirror.com/min-dom/-/min-dom-5.1.1.tgz}
dependencies:
domify: 2.0.0
min-dash: 4.2.2
dev: true
/minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, tarball: https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz}
@ -7798,10 +7974,6 @@ packages:
engines: {node: '>=16 || 14 >=14.17'}
dev: true
/mitt@1.2.0:
resolution: {integrity: sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw==, tarball: https://registry.npmmirror.com/mitt/-/mitt-1.2.0.tgz}
dev: true
/mitt@3.0.1:
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==, tarball: https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz}
dev: false
@ -7823,18 +7995,18 @@ packages:
ufo: 1.5.4
dev: true
/moddle-xml@9.0.6:
resolution: {integrity: sha512-tl0reHpsY/aKlLGhXeFlQWlYAQHFxTkFqC8tq8jXRYpQSnLVw13T6swMaourLd7EXqHdWsc+5ggsB+fEep6xZQ==, tarball: https://registry.npmmirror.com/moddle-xml/-/moddle-xml-9.0.6.tgz}
/moddle-xml@10.1.0:
resolution: {integrity: sha512-erWckwLt+dYskewKXJso9u+aAZ5172lOiYxSOqKCPTy7L/xmqH1PoeoA7eVC7oJTt3PqF5TkZzUmbjGH6soQBg==, tarball: https://registry.npmmirror.com/moddle-xml/-/moddle-xml-10.1.0.tgz}
dependencies:
min-dash: 3.8.1
moddle: 5.0.4
min-dash: 4.2.2
moddle: 6.2.3
saxen: 8.1.2
dev: true
/moddle@5.0.4:
resolution: {integrity: sha512-Kjb+hjuzO+YlojNGxEUXvdhLYTHTtAABDlDcJTtTcn5MbJF9Zkv4I1Fyvp3Ypmfgg1EfHDZ3PsCQTuML9JD6wg==, tarball: https://registry.npmmirror.com/moddle/-/moddle-5.0.4.tgz}
/moddle@6.2.3:
resolution: {integrity: sha512-bLVN+ZHL3aKnhxc19XtjUfvdJsS3EsiEJC7bT6YPD11qYmTzvsxrGgyYz1Ouof7TZuGw0lDJ1OLmEnxcpQWk3Q==, tarball: https://registry.npmmirror.com/moddle/-/moddle-6.2.3.tgz}
dependencies:
min-dash: 3.8.1
min-dash: 4.2.2
dev: true
/mpd-parser@0.22.1:
@ -7994,6 +8166,10 @@ packages:
/object-refs@0.3.0:
resolution: {integrity: sha512-eP0ywuoWOaDoiake/6kTJlPJhs+k0qNm4nYRzXLNHj6vh+5M3i9R1epJTdxIPGlhWc4fNRQ7a6XJNCX+/L4FOQ==, tarball: https://registry.npmmirror.com/object-refs/-/object-refs-0.3.0.tgz}
/object-refs@0.4.0:
resolution: {integrity: sha512-6kJqKWryKZmtte6QYvouas0/EIJKPI1/MMIuRsiBlNuhIMfqYTggzX2F1AJ2+cDs288xyi9GL7FyasHINR98BQ==, tarball: https://registry.npmmirror.com/object-refs/-/object-refs-0.4.0.tgz}
dev: true
/object-visit@1.0.1:
resolution: {integrity: sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==, tarball: https://registry.npmmirror.com/object-visit/-/object-visit-1.0.1.tgz}
engines: {node: '>=0.10.0'}
@ -8164,6 +8340,11 @@ packages:
/path-intersection@2.2.1:
resolution: {integrity: sha512-9u8xvMcSfuOiStv9bPdnRJQhGQXLKurew94n4GPQCdH1nj9QKC9ObbNoIpiRq8skiOBxKkt277PgOoFgAt3/rA==, tarball: https://registry.npmmirror.com/path-intersection/-/path-intersection-2.2.1.tgz}
/path-intersection@3.1.0:
resolution: {integrity: sha512-3xS3lvv/vuwm5aH2BVvNRvnvwR2Drde7jQClKpCXTYXIMMjcw/EnMhzCgeHwqbCpzi760PEfAkU53vSIlrNr9A==, tarball: https://registry.npmmirror.com/path-intersection/-/path-intersection-3.1.0.tgz}
engines: {node: '>= 14.20'}
dev: true
/path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==, tarball: https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz}
engines: {node: '>=0.10.0'}
@ -8415,7 +8596,6 @@ packages:
/preact@10.25.0:
resolution: {integrity: sha512-6bYnzlLxXV3OSpUxLdaxBmE7PMOu0aR3pG6lryK/0jmvcDFPlcXGQAt5DpK3RITWiDrfYZRI0druyaK/S9kYLg==, tarball: https://registry.npmmirror.com/preact/-/preact-10.25.0.tgz}
dev: false
/prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, tarball: https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz}
@ -8872,22 +9052,10 @@ packages:
compute-scroll-into-view: 1.0.20
dev: false
/scroll-tabs@1.0.1:
resolution: {integrity: sha512-W4xjEwNS4QAyQnaJ450vQTcKpbnalBAfsTDV926WrxEMOqjyj2To8uv2d0Cp0oxMdk5TkygtzXmctPNc2zgBcg==, tarball: https://registry.npmmirror.com/scroll-tabs/-/scroll-tabs-1.0.1.tgz}
dependencies:
min-dash: 3.8.1
min-dom: 3.2.1
mitt: 1.2.0
dev: true
/scule@1.3.0:
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==, tarball: https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz}
dev: true
/selection-update@0.1.2:
resolution: {integrity: sha512-4jzoJNh7VT2s2tvm/kUSskSw7pD0BVcrrGccbfOMK+3AXLBPz6nIy1yo+pbXgvNoTNII96Pq92+sAY+rF0LUAA==, tarball: https://registry.npmmirror.com/selection-update/-/selection-update-0.1.2.tgz}
dev: true
/semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==, tarball: https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz}
hasBin: true
@ -9245,6 +9413,10 @@ packages:
resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==, tarball: https://registry.npmmirror.com/strnum/-/strnum-1.0.5.tgz}
dev: false
/style-mod@4.1.2:
resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==, tarball: https://registry.npmmirror.com/style-mod/-/style-mod-4.1.2.tgz}
dev: true
/stylelint-config-html@1.1.0(postcss-html@1.7.0)(stylelint@16.11.0):
resolution: {integrity: sha512-IZv4IVESjKLumUGi+HWeb7skgO6/g4VMuAYrJdlqQFndgbj6WJAXPhaysvBiXefX79upBdQVumgYcdd17gCpjQ==, tarball: https://registry.npmmirror.com/stylelint-config-html/-/stylelint-config-html-1.1.0.tgz}
engines: {node: ^12 || >=14}
@ -9427,6 +9599,10 @@ packages:
resolution: {integrity: sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==, tarball: https://registry.npmmirror.com/systemjs/-/systemjs-6.15.1.tgz}
dev: true
/tabbable@6.2.0:
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==, tarball: https://registry.npmmirror.com/tabbable/-/tabbable-6.2.0.tgz}
dev: true
/table@6.8.2:
resolution: {integrity: sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==, tarball: https://registry.npmmirror.com/table/-/table-6.8.2.tgz}
engines: {node: '>=10.0.0'}
@ -9462,13 +9638,8 @@ packages:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==, tarball: https://registry.npmmirror.com/through/-/through-2.3.8.tgz}
dev: true
/tiny-svg@2.2.4:
resolution: {integrity: sha512-NOi39lBknf4UdDEahNkbEAJnzhu1ZcN2j75IS2vLRmIhsfxdZpTChfLKBcN1ShplVmPIXJAIafk6YY5/Aa80lQ==, tarball: https://registry.npmmirror.com/tiny-svg/-/tiny-svg-2.2.4.tgz}
dev: true
/tiny-svg@3.1.3:
resolution: {integrity: sha512-9mwnPqXInRsBmH/DO6NMxBE++9LsqpVXQSSTZGc5bomoKKvL5OX/Hlotw7XVXP6XLRcHWIzZpxfovGqWKgCypQ==, tarball: https://registry.npmmirror.com/tiny-svg/-/tiny-svg-3.1.3.tgz}
dev: false
/tiny-warning@1.0.3:
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==, tarball: https://registry.npmmirror.com/tiny-warning/-/tiny-warning-1.0.3.tgz}
@ -10177,6 +10348,10 @@ packages:
vue: 3.5.12(typescript@5.3.3)
dev: false
/w3c-keyname@2.2.8:
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==, tarball: https://registry.npmmirror.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz}
dev: true
/wangeditor@4.7.15:
resolution: {integrity: sha512-aPTdREd8BxXVyJ5MI+LU83FQ7u1EPd341iXIorRNYSOvoimNoZ4nPg+yn3FGbB93/owEa6buLw8wdhYnMCJQLg==, tarball: https://registry.npmmirror.com/wangeditor/-/wangeditor-4.7.15.tgz}
dependencies:
@ -10429,6 +10604,10 @@ packages:
engines: {node: '>=12.20'}
dev: true
/zeebe-bpmn-moddle@1.7.0:
resolution: {integrity: sha512-eZ6OXSt0c4n9V/oN/46gTlwDIS3GhWQLt9jbM5uS/YryB4yN8wdrrKrtw+TpyNy0SSKWXNDHyC83nCA2blPO3Q==, tarball: https://registry.npmmirror.com/zeebe-bpmn-moddle/-/zeebe-bpmn-moddle-1.7.0.tgz}
dev: true
/zrender@5.6.0:
resolution: {integrity: sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==, tarball: https://registry.npmmirror.com/zrender/-/zrender-5.6.0.tgz}
dependencies:

View File

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

View File

@ -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 })

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1735905505218" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4277" width="200" height="200" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M561.778 454.929h198.117c0.549 0 0.994 0.444 0.994 1.001v97.553a0.998 0.998 0 0 1-0.994 1.001H463.224a1.005 1.005 0 0 1-1.002-1V207.04c0-0.552 0.444-1 1.002-1h97.552c0.553 0 1.002 0.455 1.002 1v247.89zM512 952.706c-247.424 0-448-200.576-448-448 0-247.423 200.576-448 448-448s448 200.577 448 448c0 247.424-200.576 448-448 448z m0-99.555c192.44 0 348.444-156.004 348.444-348.445 0-192.44-156.003-348.444-348.444-348.444-192.44 0-348.444 156.004-348.444 348.444 0 192.441 156.003 348.445 348.444 348.445z" fill="#3296FA" p-id="4278"></path></svg>

After

Width:  |  Height:  |  Size: 876 B

View File

@ -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>

View File

@ -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
// 显示位置

View File

@ -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%;

View File

@ -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>

View File

@ -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;

View File

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

View File

@ -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()
},

View File

@ -79,9 +79,14 @@ function remoteMethod(data) {
function handleChange(path) {
router.push({ path })
hiddenSearch()
hiddenTopSearch()
}
function hiddenSearch() {
showSearch.value = false
}
function hiddenTopSearch() {
showTopSearch.value = false
}
@ -99,6 +104,8 @@ onUnmounted(() => {
// ctrl + k
function listenKey(event) {
if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
//
event.preventDefault()
showSearch.value = !showSearch.value
//
}

View File

@ -39,6 +39,13 @@
</div>
<div class="handler-item-text">包容分支</div>
</div>
<div class="handler-item" @click="addNode(NodeType.DELAY_TIMER_NODE)">
<!-- TODO @芋艿 需要更换一下iconfont的图标 -->
<div class="handler-item-icon copy">
<span class="iconfont icon-size icon-copy"></span>
</div>
<div class="handler-item-text">延迟器</div>
</div>
</div>
<template #reference>
<div class="add-icon"><Icon icon="ep:plus" /></div>
@ -208,6 +215,16 @@ const addNode = (type: number) => {
}
emits('update:childNode', data)
}
if (type === NodeType.DELAY_TIMER_NODE) {
const data: SimpleFlowNode = {
id: 'Activity_' + generateUUID(),
name: NODE_DEFAULT_NAME.get(NodeType.DELAY_TIMER_NODE) as string,
showText: '',
type: NodeType.DELAY_TIMER_NODE,
childNode: props.childNode
}
emits('update:childNode', data)
}
}
</script>

View File

@ -38,6 +38,12 @@
@update:model-value="handleModelValueUpdate"
@find:parent-node="findFromParentNode"
/>
<!-- 延迟器节点 -->
<DelayTimerNode
v-if="currentNode && currentNode.type === NodeType.DELAY_TIMER_NODE"
:flow-node="currentNode"
@update:flow-node="handleModelValueUpdate"
/>
<!-- 递归显示孩子节点 -->
<ProcessNodeTree
v-if="currentNode && currentNode.childNode"
@ -60,6 +66,7 @@ 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 DelayTimerNode from './nodes/DelayTimerNode.vue'
import { SimpleFlowNode, NodeType } from './consts'
import { useWatchNode } from './node'
defineOptions({

View File

@ -1,6 +1,7 @@
<template>
<div v-loading="loading" class="overflow-auto">
<SimpleProcessModel
ref="simpleProcessModelRef"
v-if="processNodeTree"
:flow-node="processNodeTree"
:readonly="false"
@ -38,12 +39,30 @@ import * as UserGroupApi from '@/api/bpm/userGroup'
defineOptions({
name: 'SimpleProcessDesigner'
})
const emits = defineEmits(['success']) //
const emits = defineEmits(['success', 'init-finished']) //
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
}
})
@ -56,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)
@ -64,33 +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 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) {
message.error('模型数据为空')
return
}
//
errorNodes = []
validateNode(simpleModelNode, errorNodes)
if (errorNodes.length > 0) {
errorDialogVisible.value = true
return
}
try {
loading.value = true
if (props.modelId) {
//
const data = {
id: props.modelId,
simpleModel: simpleModelNode
}
const result = await updateBpmSimpleModel(data)
if (result) {
message.success('修改成功')
emits('success')
} else {
message.alert('修改失败')
await updateBpmSimpleModel(data)
}
} finally {
loading.value = false
//
currentValue.value = simpleModelNode
emits('success', simpleModelNode)
} catch (error) {
console.error('保存失败:', error)
}
}
// showText
const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => {
if (node) {
@ -134,6 +225,7 @@ onMounted(async () => {
try {
loading.value = true
//
if (props.modelId) {
const bpmnModel = await getModel(props.modelId)
if (bpmnModel) {
formType.value = bpmnModel.formType
@ -142,6 +234,7 @@ onMounted(async () => {
formFields.value = bpmnForm?.fields
}
}
}
//
roleOptions.value = await RoleApi.getSimpleRoleList()
//
@ -150,30 +243,64 @@ 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
//
if (props.modelId) {
// SIMPLE
const result = await getBpmSimpleModel(props.modelId)
if (result) {
processNodeTree.value = result
await loadProcessData(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
}
updateModel()
}
} else if (props.value) {
await loadProcessData(props.value)
} else {
updateModel()
}
} finally {
loading.value = false
emits('init-finished')
}
})
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>

View File

@ -8,15 +8,6 @@
<el-button size="default" class="w-80px"> {{ scaleValue }}% </el-button>
<el-button size="default" :plain="true" :icon="ZoomIn" @click="zoomIn()" />
</el-button-group>
<el-button
v-if="!readonly"
size="default"
class="ml-4px"
type="primary"
:icon="Select"
@click="saveSimpleFlowModel"
>保存模型</el-button
>
</el-row>
</div>
<div class="simple-process-model" :style="`transform: scale(${scaleValue / 100});`">
@ -42,7 +33,8 @@
import ProcessNodeTree from './ProcessNodeTree.vue'
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from './consts'
import { useWatchNode } from './node'
import { Select, ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue'
import { ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue'
defineOptions({
name: 'SimpleProcessModel'
})
@ -58,6 +50,7 @@ const props = defineProps({
default: true
}
})
const emits = defineEmits<{
'save': [node: SimpleFlowNode | undefined]
}>()
@ -68,6 +61,7 @@ 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) {
@ -75,6 +69,7 @@ const zoomIn = () => {
}
scaleValue.value += 10
}
//
const zoomOut = () => {
if (scaleValue.value == MIN_SCALE_VALUE) {
@ -82,21 +77,14 @@ const zoomOut = () => {
}
scaleValue.value -= 10
}
const processReZoom = () => {
scaleValue.value = 100
}
const errorDialogVisible = ref(false)
let errorNodes: SimpleFlowNode[] = []
const saveSimpleFlowModel = async () => {
errorNodes = []
validateNode(processNodeTree.value, errorNodes)
if (errorNodes.length > 0) {
errorDialogVisible.value = true
return
}
emits('save', processNodeTree.value)
}
// showText
const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => {
if (node) {
@ -135,6 +123,26 @@ const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNo
}
}
}
/** 获取当前流程数据 */
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>

View File

@ -23,6 +23,11 @@ export enum NodeType {
*/
COPY_TASK_NODE = 12,
/**
*
*/
DELAY_TIMER_NODE = 14,
/**
*
*/
@ -98,6 +103,8 @@ export interface SimpleFlowNode {
defaultFlow?: boolean
// 活动的状态,用于前端节点状态展示
activityStatus?: TaskStatusEnum
// 延迟设置
delaySetting?: DelaySetting
}
// 候选人策略枚举 用于审批节点。抄送节点 )
export enum CandidateStrategy {
@ -413,12 +420,14 @@ NODE_DEFAULT_TEXT.set(NodeType.USER_TASK_NODE, '请配置审批人')
NODE_DEFAULT_TEXT.set(NodeType.COPY_TASK_NODE, '请配置抄送人')
NODE_DEFAULT_TEXT.set(NodeType.CONDITION_NODE, '请设置条件')
NODE_DEFAULT_TEXT.set(NodeType.START_USER_NODE, '请设置发起人')
NODE_DEFAULT_TEXT.set(NodeType.DELAY_TIMER_NODE, '请设置延迟器')
export const NODE_DEFAULT_NAME = new Map<number, string>()
NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人')
NODE_DEFAULT_NAME.set(NodeType.COPY_TASK_NODE, '抄送人')
NODE_DEFAULT_NAME.set(NodeType.CONDITION_NODE, '条件')
NODE_DEFAULT_NAME.set(NodeType.START_USER_NODE, '发起人')
NODE_DEFAULT_NAME.set(NodeType.DELAY_TIMER_NODE, '延迟器')
// 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序
export const CANDIDATE_STRATEGY: DictDataVO[] = [
@ -568,3 +577,30 @@ export enum ProcessVariableEnum {
*/
START_USER_ID = 'PROCESS_START_USER_ID'
}
/**
*
*/
export type DelaySetting = {
// 延迟类型
delayType: number
// 延迟时间表达式
delayTime: string
}
/**
*
*/
export enum DelayTypeEnum {
/**
*
*/
FIXED_TIME_DURATION = 1,
/**
*
*/
FIXED_DATE_TIME = 2
}
export const DELAY_TYPE = [
{ label: '固定时长', value: DelayTypeEnum.FIXED_TIME_DURATION },
{ label: '固定日期', value: DelayTypeEnum.FIXED_DATE_TIME }
]

View File

@ -14,8 +14,7 @@ import {
NODE_DEFAULT_NAME,
AssignStartUserHandlerType,
AssignEmptyHandlerType,
FieldPermissionType,
ProcessVariableEnum
FieldPermissionType
} from './consts'
import { parseFormFields } from '@/components/FormCreate/src/utils/index'
export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> {
@ -37,13 +36,6 @@ const parseFormCreateFields = (formFields?: string[]) => {
parseFormFields(JSON.parse(fieldStr), result)
})
}
// 固定添加发起人 ID 字段
result.unshift({
field: ProcessVariableEnum.START_USER_ID,
title: '发起人',
type: 'UserSelect',
required: true
})
return result
}
@ -60,9 +52,33 @@ 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[]) => {
let defaultFieldsPermission: Array<Record<string, any>> = []

View File

@ -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>

View File

@ -0,0 +1,189 @@
<template>
<el-drawer
:append-to-body="true"
v-model="settingVisible"
:show-close="false"
:size="550"
:before-close="saveConfig"
>
<template #header>
<div class="config-header">
<input
v-if="showInput"
type="text"
class="config-editable-input"
@blur="blurEvent()"
v-mountedFocus
v-model="nodeName"
:placeholder="nodeName"
/>
<div v-else class="node-name">
{{ nodeName }} <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
</div>
<div class="divide-line"></div>
</div>
</template>
<div>
<el-form ref="formRef" :model="configForm" label-position="top" :rules="formRules">
<el-form-item label="延迟时间" prop="delayType">
<el-radio-group v-model="configForm.delayType">
<el-radio-button
v-for="item in DELAY_TYPE"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-radio-group>
</el-form-item>
<el-form-item v-if="configForm.delayType === DelayTypeEnum.FIXED_TIME_DURATION">
<el-form-item prop="timeDuration">
<el-input-number
class="mr-2"
:style="{ width: '100px' }"
v-model="configForm.timeDuration"
:min="1"
controls-position="right"
/>
</el-form-item>
<el-select v-model="configForm.timeUnit" class="mr-2" :style="{ width: '100px' }">
<el-option
v-for="item in TIME_UNIT_TYPES"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-text>后进入下一节点</el-text>
</el-form-item>
<el-form-item v-if="configForm.delayType === DelayTypeEnum.FIXED_DATE_TIME" prop="dateTime">
<el-date-picker
class="mr-2"
v-model="configForm.dateTime"
type="datetime"
placeholder="请选择日期和时间"
value-format="YYYY-MM-DDTHH:mm:ss"
/>
<el-text>后进入下一节点</el-text>
</el-form-item>
</el-form>
</div>
<template #footer>
<el-divider />
<div>
<el-button type="primary" @click="saveConfig"> </el-button>
<el-button @click="closeDrawer"> </el-button>
</div>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import {
SimpleFlowNode,
NodeType,
TIME_UNIT_TYPES,
TimeUnitType,
DelayTypeEnum,
DELAY_TYPE
} from '../consts'
import { useWatchNode, useDrawer, useNodeName } from '../node'
import { convertTimeUnit } from '../utils'
defineOptions({
name: 'DelayTimerNodeConfig'
})
const props = defineProps({
flowNode: {
type: Object as () => SimpleFlowNode,
required: true
}
})
//
const { settingVisible, closeDrawer, openDrawer } = useDrawer()
//
const currentNode = useWatchNode(props)
//
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.DELAY_TIMER_NODE)
//
const formRef = ref() // Ref
//
const formRules = reactive({
delayType: [{ required: true, message: '延迟时间不能为空', trigger: 'change' }],
timeDuration: [{ required: true, message: '延迟时间不能为空', trigger: 'change' }],
dateTime: [{ required: true, message: '延迟时间不能为空', trigger: 'change' }]
})
//
const configForm = ref({
delayType: DelayTypeEnum.FIXED_TIME_DURATION,
timeDuration: 1,
timeUnit: TimeUnitType.HOUR,
dateTime: ''
})
//
const saveConfig = async () => {
if (!formRef) return false
const valid = await formRef.value.validate()
if (!valid) return false
const showText = getShowText()
if (!showText) return false
currentNode.value.showText = showText
if (configForm.value.delayType === DelayTypeEnum.FIXED_TIME_DURATION) {
currentNode.value.delaySetting = {
delayType: configForm.value.delayType,
delayTime: getIsoTimeDuration()
}
}
if (configForm.value.delayType === DelayTypeEnum.FIXED_DATE_TIME) {
currentNode.value.delaySetting = {
delayType: configForm.value.delayType,
delayTime: configForm.value.dateTime
}
}
settingVisible.value = false
return true
}
const getShowText = (): string => {
let showText = ''
if (configForm.value.delayType === DelayTypeEnum.FIXED_TIME_DURATION) {
showText = `延迟${configForm.value.timeDuration}${TIME_UNIT_TYPES.find((item) => item.value === configForm.value.timeUnit).label}`
}
if (configForm.value.delayType === DelayTypeEnum.FIXED_DATE_TIME) {
showText = `延迟至${configForm.value.dateTime.replace('T', ' ')}`
}
return showText
}
const getIsoTimeDuration = () => {
let strTimeDuration = 'PT'
if (configForm.value.timeUnit === TimeUnitType.MINUTE) {
strTimeDuration += configForm.value.timeDuration + 'M'
}
if (configForm.value.timeUnit === TimeUnitType.HOUR) {
strTimeDuration += configForm.value.timeDuration + 'H'
}
if (configForm.value.timeUnit === TimeUnitType.DAY) {
strTimeDuration += configForm.value.timeDuration + 'D'
}
return strTimeDuration
}
//
const showDelayTimerNodeConfig = (node: SimpleFlowNode) => {
nodeName.value = node.name
if (node.delaySetting) {
configForm.value.delayType = node.delaySetting.delayType
//
if (configForm.value.delayType === DelayTypeEnum.FIXED_TIME_DURATION) {
const strTimeDuration = node.delaySetting.delayTime
let parseTime = strTimeDuration.slice(2, strTimeDuration.length - 1)
let parseTimeUnit = strTimeDuration.slice(strTimeDuration.length - 1)
configForm.value.timeDuration = parseInt(parseTime)
configForm.value.timeUnit = convertTimeUnit(parseTimeUnit)
}
//
if (configForm.value.delayType === DelayTypeEnum.FIXED_DATE_TIME) {
configForm.value.dateTime = node.delaySetting.delayTime
}
}
}
defineExpose({ openDrawer, showDelayTimerNodeConfig }) //
</script>
<style lang="scss" scoped></style>

View File

@ -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,12 +125,23 @@ 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

View File

@ -469,7 +469,8 @@ import {
TimeoutHandlerType,
ASSIGN_EMPTY_HANDLER_TYPES,
AssignEmptyHandlerType,
FieldPermissionType
FieldPermissionType,
ProcessVariableEnum
} from '../consts'
import {
@ -519,6 +520,13 @@ const { formType, fieldsPermissionConfig, formFieldOptions, getNodeConfigFormFie
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')
})
// ,

View File

@ -0,0 +1,98 @@
<template>
<div class="node-wrapper">
<div class="node-container">
<div
class="node-box"
:class="[
{ 'node-config-error': !currentNode.showText },
`${useTaskStatusClass(currentNode?.activityStatus)}`
]"
>
<div class="node-title-container">
<!-- TODO @芋艿 需要更换图标 -->
<div class="node-title-icon copy-task"><span class="iconfont icon-copy"></span></div>
<input
v-if="!readonly && showInput"
type="text"
class="editable-title-input"
@blur="blurEvent()"
v-mountedFocus
v-model="currentNode.name"
:placeholder="currentNode.name"
/>
<div v-else class="node-title" @click="clickTitle">
{{ currentNode.name }}
</div>
</div>
<div class="node-content" @click="openNodeConfig">
<div class="node-text" :title="currentNode.showText" v-if="currentNode.showText">
{{ currentNode.showText }}
</div>
<div class="node-text" v-else>
{{ NODE_DEFAULT_TEXT.get(NodeType.DELAY_TIMER_NODE) }}
</div>
<Icon v-if="!readonly" icon="ep:arrow-right-bold" />
</div>
<div v-if="!readonly" class="node-toolbar">
<div class="toolbar-icon"
><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
/></div>
</div>
</div>
<!-- 传递子节点给添加节点组件会在子节点前面添加节点 -->
<NodeHandler
v-if="currentNode"
v-model:child-node="currentNode.childNode"
:current-node="currentNode"
/>
</div>
<DelayTimerNodeConfig
v-if="!readonly && currentNode"
ref="nodeSetting"
:flow-node="currentNode"
/>
</div>
</template>
<script setup lang="ts">
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
import NodeHandler from '../NodeHandler.vue'
import { useNodeName2, useWatchNode, useTaskStatusClass } from '../node'
import DelayTimerNodeConfig from '../nodes-config/DelayTimerNodeConfig.vue'
defineOptions({
name: 'DelayTimerNode'
})
const props = defineProps({
flowNode: {
type: Object as () => SimpleFlowNode,
required: true
}
})
//
const emits = defineEmits<{
'update:flowNode': [node: SimpleFlowNode | undefined]
}>()
//
const readonly = inject<Boolean>('readonly')
//
const currentNode = useWatchNode(props)
//
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.DELAY_TIMER_NODE)
const nodeSetting = ref()
//
const openNodeConfig = () => {
if (readonly) {
return
}
nodeSetting.value.showDelayTimerNodeConfig(currentNode.value)
nodeSetting.value.openDrawer()
}
//
const deleteNode = () => {
emits('update:flowNode', currentNode.value.childNode)
}
</script>
<style lang="scss" scoped></style>

View File

@ -173,13 +173,16 @@
height: 100%;
padding-top: 32px;
background-color: #fafafa;
overflow-x: auto;
width: 100%;
.simple-process-model {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
transform-origin: 50% 0 0;
overflow: auto;
min-width: fit-content;
transform: scale(1);
transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
background: url(@/assets/svgs/bpm/simple-process-bg.svg) 0 0 repeat;
@ -473,6 +476,7 @@
.branch-node-container {
position: relative;
display: flex;
min-width: fit-content;
&::before {
position: absolute;
@ -548,6 +552,7 @@
background: transparent;
border-top: 2px solid #dedede;
border-bottom: 2px solid #dedede;
flex-shrink: 0;
&::before {
position: absolute;

View File

@ -39,7 +39,7 @@
</Dialog>
</template>
<script lang="ts" setup>
import { defaultProps, findTreeNode, handleTree } from '@/utils/tree'
import { defaultProps, handleTree } from '@/utils/tree'
import * as DeptApi from '@/api/system/dept'
import * as UserApi from '@/api/system/user'
@ -50,6 +50,7 @@ const emit = defineEmits<{
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([]) //
@ -79,7 +80,9 @@ const open = async (id: number, selectedList?: any[]) => {
resetForm()
//
deptTree.value = handleTree(await DeptApi.getSimpleDeptList())
const deptData = await DeptApi.getSimpleDeptList()
deptList.value = deptData //
deptTree.value = handleTree(deptData) //
userList.value = await UserApi.getSimpleUserList()
//
@ -88,16 +91,31 @@ const open = async (id: number, selectedList?: any[]) => {
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 getUserList = async (deptId?: number) => {
const filterUserList = async (deptId?: number) => {
formLoading.value = true
try {
// @ts-ignore
// TODO @ simple List deptId
// TODO @Zqqq使 deptList deptId userList
const data = await UserApi.getUserPage({ pageSize: 100, pageNo: 1, deptId })
//
filteredUserList.value = data.list
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
}
@ -121,6 +139,7 @@ const submitForm = async () => {
/** 重置表单 */
const resetForm = () => {
deptTree.value = []
deptList.value = []
userList.value = []
filteredUserList.value = []
selectedUserIdList.value = []
@ -128,7 +147,7 @@ const resetForm = () => {
/** 处理部门被点击 */
const handleNodeClick = (row: { [key: string]: any }) => {
getUserList(row.id)
filterUserList(row.id)
}
defineExpose({ open }) // open

View File

@ -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 }
)
// processIdprocessName
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 || '&nbsp;'
// }
onBeforeMount(() => {
console.log(props, 'propspropspropsprops')
})
onMounted(() => {
initBpmnModeler()
createNewDiagram(props.value)
})
onBeforeUnmount(() => {
// this.$once('hook:beforeDestroy', () => {
// })
if (bpmnModeler) bpmnModeler.destroy()
emit('destroy', bpmnModeler)
bpmnModeler = null

View File

@ -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"
}
]
},
@ -1281,6 +1306,138 @@
"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": []

View File

@ -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',

View File

@ -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',

View File

@ -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': '创建数据存储',

View File

@ -1,6 +1,6 @@
<template>
<div class="process-panel__container" :style="{ width: `${width}px`,maxHeight: '700px' }">
<el-collapse v-model="activeTab">
<div class="process-panel__container" :style="{ width: `${width}px` }">
<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,9 +60,13 @@
<template #title><Icon icon="ep:promotion" />其他</template>
<element-other-config :id="elementId" />
</el-collapse-item>
<el-collapse-item name="customConfig" v-if="elementType.indexOf('Task') !== -1" key="customConfig">
<template #title><Icon icon="ep:circle-plus-filled" />自定义配置</template>
<element-custom-config :id="elementId" :type="elementType" />
<el-collapse-item 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>
@ -72,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' })
@ -108,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'),
@ -137,9 +140,45 @@ const unwatchBpmn = watch(
selection: props.bpmnModeler.get('selection')
}
console.log(bpmnInstances(), 'window.bpmnInstances')
//
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()
unwatchBpmn()
} else {
console.error('modeler 实例未完全初始化')
}
} catch (error) {
console.error('初始化失败:', error)
}
},
{
immediate: true
@ -147,6 +186,8 @@ const unwatchBpmn = watch(
)
const getActiveElement = () => {
if (!isReady.value || !props.bpmnModeler) return
// bpmn:Process
initFormOnChanged(null)
props.bpmnModeler.on('import.done', (e) => {
@ -164,8 +205,11 @@ const getActiveElement = () => {
}
})
}
//
const initFormOnChanged = (element) => {
if (!isReady.value || !bpmnInstances()) return
let activatedElement = element
if (!activatedElement) {
activatedElement =
@ -173,6 +217,8 @@ const initFormOnChanged = (element) => {
bpmnInstances().elementRegistry.find((el) => el.type === 'bpmn:Collaboration')
}
if (!activatedElement) return
try {
console.log(`
----------
select element changed:
@ -192,13 +238,15 @@ const initFormOnChanged = (element) => {
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(

View File

@ -1,283 +1,39 @@
<!-- UserTask 自定义配置
1. 审批人与提交人为同一人时
2. 审批人拒绝时
3. 审批人为空时
-->
<template>
<div class="panel-tab__content">
<el-divider content-position="left">审批人拒绝时</el-divider>
<el-form-item prop="rejectHandlerType">
<el-radio-group
v-model="rejectHandlerType"
:disabled="returnTaskList.length === 0"
@change="updateRejectHandlerType"
>
<div class="flex-col">
<div v-for="(item, index) in REJECT_HANDLER_TYPES" :key="index">
<el-radio :key="item.value" :value="item.value" :label="item.label" />
</div>
</div>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="rejectHandlerType == RejectHandlerType.RETURN_USER_TASK"
label="驳回节点"
prop="returnNodeId"
>
<el-select v-model="returnNodeId" clearable style="width: 100%" @change="updateReturnNodeId">
<el-option
v-for="item in returnTaskList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-divider content-position="left">审批人为空时</el-divider>
<el-form-item prop="assignEmptyHandlerType">
<el-radio-group v-model="assignEmptyHandlerType" @change="updateAssignEmptyHandlerType">
<div class="flex-col">
<div v-for="(item, index) in ASSIGN_EMPTY_HANDLER_TYPES" :key="index">
<el-radio :key="item.value" :value="item.value" :label="item.label" />
</div>
</div>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="assignEmptyHandlerType == AssignEmptyHandlerType.ASSIGN_USER"
label="指定用户"
prop="assignEmptyHandlerUserIds"
span="24"
>
<el-select
v-model="assignEmptyUserIds"
clearable
multiple
style="width: 100%"
@change="updateAssignEmptyUserIds"
>
<el-option
v-for="item in userOptions"
:key="item.id"
:label="item.nickname"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-divider content-position="left">审批人与提交人为同一人时</el-divider>
<el-radio-group v-model="assignStartUserHandlerType" @change="updateAssignStartUserHandlerType">
<div class="flex-col">
<div v-for="(item, index) in ASSIGN_START_USER_HANDLER_TYPES" :key="index">
<el-radio :key="item.value" :value="item.value" :label="item.label" />
</div>
</div>
</el-radio-group>
<component :is="customConfigComponent" v-bind="$props" />
</div>
</template>
<script lang="ts" setup>
import {
ASSIGN_START_USER_HANDLER_TYPES,
RejectHandlerType,
REJECT_HANDLER_TYPES,
ASSIGN_EMPTY_HANDLER_TYPES,
AssignEmptyHandlerType
} from '@/components/SimpleProcessDesignerV2/src/consts'
import * as UserApi from '@/api/system/user'
import { CustomConfigMap } from './data'
defineOptions({ name: 'ElementCustomConfig' })
const props = defineProps({
id: String,
type: String
type: String,
businessObject: {
type: Object,
default: () => {}
}
})
const prefix = inject('prefix')
//
const assignStartUserHandlerTypeEl = ref()
const assignStartUserHandlerType = ref()
//
const rejectHandlerTypeEl = ref()
const rejectHandlerType = ref()
const returnNodeIdEl = ref()
const returnNodeId = ref()
const returnTaskList = ref([])
//
const assignEmptyHandlerTypeEl = ref()
const assignEmptyHandlerType = ref()
const assignEmptyUserIdsEl = ref()
const assignEmptyUserIds = ref()
const elExtensionElements = ref()
const otherExtensions = ref()
const bpmnElement = ref()
const bpmnInstances = () => (window as any)?.bpmnInstances
const resetCustomConfigList = () => {
bpmnElement.value = bpmnInstances().bpmnElement
// 退
returnTaskList.value = findAllPredecessorsExcludingStart(
bpmnElement.value.id,
bpmnInstances().modeler
)
//
elExtensionElements.value =
bpmnElement.value.businessObject?.extensionElements ??
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
//
assignStartUserHandlerTypeEl.value =
elExtensionElements.value.values?.filter(
(ex) => ex.$type === `${prefix}:AssignStartUserHandlerType`
)?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignStartUserHandlerType`, { value: 1 })
assignStartUserHandlerType.value = assignStartUserHandlerTypeEl.value.value
//
rejectHandlerTypeEl.value =
elExtensionElements.value.values?.filter(
(ex) => ex.$type === `${prefix}:RejectHandlerType`
)?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectHandlerType`, { value: 1 })
rejectHandlerType.value = rejectHandlerTypeEl.value.value
returnNodeIdEl.value =
elExtensionElements.value.values?.filter(
(ex) => ex.$type === `${prefix}:RejectReturnTaskId`
)?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectReturnTaskId`, { value: '' })
returnNodeId.value = returnNodeIdEl.value.value
//
assignEmptyHandlerTypeEl.value =
elExtensionElements.value.values?.filter(
(ex) => ex.$type === `${prefix}:AssignEmptyHandlerType`
)?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyHandlerType`, { value: 1 })
assignEmptyHandlerType.value = assignEmptyHandlerTypeEl.value.value
assignEmptyUserIdsEl.value =
elExtensionElements.value.values?.filter(
(ex) => ex.$type === `${prefix}:AssignEmptyUserIds`
)?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyUserIds`, { value: '' })
assignEmptyUserIds.value = assignEmptyUserIdsEl.value.value.split(',').map((item) => {
//
let num = Number(item)
return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER ? item : num
})
// 便
otherExtensions.value =
elExtensionElements.value.values?.filter(
(ex) =>
ex.$type !== `${prefix}:AssignStartUserHandlerType` &&
ex.$type !== `${prefix}:RejectHandlerType` &&
ex.$type !== `${prefix}:RejectReturnTaskId` &&
ex.$type !== `${prefix}:AssignEmptyHandlerType` &&
ex.$type !== `${prefix}:AssignEmptyUserIds`
) ?? []
//
updateElementExtensions()
}
const updateAssignStartUserHandlerType = () => {
assignStartUserHandlerTypeEl.value.value = assignStartUserHandlerType.value
updateElementExtensions()
}
const updateRejectHandlerType = () => {
rejectHandlerTypeEl.value.value = rejectHandlerType.value
returnNodeId.value = returnTaskList.value[0].id
returnNodeIdEl.value.value = returnNodeId.value
updateElementExtensions()
}
const updateReturnNodeId = () => {
returnNodeIdEl.value.value = returnNodeId.value
updateElementExtensions()
}
const updateAssignEmptyHandlerType = () => {
assignEmptyHandlerTypeEl.value.value = assignEmptyHandlerType.value
updateElementExtensions()
}
const updateAssignEmptyUserIds = () => {
assignEmptyUserIdsEl.value.value = assignEmptyUserIds.value.toString()
updateElementExtensions()
}
const updateElementExtensions = () => {
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
values: [
...otherExtensions.value,
assignStartUserHandlerTypeEl.value,
rejectHandlerTypeEl.value,
returnNodeIdEl.value,
assignEmptyHandlerTypeEl.value,
assignEmptyUserIdsEl.value
]
})
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
extensionElements: extensions
})
}
const customConfigComponent = ref<any>(null)
watch(
() => props.id,
(val) => {
val &&
val.length &&
nextTick(() => {
resetCustomConfigList()
})
() => 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 }
)
function findAllPredecessorsExcludingStart(elementId, modeler) {
const elementRegistry = modeler.get('elementRegistry')
const allConnections = elementRegistry.filter((element) => element.type === 'bpmn:SequenceFlow')
const predecessors = new Set() // 使 Set
//
function isStartEvent(element) {
return element.type === 'bpmn:StartEvent'
}
function findPredecessorsRecursively(element) {
//
const incomingConnections = allConnections.filter((connection) => connection.target === element)
incomingConnections.forEach((connection) => {
const source = connection.source //
//
if (!isStartEvent(source)) {
predecessors.add(source.businessObject)
//
findPredecessorsRecursively(source)
}
})
}
const targetElement = elementRegistry.get(elementId)
if (targetElement) {
findPredecessorsRecursively(targetElement)
}
return Array.from(predecessors) //
}
const userOptions = ref<UserApi.UserVO[]>([]) //
onMounted(async () => {
//
userOptions.value = await UserApi.getSimpleUserList()
})
</script>
<style lang="scss" scoped></style>

View File

@ -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>

View File

@ -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 }} &nbsp;<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>

View File

@ -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
}
}

View File

@ -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 = () => {
// ApproveMethodmultiInstanceLoopCharacteristics
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)
})
}
// ApproveMethodExtensionElements
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) => {
if (val) {
nextTick(() => {
bpmnElement.value = bpmnInstances().bpmnElement
getElementLoop(val)
// getElementLoop(val)
getElementLoopNew()
})
}
},
{ immediate: true }
)

View File

@ -75,7 +75,6 @@ const attributeFormRef = ref()
const bpmnInstances = () => (window as any)?.bpmnInstances
const resetAttributesList = () => {
console.log(window, 'windowwindowwindowwindowwindowwindowwindow')
bpmnElement.value = bpmnInstances().bpmnElement
otherExtensionList.value = [] //
bpmnElementProperties.value =
@ -85,7 +84,7 @@ const resetAttributesList = () => {
otherExtensionList.value.push(ex)
}
return ex.$type === `${prefix}:Properties`
}) ?? []
}) ?? [];
//
bpmnElementPropertyList.value = bpmnElementProperties.value.reduce(

View File

@ -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 }

View File

@ -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]
}

View File

@ -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>

View File

@ -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>

View File

@ -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,
() => {

View File

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

View File

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

View File

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

View File

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

View File

@ -330,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'
}
}
]
},

View File

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

View File

@ -376,6 +376,9 @@ export const treeToString = (tree: any[], nodeId) => {
let str = ''
function performAThoroughValidation(arr) {
if (typeof arr === 'undefined' || !Array.isArray(arr) || arr.length === 0) {
return false
}
for (const item of arr) {
if (item.id === nodeId) {
str += ` / ${item.name}`

View File

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

View File

@ -133,6 +133,7 @@
</el-form-item>
</el-col>
<Verify
v-if="loginData.captchaEnable === 'true'"
ref="verify"
:captchaType="captchaType"
:imgSize="{ width: '400px', height: '200px' }"

View File

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

View File

@ -59,7 +59,13 @@
</el-checkbox>
</el-col>
<el-col :offset="6" :span="12">
<el-link style="float: right" type="primary">{{ t('login.forgetPassword') }}</el-link>
<el-link
style="float: right"
type="primary"
@click="setLoginState(LoginStateEnum.RESET_PASSWORD)"
>
{{ t('login.forgetPassword') }}
</el-link>
</el-col>
</el-row>
</el-form-item>
@ -76,6 +82,7 @@
</el-form-item>
</el-col>
<Verify
v-if="loginData.captchaEnable === 'true'"
ref="verify"
:captchaType="captchaType"
:imgSize="{ width: '400px', height: '200px' }"
@ -241,7 +248,7 @@ const getTenantByWebsite = async () => {
}
const loading = ref() // ElLoading.service
//
const handleLogin = async (params) => {
const handleLogin = async (params: any) => {
loginLoading.value = true
try {
await getTenantId()
@ -273,7 +280,7 @@ const handleLogin = async (params) => {
if (redirect.value.indexOf('sso') !== -1) {
window.location.href = window.location.href.replace('/login?redirect=', '')
} else {
push({ path: redirect.value || permissionStore.addRouters[0].path })
await push({ path: redirect.value || permissionStore.addRouters[0].path })
}
} finally {
loginLoading.value = false
@ -313,8 +320,7 @@ const doSocialLogin = async (type: number) => {
encodeURIComponent(`type=${type}&redirect=${redirect.value || '/'}`)
//
const res = await LoginApi.socialAuthRedirect(type, encodeURIComponent(redirectUri))
window.location.href = res
window.location.href = await LoginApi.socialAuthRedirect(type, encodeURIComponent(redirectUri))
}
}
watch(

View File

@ -85,6 +85,7 @@
</el-form-item>
</el-col>
<Verify
v-if="registerData.captchaEnable === 'true'"
ref="verify"
:captchaType="captchaType"
:imgSize="{ width: '400px', height: '200px' }"

View File

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

View File

@ -64,6 +64,7 @@
</div>
</div>
</div>
<!-- 模型列表 -->
<el-collapse-transition>
<div v-show="isExpand">
@ -90,7 +91,7 @@
</div>
</template>
</el-table-column>
<el-table-column label="可见范围" prop="startUserIds" min-width="100">
<el-table-column label="可见范围" prop="startUserIds" min-width="150">
<template #default="scope">
<el-text v-if="!scope.row.startUsers || scope.row.startUsers.length === 0">
全部可见
@ -110,7 +111,7 @@
</el-text>
</template>
</el-table-column>
<el-table-column label="表单信息" prop="formType" min-width="200">
<el-table-column label="表单信息" prop="formType" min-width="150">
<template #default="scope">
<el-button
v-if="scope.row.formType === BpmModelFormType.NORMAL"
@ -162,16 +163,6 @@
>
修改
</el-button>
<el-button
link
class="!ml-5px"
type="primary"
@click="handleDesign(scope.row)"
v-hasPermi="['bpm:model:update']"
:disabled="!isManagerUser(scope.row)"
>
设计
</el-button>
<el-button
link
class="!ml-5px"
@ -249,7 +240,7 @@ import { formatDate } from '@/utils/formatTime'
import * as ModelApi from '@/api/bpm/model'
import * as FormApi from '@/api/bpm/form'
import { setConfAndFields2 } from '@/utils/formCreate'
import { BpmModelFormType, BpmModelType } from '@/utils/constants'
import { BpmModelFormType } from '@/utils/constants'
import { checkPermi } from '@/utils/permission'
import { useUserStoreWithOut } from '@/store/modules/user'
import { useAppStore } from '@/store/modules/app'
@ -337,25 +328,6 @@ const handleChangeState = async (row: any) => {
} catch {}
}
/** 设计流程 */
const handleDesign = (row: any) => {
if (row.type == BpmModelType.BPMN) {
push({
name: 'BpmModelEditor',
query: {
modelId: row.id
}
})
} else {
push({
name: 'SimpleModelDesign',
query: {
modelId: row.id
}
})
}
}
/** 发布流程 */
const handleDeploy = async (row: any) => {
try {
@ -496,7 +468,14 @@ const handleDeleteCategory = async () => {
/** 添加流程模型弹窗 */
const modelFormRef = ref()
const openModelForm = (type: string, id?: number) => {
modelFormRef.value.open(type, id)
if (type === 'create') {
push({ name: 'BpmModelCreate' })
} else {
push({
name: 'BpmModelUpdate',
params: { id }
})
}
}
watch(() => props.categoryInfo.modelList, updateModeList, { immediate: true })

View File

@ -123,29 +123,69 @@
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="谁可以发起" prop="startUserIds">
<el-form-item label="谁可以发起" prop="startUserType">
<el-select
v-model="formData.startUserIds"
multiple
placeholder="请选择可发起人,默认(不选择)则所有人都可以发起"
v-model="formData.startUserType"
placeholder="请选择谁可以发起"
@change="handleStartUserTypeChange"
>
<el-option
v-for="user in userList"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
<el-option label="全员" :value="0" />
<el-option label="指定人员" :value="1" />
<el-option label="均不可提交" :value="2" />
</el-select>
<div v-if="formData.startUserType === 1" class="mt-2 flex flex-wrap gap-2">
<div
v-for="user in selectedStartUsers"
:key="user.id"
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
>
<el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
<el-avatar class="!m-5px" :size="28" v-else>
{{ user.nickname.substring(0, 1) }}
</el-avatar>
{{ user.nickname }}
<Icon
icon="ep:close"
class="ml-2 cursor-pointer hover:text-red-500"
@click="handleRemoveStartUser(user)"
/>
</div>
<el-button type="primary" link @click="openStartUserSelect">
<Icon icon="ep:plus" />选择人员
</el-button>
</div>
</el-form-item>
<el-form-item label="流程管理员" prop="managerUserIds">
<el-select v-model="formData.managerUserIds" multiple placeholder="请选择流程管理员">
<el-option
v-for="user in userList"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
<el-form-item label="流程管理员" prop="managerUserType">
<el-select
v-model="formData.managerUserType"
placeholder="请选择流程管理员"
@change="handleManagerUserTypeChange"
>
<el-option label="全员" :value="0" />
<el-option label="指定人员" :value="1" />
<el-option label="均不可提交" :value="2" />
</el-select>
<div v-if="formData.managerUserType === 1" class="mt-2 flex flex-wrap gap-2">
<div
v-for="user in selectedManagerUsers"
:key="user.id"
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
>
<el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
<el-avatar class="!m-5px" :size="28" v-else>
{{ user.nickname.substring(0, 1) }}
</el-avatar>
{{ user.nickname }}
<Icon
icon="ep:close"
class="ml-2 cursor-pointer hover:text-red-500"
@click="handleRemoveManagerUser(user)"
/>
</div>
<el-button type="primary" link @click="openManagerUserSelect">
<Icon icon="ep:plus" />选择人员
</el-button>
</div>
</el-form-item>
</el-form>
<template #footer>
@ -153,6 +193,7 @@
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
<UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
</template>
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes'
@ -160,11 +201,12 @@ import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
import { ElMessageBox } from 'element-plus'
import * as ModelApi from '@/api/bpm/model'
import * as FormApi from '@/api/bpm/form'
import { CategoryApi } from '@/api/bpm/category'
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import { BpmModelFormType, BpmModelType } from '@/utils/constants'
import { UserVO } from '@/api/system/user'
import * as UserApi from '@/api/system/user'
import { useUserStoreWithOut } from '@/store/modules/user'
import { FormVO } from '@/api/bpm/form'
defineOptions({ name: 'ModelForm' })
@ -178,7 +220,7 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
const formData: any = ref({
id: undefined,
name: '',
key: '',
@ -191,6 +233,8 @@ const formData = ref({
formCustomCreatePath: '',
formCustomViewPath: '',
visible: true,
startUserType: undefined,
managerUserType: undefined,
startUserIds: [],
managerUserIds: []
})
@ -208,9 +252,13 @@ const formRules = reactive({
managerUserIds: [{ required: true, message: '流程管理员不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
const formList = ref([]) //
const categoryList = ref([]) //
const formList = ref<FormVO[]>([]) //
const categoryList = ref<CategoryVO[]>([]) //
const userList = ref<UserVO[]>([]) //
const selectedStartUsers = ref<UserVO[]>([]) //
const selectedManagerUsers = ref<UserVO[]>([]) //
const userSelectFormRef = ref() // ref
const currentSelectType = ref<'start' | 'manager'>('start') //
/** 打开弹窗 */
const open = async (type: string, id?: string) => {
@ -226,6 +274,19 @@ const open = async (type: string, id?: string) => {
} finally {
formLoading.value = false
}
// ID
if (formData.value.startUserIds?.length) {
formData.value.startUserType = 1
selectedStartUsers.value = userList.value.filter((user) =>
formData.value.startUserIds.includes(user.id)
)
}
if (formData.value.managerUserIds?.length) {
formData.value.managerUserType = 1
selectedManagerUsers.value = userList.value.filter((user) =>
formData.value.managerUserIds.includes(user.id)
)
}
} else {
formData.value.managerUserIds.push(userStore.getUser.id)
}
@ -293,9 +354,87 @@ const resetForm = () => {
formCustomCreatePath: '',
formCustomViewPath: '',
visible: true,
startUserType: undefined,
managerUserType: undefined,
startUserIds: [],
managerUserIds: []
}
formRef.value?.resetFields()
selectedStartUsers.value = []
selectedManagerUsers.value = []
}
/** 处理发起人类型变化 */
const handleStartUserTypeChange = (value: number) => {
if (value !== 1) {
selectedStartUsers.value = []
formData.value.startUserIds = []
}
}
/** 处理管理员类型变化 */
const handleManagerUserTypeChange = (value: number) => {
if (value !== 1) {
selectedManagerUsers.value = []
formData.value.managerUserIds = []
}
}
/** 打开发起人选择 */
const openStartUserSelect = () => {
currentSelectType.value = 'start'
userSelectFormRef.value.open(0, selectedStartUsers.value)
}
/** 打开管理员选择 */
const openManagerUserSelect = () => {
currentSelectType.value = 'manager'
userSelectFormRef.value.open(0, selectedManagerUsers.value)
}
/** 处理用户选择确认 */
const handleUserSelectConfirm = (_, users: UserVO[]) => {
if (currentSelectType.value === 'start') {
selectedStartUsers.value = users
formData.value.startUserIds = users.map((u) => u.id)
} else {
selectedManagerUsers.value = users
formData.value.managerUserIds = users.map((u) => u.id)
}
}
/** 移除发起人 */
const handleRemoveStartUser = (user: UserVO) => {
selectedStartUsers.value = selectedStartUsers.value.filter((u) => u.id !== user.id)
formData.value.startUserIds = formData.value.startUserIds.filter((id: number) => id !== user.id)
}
/** 移除管理员 */
const handleRemoveManagerUser = (user: UserVO) => {
selectedManagerUsers.value = selectedManagerUsers.value.filter((u) => u.id !== user.id)
formData.value.managerUserIds = formData.value.managerUserIds.filter(
(id: number) => id !== user.id
)
}
</script>
<style lang="scss" scoped>
.bg-gray-100 {
background-color: #f5f7fa;
transition: all 0.3s;
&:hover {
background-color: #e6e8eb;
}
.ep-close {
font-size: 14px;
color: #909399;
transition: color 0.3s;
&:hover {
color: #f56c6c;
}
}
}
</style>

View File

@ -3,7 +3,6 @@
<!-- 流程设计器负责绘制流程等 -->
<MyProcessDesigner
key="designer"
v-if="xmlString !== undefined"
v-model="xmlString"
:value="xmlString"
v-bind="controlForm"
@ -11,12 +10,14 @@
ref="processDesigner"
@init-finished="initModeler"
:additionalModel="controlForm.additionalModel"
:model="model"
@save="save"
/>
<!-- 流程属性器负责编辑每个流程节点的属性 -->
<MyProcessPenal
v-if="isModelerReady && modeler"
key="penal"
:bpmnModeler="modeler as any"
:bpmnModeler="modeler"
:prefix="controlForm.prefix"
class="process-panel"
:model="model"
@ -34,12 +35,26 @@ import * as ModelApi from '@/api/bpm/model'
defineOptions({ name: 'BpmModelEditor' })
const router = useRouter() //
const { query } = useRoute() //
const props = defineProps<{
modelId?: string
modelKey?: string
modelName?: string
value?: string
}>()
const emit = defineEmits(['success', 'init-finished'])
const message = useMessage() //
const xmlString = ref(undefined) // BPMN XML
const modeler = ref(null) // BPMN Modeler
//
const formFields = ref<string[]>([])
const formType = ref(20)
provide('formFields', formFields)
provide('formType', formType)
const xmlString = ref<string>('') // BPMN XML
const modeler = shallowRef() // BPMN Modeler
const processDesigner = ref()
const isModelerReady = ref(false)
const controlForm = ref({
simulation: true,
labelEditing: false,
@ -50,66 +65,215 @@ const controlForm = ref({
})
const model = ref<ModelApi.ModelVO>() //
// bpmnInstances
const initBpmnInstances = () => {
if (!modeler.value) return false
try {
const instances = {
modeler: modeler.value,
modeling: modeler.value.get('modeling'),
moddle: modeler.value.get('moddle'),
eventBus: modeler.value.get('eventBus'),
bpmnFactory: modeler.value.get('bpmnFactory'),
elementFactory: modeler.value.get('elementFactory'),
elementRegistry: modeler.value.get('elementRegistry'),
replace: modeler.value.get('replace'),
selection: modeler.value.get('selection')
}
//
return Object.values(instances).every((instance) => instance)
} catch (error) {
console.error('初始化 bpmnInstances 失败:', error)
return false
}
}
/** 初始化 modeler */
const initModeler = (item) => {
setTimeout(() => {
const initModeler = async (item) => {
try {
modeler.value = item
}, 10)
}
// modeler
await nextTick()
/** 添加/修改模型 */
const save = async (bpmnXml: string) => {
const data = {
...model.value,
bpmnXml: bpmnXml // bpmnXml
} as unknown as ModelApi.ModelVO
//
if (data.id) {
await ModelApi.updateModelBpmn(data)
message.success('修改成功')
} else {
await ModelApi.updateModelBpmn(data)
message.success('新增成功')
}
//
close()
}
// modeler
if (initBpmnInstances()) {
isModelerReady.value = true
emit('init-finished')
/** 关闭按钮 */
const close = () => {
router.push({ path: '/bpm/manager/model' })
}
/** 初始化 */
onMounted(async () => {
const modelId = query.modelId as unknown as number
if (!modelId) {
message.error('缺少模型 modelId 编号')
return
}
//
const data = await ModelApi.getModel(modelId)
if (!data.bpmnXml) {
// Model bpmnXml
data.bpmnXml = ` <?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.activiti.org/processdef">
<process id="${data.key}" name="${data.name}" isExecutable="true" />
<bpmndi:BPMNDiagram id="BPMNDiagram">
<bpmndi:BPMNPlane id="${data.key}_di" bpmnElement="${data.key}" />
</bpmndi:BPMNDiagram>
</definitions>`
}
//
if (props.modelId) {
//
const data = await ModelApi.getModel(props.modelId)
model.value = {
...data,
bpmnXml: undefined // bpmnXml
}
xmlString.value = data.bpmnXml
xmlString.value = data.bpmnXml || getDefaultBpmnXml(data.key, data.name)
} else if (props.modelKey && props.modelName) {
//
xmlString.value = props.value || getDefaultBpmnXml(props.modelKey, props.modelName)
model.value = {
key: props.modelKey,
name: props.modelName
} as ModelApi.ModelVO
}
// XML
await nextTick()
try {
await modeler.value.importXML(xmlString.value)
if (processDesigner.value?.refresh) {
processDesigner.value.refresh()
}
} catch (error) {
console.error('导入XML失败:', error)
}
} else {
console.error('modeler 实例未完全初始化')
}
} catch (error) {
console.error('初始化 modeler 失败:', error)
}
}
/** 获取默认的BPMN XML */
const getDefaultBpmnXml = (key: string, name: string) => {
return `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.activiti.org/processdef">
<process id="${key}" name="${name}" isExecutable="true" />
<bpmndi:BPMNDiagram id="BPMNDiagram">
<bpmndi:BPMNPlane id="${key}_di" bpmnElement="${key}" />
</bpmndi:BPMNDiagram>
</definitions>`
}
/** 添加/修改模型 */
const save = async (bpmnXml: string) => {
try {
xmlString.value = bpmnXml
if (props.modelId) {
//
const data = {
...model.value,
bpmnXml: bpmnXml
} as unknown as ModelApi.ModelVO
await ModelApi.updateModelBpmn(data)
emit('success')
} else {
// XML
emit('success', bpmnXml)
}
} catch (error) {
console.error('保存失败:', error)
message.error('保存失败')
}
}
// keyname value
watch(
[() => props.modelKey, () => props.modelName, () => props.value],
async ([newKey, newName, newValue]) => {
if (!props.modelId && isModelerReady.value) {
let shouldRefresh = false
if (newKey && newName) {
const newXml = newValue || getDefaultBpmnXml(newKey, newName)
if (newXml !== xmlString.value) {
xmlString.value = newXml
shouldRefresh = true
}
model.value = {
...model.value,
key: newKey,
name: newName
} as ModelApi.ModelVO
} else if (newValue && newValue !== xmlString.value) {
xmlString.value = newValue
shouldRefresh = true
}
if (shouldRefresh) {
//
await nextTick()
if (processDesigner.value?.refresh) {
try {
await modeler.value?.importXML(xmlString.value)
processDesigner.value.refresh()
} catch (error) {
console.error('导入XML失败:', error)
}
}
}
}
},
{ deep: true }
)
//
onBeforeUnmount(() => {
isModelerReady.value = false
modeler.value = null
//
const w = window as any
if (w.bpmnInstances) {
w.bpmnInstances = null
}
})
/** 获取 XML 字符串 */
const saveXML = async () => {
if (!modeler.value) {
return { xml: xmlString.value }
}
try {
const result = await modeler.value.saveXML({ format: true })
xmlString.value = result.xml
return result
} catch (error) {
console.error('获取XML失败:', error)
return { xml: xmlString.value }
}
}
/** 获取SVG字符串 */
const saveSVG = async () => {
if (!modeler.value) {
return { svg: undefined }
}
try {
return await modeler.value.saveSVG()
} catch (error) {
console.error('获取SVG失败:', error)
return { svg: undefined }
}
}
/** 刷新视图 */
const refresh = async () => {
if (processDesigner.value?.refresh && modeler.value) {
try {
await modeler.value.importXML(xmlString.value)
processDesigner.value.refresh()
} catch (error) {
console.error('刷新视图失败:', error)
}
}
}
//
defineExpose({
modeler,
isModelerReady,
saveXML,
saveSVG,
refresh
})
</script>
<style lang="scss">
.process-panel__container {
position: absolute;
top: 90px;
right: 60px;
top: 172px;
right: 70px;
}
</style>

View File

@ -0,0 +1,301 @@
<template>
<el-form ref="formRef" :model="modelData" :rules="rules" label-width="120px" class="mt-20px">
<el-form-item label="流程标识" prop="key" class="mb-20px">
<div class="flex items-center">
<el-input
class="!w-440px"
v-model="modelData.key"
:disabled="!!modelData.id"
placeholder="请输入流标标识"
/>
<el-tooltip
class="item"
:content="modelData.id ? '流程标识不可修改!' : '新建后,流程标识不可修改!'"
effect="light"
placement="top"
>
<Icon icon="ep:question-filled" class="ml-5px" />
</el-tooltip>
</div>
</el-form-item>
<el-form-item label="流程名称" prop="name" class="mb-20px">
<el-input
v-model="modelData.name"
:disabled="!!modelData.id"
clearable
placeholder="请输入流程名称"
/>
</el-form-item>
<el-form-item label="流程分类" prop="category" class="mb-20px">
<el-select
class="!w-full"
v-model="modelData.category"
clearable
placeholder="请选择流程分类"
>
<el-option
v-for="category in categoryList"
:key="category.code"
:label="category.name"
:value="category.code"
/>
</el-select>
</el-form-item>
<el-form-item label="流程图标" prop="icon" class="mb-20px">
<UploadImg v-model="modelData.icon" :limit="1" height="64px" width="64px" />
</el-form-item>
<el-form-item label="流程描述" prop="description" class="mb-20px">
<el-input v-model="modelData.description" clearable type="textarea" />
</el-form-item>
<el-form-item label="流程类型" prop="type" class="mb-20px">
<el-radio-group v-model="modelData.type">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_TYPE)"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="是否可见" prop="visible" class="mb-20px">
<el-radio-group v-model="modelData.visible">
<el-radio
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="谁可以发起" prop="startUserType" class="mb-20px">
<el-select
v-model="modelData.startUserType"
placeholder="请选择谁可以发起"
@change="handleStartUserTypeChange"
>
<el-option label="全员" :value="0" />
<el-option label="指定人员" :value="1" />
<el-option label="均不可提交" :value="2" />
</el-select>
<div v-if="modelData.startUserType === 1" class="mt-2 flex flex-wrap gap-2">
<div
v-for="user in selectedStartUsers"
:key="user.id"
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
>
<el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
<el-avatar class="!m-5px" :size="28" v-else>
{{ user.nickname.substring(0, 1) }}
</el-avatar>
{{ user.nickname }}
<Icon
icon="ep:close"
class="ml-2 cursor-pointer hover:text-red-500"
@click="handleRemoveStartUser(user)"
/>
</div>
<el-button type="primary" link @click="openStartUserSelect">
<Icon icon="ep:plus" />选择人员
</el-button>
</div>
</el-form-item>
<el-form-item label="流程管理员" prop="managerUserType" class="mb-20px">
<el-select
v-model="modelData.managerUserType"
placeholder="请选择流程管理员"
@change="handleManagerUserTypeChange"
>
<el-option label="全员" :value="0" />
<el-option label="指定人员" :value="1" />
<el-option label="均不可提交" :value="2" />
</el-select>
<div v-if="modelData.managerUserType === 1" class="mt-2 flex flex-wrap gap-2">
<div
v-for="user in selectedManagerUsers"
:key="user.id"
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
>
<el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
<el-avatar class="!m-5px" :size="28" v-else>
{{ user.nickname.substring(0, 1) }}
</el-avatar>
{{ user.nickname }}
<Icon
icon="ep:close"
class="ml-2 cursor-pointer hover:text-red-500"
@click="handleRemoveManagerUser(user)"
/>
</div>
<el-button type="primary" link @click="openManagerUserSelect">
<Icon icon="ep:plus" />选择人员
</el-button>
</div>
</el-form-item>
</el-form>
<!-- 用户选择弹窗 -->
<UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
</template>
<script lang="ts" setup>
import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
import { UserVO } from '@/api/system/user'
const props = defineProps({
modelValue: {
type: Object,
required: true
},
categoryList: {
type: Array,
required: true
},
userList: {
type: Array,
required: true
}
})
const emit = defineEmits(['update:modelValue'])
const formRef = ref()
const selectedStartUsers = ref<UserVO[]>([])
const selectedManagerUsers = ref<UserVO[]>([])
const userSelectFormRef = ref()
const currentSelectType = ref<'start' | 'manager'>('start')
const rules = {
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }],
icon: [{ required: true, message: '流程图标不能为空', trigger: 'blur' }],
type: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
managerUserIds: [{ required: true, message: '流程管理员不能为空', trigger: 'blur' }]
}
//
const modelData = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
//
watch(
() => props.modelValue,
(newVal) => {
if (newVal.startUserIds?.length) {
selectedStartUsers.value = props.userList.filter((user: UserVO) =>
newVal.startUserIds.includes(user.id)
) as UserVO[]
}
if (newVal.managerUserIds?.length) {
selectedManagerUsers.value = props.userList.filter((user: UserVO) =>
newVal.managerUserIds.includes(user.id)
) as UserVO[]
}
},
{ immediate: true }
)
/** 打开发起人选择 */
const openStartUserSelect = () => {
currentSelectType.value = 'start'
userSelectFormRef.value.open(0, selectedStartUsers.value)
}
/** 打开管理员选择 */
const openManagerUserSelect = () => {
currentSelectType.value = 'manager'
userSelectFormRef.value.open(0, selectedManagerUsers.value)
}
/** 处理用户选择确认 */
const handleUserSelectConfirm = (_, users: UserVO[]) => {
if (currentSelectType.value === 'start') {
selectedStartUsers.value = users
emit('update:modelValue', {
...modelData.value,
startUserIds: users.map((u) => u.id)
})
} else {
selectedManagerUsers.value = users
emit('update:modelValue', {
...modelData.value,
managerUserIds: users.map((u) => u.id)
})
}
}
/** 处理发起人类型变化 */
const handleStartUserTypeChange = (value: number) => {
if (value !== 1) {
selectedStartUsers.value = []
emit('update:modelValue', {
...modelData.value,
startUserIds: []
})
}
}
/** 处理管理员类型变化 */
const handleManagerUserTypeChange = (value: number) => {
if (value !== 1) {
selectedManagerUsers.value = []
emit('update:modelValue', {
...modelData.value,
managerUserIds: []
})
}
}
/** 移除发起人 */
const handleRemoveStartUser = (user: UserVO) => {
selectedStartUsers.value = selectedStartUsers.value.filter((u) => u.id !== user.id)
emit('update:modelValue', {
...modelData.value,
startUserIds: modelData.value.startUserIds.filter((id: number) => id !== user.id)
})
}
/** 移除管理员 */
const handleRemoveManagerUser = (user: UserVO) => {
selectedManagerUsers.value = selectedManagerUsers.value.filter((u) => u.id !== user.id)
emit('update:modelValue', {
...modelData.value,
managerUserIds: modelData.value.managerUserIds.filter((id: number) => id !== user.id)
})
}
/** 表单校验 */
const validate = async () => {
await formRef.value?.validate()
}
defineExpose({
validate
})
</script>
<style lang="scss" scoped>
.bg-gray-100 {
background-color: #f5f7fa;
transition: all 0.3s;
&:hover {
background-color: #e6e8eb;
}
.ep-close {
font-size: 14px;
color: #909399;
transition: color 0.3s;
&:hover {
color: #f56c6c;
}
}
}
</style>

View File

@ -0,0 +1,137 @@
<template>
<el-form ref="formRef" :model="modelData" :rules="rules" label-width="120px" class="mt-20px">
<el-form-item label="表单类型" prop="formType" class="mb-20px">
<el-radio-group v-model="modelData.formType">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="modelData.formType === 10" label="流程表单" prop="formId">
<el-select v-model="modelData.formId" clearable style="width: 100%">
<el-option v-for="form in formList" :key="form.id" :label="form.name" :value="form.id" />
</el-select>
</el-form-item>
<el-form-item v-if="modelData.formType === 20" label="表单提交路由" prop="formCustomCreatePath">
<el-input
v-model="modelData.formCustomCreatePath"
placeholder="请输入表单提交路由"
style="width: 330px"
/>
<el-tooltip
class="item"
content="自定义表单的提交路径,使用 Vue 的路由地址例如说bpm/oa/leave/create.vue"
effect="light"
placement="top"
>
<Icon icon="ep:question" class="ml-5px" />
</el-tooltip>
</el-form-item>
<el-form-item v-if="modelData.formType === 20" label="表单查看地址" prop="formCustomViewPath">
<el-input
v-model="modelData.formCustomViewPath"
placeholder="请输入表单查看的组件地址"
style="width: 330px"
/>
<el-tooltip
class="item"
content="自定义表单的查看组件地址,使用 Vue 的组件地址例如说bpm/oa/leave/detail.vue"
effect="light"
placement="top"
>
<Icon icon="ep:question" class="ml-5px" />
</el-tooltip>
</el-form-item>
<!-- 表单预览 -->
<div
v-if="modelData.formType === 10 && modelData.formId && formPreview.rule.length > 0"
class="mt-20px"
>
<div class="flex items-center mb-15px">
<div class="h-15px w-4px bg-[#1890ff] mr-10px"></div>
<span class="text-15px font-bold">表单预览</span>
</div>
<form-create
v-model="formPreview.formData"
:rule="formPreview.rule"
:option="formPreview.option"
/>
</div>
</el-form>
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as FormApi from '@/api/bpm/form'
import { setConfAndFields2 } from '@/utils/formCreate'
const props = defineProps({
modelValue: {
type: Object,
required: true
},
formList: {
type: Array,
required: true
}
})
const emit = defineEmits(['update:modelValue'])
const formRef = ref()
//
const modelData = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
//
const formPreview = ref({
formData: {},
rule: [],
option: {
submitBtn: false,
resetBtn: false,
formData: {}
}
})
// ID
watch(
() => modelData.value.formId,
async (newFormId) => {
if (newFormId && modelData.value.formType === 10) {
const data = await FormApi.getForm(newFormId)
setConfAndFields2(formPreview.value, data.conf, data.fields)
//
formPreview.value.rule.forEach((item: any) => {
item.props = { ...item.props, disabled: true }
})
} else {
formPreview.value.rule = []
}
},
{ immediate: true }
)
const rules = {
formType: [{ required: true, message: '表单类型不能为空', trigger: 'blur' }],
formId: [{ required: true, message: '流程表单不能为空', trigger: 'blur' }],
formCustomCreatePath: [{ required: true, message: '表单提交路由不能为空', trigger: 'blur' }],
formCustomViewPath: [{ required: true, message: '表单查看地址不能为空', trigger: 'blur' }]
}
/** 表单校验 */
const validate = async () => {
await formRef.value?.validate()
}
defineExpose({
validate
})
</script>

View File

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

View File

@ -0,0 +1,439 @@
<template>
<ContentWrap>
<div class="mx-auto">
<!-- 头部导航栏 -->
<div
class="absolute top-0 left-0 right-0 h-50px bg-white border-bottom z-10 flex items-center px-20px"
>
<!-- 左侧标题 -->
<div class="w-200px flex items-center overflow-hidden">
<Icon icon="ep:arrow-left" class="cursor-pointer flex-shrink-0" @click="handleBack" />
<span class="ml-10px text-16px truncate" :title="formData.name || '创建流程'">
{{ formData.name || '创建流程' }}
</span>
</div>
<!-- 步骤条 -->
<div class="flex-1 flex items-center justify-center h-full">
<div class="w-400px flex items-center justify-between h-full">
<div
v-for="(step, index) in steps"
:key="index"
class="flex items-center cursor-pointer mx-15px relative h-full"
:class="[
currentStep === index
? 'text-[#3473ff] border-[#3473ff] border-b-2 border-b-solid'
: 'text-gray-500'
]"
@click="handleStepClick(index)"
>
<div
class="w-28px h-28px rounded-full flex items-center justify-center mr-8px border-2 border-solid text-15px"
:class="[
currentStep === index
? 'bg-[#3473ff] text-white border-[#3473ff]'
: 'border-gray-300 bg-white text-gray-500'
]"
>
{{ index + 1 }}
</div>
<span class="text-16px font-bold whitespace-nowrap">{{ step.title }}</span>
</div>
</div>
</div>
<!-- 右侧按钮 -->
<div class="w-200px flex items-center justify-end gap-2">
<el-button v-if="route.params.id" type="success" @click="handleDeploy"> </el-button>
<el-button type="primary" @click="handleSave"> </el-button>
</div>
</div>
<!-- 主体内容 -->
<div class="mt-50px">
<!-- 第一步基本信息 -->
<div v-if="currentStep === 0" class="mx-auto w-560px">
<BasicInfo
v-model="formData"
:categoryList="categoryList"
:userList="userList"
ref="basicInfoRef"
/>
</div>
<!-- 第二步表单设计 -->
<div v-if="currentStep === 1" class="mx-auto w-560px">
<FormDesign v-model="formData" :formList="formList" ref="formDesignRef" />
</div>
<!-- 第三步流程设计 -->
<ProcessDesign
v-if="currentStep === 2"
v-model="formData"
ref="processDesignRef"
@success="handleDesignSuccess"
/>
</div>
</div>
</ContentWrap>
</template>
<script lang="ts" setup>
import { useRoute, useRouter } from 'vue-router'
import { useMessage } from '@/hooks/web/useMessage'
import * as ModelApi from '@/api/bpm/model'
import * as FormApi from '@/api/bpm/form'
import { CategoryApi } from '@/api/bpm/category'
import * as UserApi from '@/api/system/user'
import { useUserStoreWithOut } from '@/store/modules/user'
import { BpmModelFormType, BpmModelType } from '@/utils/constants'
import BasicInfo from './BasicInfo.vue'
import FormDesign from './FormDesign.vue'
import ProcessDesign from './ProcessDesign.vue'
import { useTagsViewStore } from '@/store/modules/tagsView'
const router = useRouter()
const { delView } = useTagsViewStore() //
const route = useRoute()
const message = useMessage()
const userStore = useUserStoreWithOut()
//
const basicInfoRef = ref()
const formDesignRef = ref()
const processDesignRef = ref()
/** 步骤校验函数 */
const validateBasic = async () => {
await basicInfoRef.value?.validate()
}
/** 表单设计校验 */
const validateForm = async () => {
await formDesignRef.value?.validate()
}
/** 流程设计校验 */
const validateProcess = async () => {
await processDesignRef.value?.validate()
}
const currentStep = ref(0) //
const steps = [
{ title: '基本信息', validator: validateBasic },
{ title: '表单设计', validator: validateForm },
{ title: '流程设计', validator: validateProcess }
]
//
const formData: any = ref({
id: undefined,
name: '',
key: '',
category: undefined,
icon: undefined,
description: '',
type: BpmModelType.BPMN,
formType: BpmModelFormType.NORMAL,
formId: '',
formCustomCreatePath: '',
formCustomViewPath: '',
visible: true,
startUserType: undefined,
managerUserType: undefined,
startUserIds: [],
managerUserIds: []
})
//
const formList = ref([])
const categoryList = ref([])
const userList = ref<UserApi.UserVO[]>([])
/** 初始化数据 */
const initData = async () => {
const modelId = route.params.id as string
if (modelId) {
//
formData.value = await ModelApi.getModel(modelId)
} else {
//
formData.value.managerUserIds.push(userStore.getUser.id)
}
//
formList.value = await FormApi.getFormSimpleList()
//
categoryList.value = await CategoryApi.getCategorySimpleList()
//
userList.value = await UserApi.getSimpleUserList()
}
/** 校验所有步骤数据是否完整 */
const validateAllSteps = async () => {
try {
//
await basicInfoRef.value?.validate()
if (!formData.value.key || !formData.value.name || !formData.value.category) {
currentStep.value = 0
throw new Error('请完善基本信息')
}
//
await formDesignRef.value?.validate()
if (formData.value.formType === 10 && !formData.value.formId) {
currentStep.value = 1
throw new Error('请选择流程表单')
}
if (
formData.value.formType === 20 &&
(!formData.value.formCustomCreatePath || !formData.value.formCustomViewPath)
) {
currentStep.value = 1
throw new Error('请完善自定义表单信息')
}
//
//
if (!formData.value.bpmnXml && !formData.value.simpleModel) {
//
if (currentStep.value !== 2) {
await steps[currentStep.value].validator()
//
currentStep.value = 2
//
await nextTick()
}
//
await processDesignRef.value?.validate()
const processData = await processDesignRef.value?.getProcessData()
if (!processData) {
throw new Error('请设计流程')
}
//
if (formData.value.type === BpmModelType.BPMN) {
formData.value.bpmnXml = processData
formData.value.simpleModel = null
} else {
formData.value.bpmnXml = null
formData.value.simpleModel = processData
}
}
return true
} catch (error) {
throw error
}
}
/** 保存操作 */
const handleSave = async () => {
try {
//
await validateAllSteps()
//
const modelData = {
...formData.value
}
//
if (currentStep.value === 2) {
const processData = await processDesignRef.value?.getProcessData()
if (processData) {
if (formData.value.type === BpmModelType.BPMN) {
modelData.bpmnXml = processData
modelData.simpleModel = null
} else {
modelData.bpmnXml = null
modelData.simpleModel = processData
}
}
}
if (formData.value.id) {
//
await ModelApi.updateModel(modelData)
//
try {
await message.confirm('修改流程成功,是否发布流程?')
//
await handleDeploy()
} catch {
//
}
} else {
//
formData.value.id = await ModelApi.createModel(modelData)
message.success('新增成功')
try {
await message.confirm('创建流程成功,是否继续编辑?')
//
await nextTick()
//
delView(unref(router.currentRoute))
//
await router.push({
name: 'BpmModelUpdate',
params: { id: formData.value.id }
})
} catch {
//
delView(unref(router.currentRoute))
//
await router.push({ name: 'BpmModel' })
}
}
} catch (error: any) {
console.error('保存失败:', error)
message.warning(error.message || '请完善所有步骤的必填信息')
}
}
/** 发布操作 */
const handleDeploy = async () => {
try {
//
if (!formData.value.id) {
await message.confirm('是否确认发布该流程?')
}
//
await validateAllSteps()
//
const modelData = {
...formData.value
}
//
if (currentStep.value === 2) {
const processData = await processDesignRef.value?.getProcessData()
if (processData) {
if (formData.value.type === BpmModelType.BPMN) {
modelData.bpmnXml = processData
modelData.simpleModel = null
} else {
modelData.bpmnXml = null
modelData.simpleModel = processData
}
}
}
//
if (formData.value.id) {
await ModelApi.updateModel(modelData)
} else {
const result = await ModelApi.createModel(modelData)
formData.value.id = result.id
}
//
await ModelApi.deployModel(formData.value.id)
message.success('发布成功')
//
await router.push({ name: 'BpmModel' })
} catch (error: any) {
console.error('发布失败:', error)
message.warning(error.message || '发布失败')
}
}
/** 步骤切换处理 */
const handleStepClick = async (index: number) => {
try {
// keyname
if (index === 2) {
if (!formData.value.key || !formData.value.name) {
message.warning('请先填写流程标识和流程名称')
return
}
}
//
if (currentStep.value === 2) {
const processData = await processDesignRef.value?.getProcessData()
if (processData) {
if (formData.value.type === BpmModelType.BPMN) {
formData.value.bpmnXml = processData
formData.value.simpleModel = null
} else {
formData.value.bpmnXml = null
formData.value.simpleModel = processData
}
}
} else {
//
if (index > currentStep.value) {
if (typeof steps[currentStep.value].validator === 'function') {
await steps[currentStep.value].validator()
}
}
}
//
currentStep.value = index
//
if (index === 2) {
await nextTick()
//
await new Promise(resolve => setTimeout(resolve, 200))
if (processDesignRef.value?.refresh) {
await processDesignRef.value.refresh()
}
}
} catch (error) {
console.error('步骤切换失败:', error)
message.warning('请先完善当前步骤必填信息')
}
}
/** 处理设计器保存成功 */
const handleDesignSuccess = (bpmnXml?: string) => {
if (bpmnXml) {
formData.value.bpmnXml = bpmnXml
}
}
/** 返回列表页 */
const handleBack = () => {
//
delView(unref(router.currentRoute))
//
router.push({ name: 'BpmModel' })
}
/** 初始化 */
onMounted(async () => {
await initData()
})
//
onBeforeUnmount(() => {
//
basicInfoRef.value = null
formDesignRef.value = null
processDesignRef.value = null
})
</script>
<style lang="scss" scoped>
.border-bottom {
border-bottom: 1px solid #dcdfe6;
}
.text-primary {
color: #3473ff;
}
.bg-primary {
background-color: #3473ff;
}
.border-primary {
border-color: #3473ff;
}
</style>

View File

@ -106,6 +106,7 @@ import CategoryDraggableModel from './CategoryDraggableModel.vue'
defineOptions({ name: 'BpmModel' })
const { push } = useRouter()
const message = useMessage() //
const loading = ref(true) //
const isCategorySorting = ref(false) // category
@ -124,7 +125,14 @@ const handleQuery = () => {
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
if (type === 'create') {
push({ name: 'BpmModelCreate' })
} else {
push({
name: 'BpmModelUpdate',
params: { id }
})
}
}
/** 流程表单的详情按钮操作 */

View File

@ -1,404 +0,0 @@
<template>
<doc-alert title="流程设计器BPMN" url="https://doc.iocoder.cn/bpm/model-designer-dingding/" />
<doc-alert
title="流程设计器(钉钉、飞书)"
url="https://doc.iocoder.cn/bpm/model-designer-bpmn/"
/>
<doc-alert title="选择审批人、发起人自选" url="https://doc.iocoder.cn/bpm/assignee/" />
<doc-alert title="会签、或签、依次审批" url="https://doc.iocoder.cn/bpm/multi-instance/" />
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="流程标识" prop="key">
<el-input
v-model="queryParams.key"
placeholder="请输入流程标识"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="流程名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入流程名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="流程分类" prop="category">
<el-select
v-model="queryParams.category"
placeholder="请选择流程分类"
clearable
class="!w-240px"
>
<el-option
v-for="category in categoryList"
:key="category.code"
:label="category.name"
:value="category.code"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['bpm:model:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新建
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="流程名称" align="center" prop="name" min-width="200" />
<el-table-column label="流程图标" align="center" prop="icon" min-width="100">
<template #default="scope">
<el-image :src="scope.row.icon" class="h-32px w-32px" />
</template>
</el-table-column>
<el-table-column label="可见范围" align="center" prop="startUserIds" min-width="100">
<template #default="scope">
<el-text v-if="!scope.row.startUsers || scope.row.startUsers.length === 0">
全部可见
</el-text>
<el-text v-else-if="scope.row.startUsers.length == 1">
{{ scope.row.startUsers[0].nickname }}
</el-text>
<el-text v-else>
<el-tooltip
class="box-item"
effect="dark"
placement="top"
:content="scope.row.startUsers.map((user: any) => user.nickname).join('、')"
>
{{ scope.row.startUsers[0].nickname }} {{ scope.row.startUsers.length }} 人可见
</el-tooltip>
</el-text>
</template>
</el-table-column>
<el-table-column label="流程分类" align="center" prop="categoryName" min-width="100" />
<el-table-column label="表单信息" align="center" prop="formType" min-width="200">
<template #default="scope">
<el-button
v-if="scope.row.formType === 10"
type="primary"
link
@click="handleFormDetail(scope.row)"
>
<span>{{ scope.row.formName }}</span>
</el-button>
<el-button
v-else-if="scope.row.formType === 20"
type="primary"
link
@click="handleFormDetail(scope.row)"
>
<span>{{ scope.row.formCustomCreatePath }}</span>
</el-button>
<label v-else>暂无表单</label>
</template>
</el-table-column>
<el-table-column label="最后发布" align="center" prop="deploymentTime" min-width="250">
<template #default="scope">
<span v-if="scope.row.processDefinition">
{{ formatDate(scope.row.processDefinition.deploymentTime) }}
</span>
<el-tag v-if="scope.row.processDefinition" class="ml-10px">
v{{ scope.row.processDefinition.version }}
</el-tag>
<el-tag v-else type="warning">未部署</el-tag>
<el-tag
v-if="scope.row.processDefinition?.suspensionState === 2"
type="warning"
class="ml-10px"
>
已停用
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['bpm:model:update']"
:disabled="!isManagerUser(scope.row)"
>
修改
</el-button>
<el-button
link
class="!ml-5px"
type="primary"
@click="handleDesign(scope.row)"
v-hasPermi="['bpm:model:update']"
:disabled="!isManagerUser(scope.row)"
>
设计
</el-button>
<el-button
link
class="!ml-5px"
type="primary"
@click="handleDeploy(scope.row)"
v-hasPermi="['bpm:model:deploy']"
:disabled="!isManagerUser(scope.row)"
>
发布
</el-button>
<el-dropdown
class="!align-middle ml-5px"
@command="(command) => handleCommand(command, scope.row)"
v-hasPermi="['bpm:process-definition:query', 'bpm:model:update', 'bpm:model:delete']"
>
<el-button type="primary" link>更多</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
command="handleDefinitionList"
v-if="checkPermi(['bpm:process-definition:query'])"
>
历史
</el-dropdown-item>
<el-dropdown-item
command="handleChangeState"
v-if="checkPermi(['bpm:model:update']) && scope.row.processDefinition"
:disabled="!isManagerUser(scope.row)"
>
{{ scope.row.processDefinition.suspensionState === 1 ? '停用' : '启用' }}
</el-dropdown-item>
<el-dropdown-item
type="danger"
command="handleDelete"
v-if="checkPermi(['bpm:model:delete'])"
:disabled="!isManagerUser(scope.row)"
>
删除
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改流程 -->
<ModelForm ref="formRef" @success="getList" />
<!-- 弹窗表单详情 -->
<Dialog title="表单详情" v-model="formDetailVisible" width="800">
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
</Dialog>
</template>
<script lang="ts" setup>
import { formatDate } from '@/utils/formatTime'
import * as ModelApi from '@/api/bpm/model'
import * as FormApi from '@/api/bpm/form'
import ModelForm from './ModelForm.vue'
import { setConfAndFields2 } from '@/utils/formCreate'
import { CategoryApi } from '@/api/bpm/category'
import { BpmModelType } from '@/utils/constants'
import { checkPermi } from '@/utils/permission'
import { useUserStoreWithOut } from '@/store/modules/user'
defineOptions({ name: 'BpmModel' })
const message = useMessage() //
const { t } = useI18n() //
const { push } = useRouter() //
const userStore = useUserStoreWithOut() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
key: undefined,
name: undefined,
category: undefined
})
const queryFormRef = ref() //
const categoryList = ref([]) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ModelApi.getModelList(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** '更多'操作按钮 */
const handleCommand = (command: string, row: any) => {
switch (command) {
case 'handleDefinitionList':
handleDefinitionList(row)
break
case 'handleDelete':
handleDelete(row)
break
case 'handleChangeState':
handleChangeState(row)
break
default:
break
}
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (row: any) => {
try {
//
await message.delConfirm()
//
await ModelApi.deleteModel(row.id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 更新状态操作 */
const handleChangeState = async (row: any) => {
const state = row.processDefinition.suspensionState
const newState = state === 1 ? 2 : 1
try {
//
const id = row.id
debugger
const statusState = state === 1 ? '停用' : '启用'
const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
await message.confirm(content)
//
await ModelApi.updateModelState(id, newState)
message.success(statusState + '成功')
//
await getList()
} catch {}
}
/** 设计流程 */
const handleDesign = (row: any) => {
if (row.type == BpmModelType.BPMN) {
push({
name: 'BpmModelEditor',
query: {
modelId: row.id
}
})
} else {
push({
name: 'SimpleModelDesign',
query: {
modelId: row.id
}
})
}
}
/** 发布流程 */
const handleDeploy = async (row: any) => {
try {
//
await message.confirm('是否部署该流程!!')
//
await ModelApi.deployModel(row.id)
message.success(t('部署成功'))
//
await getList()
} catch {}
}
/** 跳转到指定流程定义列表 */
const handleDefinitionList = (row) => {
push({
name: 'BpmProcessDefinition',
query: {
key: row.key
}
})
}
/** 流程表单的详情按钮操作 */
const formDetailVisible = ref(false)
const formDetailPreview = ref({
rule: [],
option: {}
})
const handleFormDetail = async (row: any) => {
if (row.formType == 10) {
//
const data = await FormApi.getForm(row.formId)
setConfAndFields2(formDetailPreview, data.conf, data.fields)
//
formDetailVisible.value = true
} else {
await push({
path: row.formCustomCreatePath
})
}
}
/** 判断是否可以操作 */
const isManagerUser = (row: any) => {
const userId = userStore.getUser.id
return row.managerUserIds && row.managerUserIds.includes(userId)
}
/** 初始化 **/
onMounted(async () => {
await getList()
//
categoryList.value = await CategoryApi.getCategorySimpleList()
})
</script>

View File

@ -9,7 +9,7 @@
<el-tabs v-model="activeTab">
<!-- 表单信息 -->
<el-tab-pane label="表单填写" name="form">
<div class="form-scroll-area">
<div class="form-scroll-area" v-loading="processInstanceStartLoading">
<el-scrollbar>
<el-row>
<el-col :span="17">
@ -75,7 +75,11 @@
<script lang="ts" setup>
import { decodeFields, setConfAndFields2 } from '@/utils/formCreate'
import { BpmModelType } from '@/utils/constants'
import { CandidateStrategy } from '@/components/SimpleProcessDesignerV2/src/consts'
import {
CandidateStrategy,
NodeId,
FieldPermissionType
} from '@/components/SimpleProcessDesignerV2/src/consts'
import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue'
import ProcessInstanceSimpleViewer from '../detail/ProcessInstanceSimpleViewer.vue'
import ProcessInstanceTimeline from '../detail/ProcessInstanceTimeline.vue'
@ -90,7 +94,7 @@ const props = defineProps<{
selectProcessDefinition: any
}>()
const emit = defineEmits(['cancel'])
const processInstanceStartLoading = ref(false) //
const { push, currentRoute } = useRouter() //
const message = useMessage() //
const { delView } = useTagsViewStore() //
@ -129,8 +133,10 @@ const initProcessInfo = async (row: any, formVariables?: any) => {
}
}
setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables)
await nextTick()
fApi.value?.btn.show(false) //
//
await getApprovalDetail(row)
@ -152,7 +158,12 @@ const initProcessInfo = async (row: any, formVariables?: any) => {
/** 获取审批详情 */
const getApprovalDetail = async (row: any) => {
try {
const data = await ProcessInstanceApi.getApprovalDetail({ processDefinitionId: row.id })
// TODO activityId Simple
const data = await ProcessInstanceApi.getApprovalDetail({
processDefinitionId: row.id,
activityId: NodeId.START_USER_NODE_ID
})
if (!data) {
message.error('查询不到审批详情信息!')
return
@ -170,15 +181,43 @@ const getApprovalDetail = async (row: any) => {
// Timeline
activityNodes.value = data.activityNodes
//
const formFieldsPermission = data.formFieldsPermission
//
if (formFieldsPermission) {
Object.keys(formFieldsPermission).forEach((item) => {
setFieldPermission(item, formFieldsPermission[item])
})
}
} finally {
}
}
/**
* 设置表单权限
*/
const setFieldPermission = (field: string, permission: string) => {
if (permission === FieldPermissionType.READ) {
//@ts-ignore
fApi.value?.disabled(true, field)
}
if (permission === FieldPermissionType.WRITE) {
//@ts-ignore
fApi.value?.disabled(false, field)
}
if (permission === FieldPermissionType.NONE) {
//@ts-ignore
fApi.value?.hidden(true, field)
}
}
/** 提交按钮 */
const submitForm = async () => {
if (!fApi.value || !props.selectProcessDefinition) {
return
}
//
await fApi.value.validate()
//
if (startUserSelectTasks.value?.length > 0) {
for (const userTask of startUserSelectTasks.value) {
@ -191,7 +230,7 @@ const submitForm = async () => {
}
//
fApi.value.btn.loading(true)
processInstanceStartLoading.value = true
try {
await ProcessInstanceApi.createProcessInstance({
processDefinitionId: props.selectProcessDefinition.id,
@ -206,7 +245,7 @@ const submitForm = async () => {
name: 'BpmProcessInstanceMy'
})
} finally {
fApi.value.btn.loading(false)
processInstanceStartLoading.value = false
}
}

View File

@ -20,9 +20,9 @@
<el-form
label-position="top"
class="mb-auto"
ref="formRef"
:model="genericForm"
:rules="genericRule"
ref="approveFormRef"
:model="approveReasonForm"
:rules="approveReasonRule"
label-width="100px"
>
<el-card v-if="runningTask?.formId > 0" class="mb-15px !-mt-10px">
@ -38,17 +38,17 @@
</el-card>
<el-form-item label="审批意见" prop="reason">
<el-input
v-model="genericForm.reason"
v-model="approveReasonForm.reason"
placeholder="请输入审批意见"
type="textarea"
:rows="4"
/>
</el-form-item>
<el-form-item>
<el-button :disabled="formLoading" type="success" @click="handleAudit(true)">
<el-button :disabled="formLoading" type="success" @click="handleAudit(true, approveFormRef)">
{{ getButtonDisplayName(OperationButtonType.APPROVE) }}
</el-button>
<el-button @click="popOverVisible.approve = false"> 取消 </el-button>
<el-button @click="closePropover('approve', approveFormRef)"> 取消 </el-button>
</el-form-item>
</el-form>
</div>
@ -72,35 +72,24 @@
<el-form
label-position="top"
class="mb-auto"
ref="formRef"
:model="genericForm"
:rules="genericRule"
ref="rejectFormRef"
:model="rejectReasonForm"
:rules="rejectReasonRule"
label-width="100px"
>
<el-card v-if="runningTask?.formId > 0" class="mb-15px !-mt-10px">
<template #header>
<span class="el-icon-picture-outline"> 填写表单{{ runningTask?.formName }} </span>
</template>
<form-create
v-model="approveForm.value"
v-model:api="approveFormFApi"
:option="approveForm.option"
:rule="approveForm.rule"
/>
</el-card>
<el-form-item label="审批意见" prop="reason">
<el-input
v-model="genericForm.reason"
v-model="rejectReasonForm.reason"
placeholder="请输入审批意见"
type="textarea"
:rows="4"
/>
</el-form-item>
<el-form-item>
<el-button :disabled="formLoading" type="danger" @click="handleAudit(false)">
<el-button :disabled="formLoading" type="danger" @click="handleAudit(false,rejectFormRef)">
{{ getButtonDisplayName(OperationButtonType.REJECT) }}
</el-button>
<el-button @click="popOverVisible.reject = false"> 取消 </el-button>
<el-button @click="closePropover('reject', rejectFormRef)"> 取消 </el-button>
</el-form-item>
</el-form>
</div>
@ -124,14 +113,14 @@
<el-form
label-position="top"
class="mb-auto"
ref="formRef"
:model="genericForm"
:rules="genericRule"
ref="copyFormRef"
:model="copyForm"
:rules="copyFormRule"
label-width="100px"
>
<el-form-item label="抄送人" prop="copyUserIds">
<el-select
v-model="genericForm.copyUserIds"
v-model="copyForm.copyUserIds"
clearable
style="width: 100%"
multiple
@ -147,7 +136,7 @@
</el-form-item>
<el-form-item label="抄送意见" prop="copyReason">
<el-input
v-model="genericForm.copyReason"
v-model="copyForm.copyReason"
clearable
placeholder="请输入抄送意见"
type="textarea"
@ -158,13 +147,13 @@
<el-button :disabled="formLoading" type="primary" @click="handleCopy">
{{ getButtonDisplayName(OperationButtonType.COPY) }}
</el-button>
<el-button @click="popOverVisible.copy = false"> 取消 </el-button>
<el-button @click="closePropover('copy', copyFormRef)"> 取消 </el-button>
</el-form-item>
</el-form>
</div>
</el-popover>
<!-- 按钮 -->
<!-- 按钮 -->
<el-popover
:visible="popOverVisible.transfer"
placement="top-start"
@ -182,13 +171,13 @@
<el-form
label-position="top"
class="mb-auto"
ref="formRef"
:model="genericForm"
:rules="genericRule"
ref="transferFormRef"
:model="transferForm"
:rules="transferFormRule"
label-width="100px"
>
<el-form-item label="新审批人" prop="assigneeUserId">
<el-select v-model="genericForm.assigneeUserId" clearable style="width: 100%">
<el-select v-model="transferForm.assigneeUserId" clearable style="width: 100%">
<el-option
v-for="item in userOptions"
:key="item.id"
@ -199,7 +188,7 @@
</el-form-item>
<el-form-item label="审批意见" prop="reason">
<el-input
v-model="genericForm.reason"
v-model="transferForm.reason"
clearable
placeholder="请输入审批意见"
type="textarea"
@ -210,7 +199,7 @@
<el-button :disabled="formLoading" type="primary" @click="handleTransfer()">
{{ getButtonDisplayName(OperationButtonType.TRANSFER) }}
</el-button>
<el-button @click="popOverVisible.transfer = false"> 取消 </el-button>
<el-button @click="closePropover('transfer', transferFormRef)"> 取消 </el-button>
</el-form-item>
</el-form>
</div>
@ -234,13 +223,13 @@
<el-form
label-position="top"
class="mb-auto"
ref="formRef"
:model="genericForm"
:rules="genericRule"
ref="delegateFormRef"
:model="delegateForm"
:rules="delegateFormRule"
label-width="100px"
>
<el-form-item label="接收人" prop="delegateUserId">
<el-select v-model="genericForm.delegateUserId" clearable style="width: 100%">
<el-select v-model="delegateForm.delegateUserId" clearable style="width: 100%">
<el-option
v-for="item in userOptions"
:key="item.id"
@ -251,7 +240,7 @@
</el-form-item>
<el-form-item label="审批意见" prop="reason">
<el-input
v-model="genericForm.reason"
v-model="delegateForm.reason"
clearable
placeholder="请输入审批意见"
type="textarea"
@ -262,7 +251,7 @@
<el-button :disabled="formLoading" type="primary" @click="handleDelegate()">
{{ getButtonDisplayName(OperationButtonType.DELEGATE) }}
</el-button>
<el-button @click="popOverVisible.delegate = false"> 取消 </el-button>
<el-button @click="closePropover('delegate', delegateFormRef)"> 取消 </el-button>
</el-form-item>
</el-form>
</div>
@ -286,13 +275,13 @@
<el-form
label-position="top"
class="mb-auto"
ref="formRef"
:model="genericForm"
:rules="genericRule"
ref="addSignFormRef"
:model="addSignForm"
:rules="addSignFormRule"
label-width="100px"
>
<el-form-item label="加签处理人" prop="addSignUserIds">
<el-select v-model="genericForm.addSignUserIds" multiple clearable style="width: 100%">
<el-select v-model="addSignForm.addSignUserIds" multiple clearable style="width: 100%">
<el-option
v-for="item in userOptions"
:key="item.id"
@ -303,7 +292,7 @@
</el-form-item>
<el-form-item label="审批意见" prop="reason">
<el-input
v-model="genericForm.reason"
v-model="addSignForm.reason"
clearable
placeholder="请输入审批意见"
type="textarea"
@ -317,7 +306,7 @@
<el-button :disabled="formLoading" type="primary" @click="handlerAddSign('after')">
向后{{ getButtonDisplayName(OperationButtonType.ADD_SIGN) }}
</el-button>
<el-button @click="popOverVisible.addSign = false"> 取消 </el-button>
<el-button @click="closePropover('addSign', addSignFormRef)"> 取消 </el-button>
</el-form-item>
</el-form>
</div>
@ -340,13 +329,13 @@
<el-form
label-position="top"
class="mb-auto"
ref="formRef"
:model="genericForm"
:rules="genericRule"
ref="deleteSignFormRef"
:model="deleteSignForm"
:rules="deleteSignFormRule"
label-width="100px"
>
<el-form-item label="减签人员" prop="deleteSignTaskId">
<el-select v-model="genericForm.deleteSignTaskId" clearable style="width: 100%">
<el-select v-model="deleteSignForm.deleteSignTaskId" clearable style="width: 100%">
<el-option
v-for="item in runningTask.children"
:key="item.id"
@ -357,7 +346,7 @@
</el-form-item>
<el-form-item label="审批意见" prop="reason">
<el-input
v-model="genericForm.reason"
v-model="deleteSignForm.reason"
clearable
placeholder="请输入审批意见"
type="textarea"
@ -368,7 +357,7 @@
<el-button :disabled="formLoading" type="primary" @click="handlerDeleteSign()">
减签
</el-button>
<el-button @click="popOverVisible.deleteSign = false"> 取消 </el-button>
<el-button @click="closePropover('deleteSign', deleteSignFormRef)"> 取消 </el-button>
</el-form-item>
</el-form>
</div>
@ -383,7 +372,7 @@
v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.RETURN)"
>
<template #reference>
<div @click="openReturnPopover" class="hover-bg-gray-100 rounded-xl p-6px">
<div @click="openPopover('return')" class="hover-bg-gray-100 rounded-xl p-6px">
<Icon :size="14" icon="ep:back" />&nbsp;
{{ getButtonDisplayName(OperationButtonType.RETURN) }}
</div>
@ -392,13 +381,13 @@
<el-form
label-position="top"
class="mb-auto"
ref="formRef"
:model="genericForm"
:rules="genericRule"
ref="returnFormRef"
:model="returnForm"
:rules="returnFormRule"
label-width="100px"
>
<el-form-item label="退回节点" prop="targetTaskDefinitionKey">
<el-select v-model="genericForm.targetTaskDefinitionKey" clearable style="width: 100%">
<el-select v-model="returnForm.targetTaskDefinitionKey" clearable style="width: 100%">
<el-option
v-for="item in returnList"
:key="item.taskDefinitionKey"
@ -409,7 +398,7 @@
</el-form-item>
<el-form-item label="退回理由" prop="returnReason">
<el-input
v-model="genericForm.returnReason"
v-model="returnForm.returnReason"
clearable
placeholder="请输入退回理由"
type="textarea"
@ -420,7 +409,7 @@
<el-button :disabled="formLoading" type="primary" @click="handleReturn()">
{{ getButtonDisplayName(OperationButtonType.RETURN) }}
</el-button>
<el-button @click="popOverVisible.return = false"> 取消 </el-button>
<el-button @click="closePropover('return', returnFormRef)"> 取消 </el-button>
</el-form-item>
</el-form>
</div>
@ -445,15 +434,15 @@
<el-form
label-position="top"
class="mb-auto"
ref="formRef"
:model="genericForm"
:rules="genericRule"
ref="cancelFormRef"
:model="cancelForm"
:rules="cancelFormRule"
label-width="100px"
>
<el-form-item label="取消理由" prop="cancelReason">
<span class="text-#878c93 text-12px">&nbsp; 取消后该审批流程将自动结束</span>
<el-input
v-model="genericForm.cancelReason"
v-model="cancelForm.cancelReason"
clearable
placeholder="请输入取消理由"
type="textarea"
@ -462,9 +451,9 @@
</el-form-item>
<el-form-item>
<el-button :disabled="formLoading" type="primary" @click="handleCancel()">
取消
确认
</el-button>
<el-button @click="popOverVisible.cancel = false"> 取消 </el-button>
<el-button @click="closePropover('cancel', cancelFormRef)"> 取消 </el-button>
</el-form-item>
</el-form>
</div>
@ -488,26 +477,29 @@ import { useUserStoreWithOut } from '@/store/modules/user'
import { setConfAndFields2 } from '@/utils/formCreate'
import * as TaskApi from '@/api/bpm/task'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import { propTypes } from '@/utils/propTypes'
import * as UserApi from '@/api/system/user'
import {
OperationButtonType,
OPERATION_BUTTON_NAME
} from '@/components/SimpleProcessDesignerV2/src/consts'
import { BpmProcessInstanceStatus } from '@/utils/constants'
import { BpmProcessInstanceStatus, BpmModelFormType } from '@/utils/constants'
import type { FormInstance, FormRules } from 'element-plus'
defineOptions({ name: 'ProcessInstanceBtnContainer' })
const router = useRouter() //
const message = useMessage() //
const { proxy } = getCurrentInstance() as any
const userId = useUserStoreWithOut().getUser.id //
const emit = defineEmits(['success']) // success
const props = defineProps({
processInstance: propTypes.object, //
processDefinition: propTypes.object, //
userOptions: propTypes.any
})
const props = defineProps< {
processInstance: any, //
processDefinition: any, //
userOptions: UserApi.UserVO[],
normalForm: any, // formCreate
normalFormApi: any, // formCreate Api
writableFields: string[] //
}>()
const formLoading = ref(false) //
const popOverVisible = ref({
@ -525,21 +517,99 @@ const returnList = ref([] as any) // 退回节点
// ========== ==========
const runningTask = ref<any>() //
const genericForm = ref<any>({}) //
const approveForm = ref<any>({}) //
const approveFormFApi = ref<any>({}) // approveForms fAPi
const formRef = ref()
const genericRule = reactive({
//
const approveFormRef = ref<FormInstance>()
const approveReasonForm = reactive({
reason: ''
})
const approveReasonRule = reactive<FormRules<typeof approveReasonForm>>({
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
returnReason: [{ required: true, message: '退回理由不能为空', trigger: 'blur' }],
cancelReason: [{ required: true, message: '取消理由不能为空', trigger: 'blur' }],
copyUserIds: [{ required: true, message: '抄送人不能为空', trigger: 'change' }],
})
//
const rejectFormRef = ref<FormInstance>()
const rejectReasonForm = reactive({
reason: ''
})
const rejectReasonRule = reactive<FormRules<typeof rejectReasonForm>>({
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
})
//
const copyFormRef = ref<FormInstance>()
const copyForm = reactive({
copyUserIds: [],
copyReason: ''
})
const copyFormRule = reactive<FormRules<typeof copyForm>>({
copyUserIds: [{ required: true, message: '抄送人不能为空', trigger: 'change' }]
})
//
const transferFormRef = ref<FormInstance>()
const transferForm = reactive({
assigneeUserId: undefined,
reason: ''
})
const transferFormRule = reactive<FormRules<typeof transferForm>>({
assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }],
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
})
//
const delegateFormRef = ref<FormInstance>()
const delegateForm = reactive({
delegateUserId: undefined,
reason: ''
})
const delegateFormRule = reactive<FormRules<typeof delegateForm>>({
delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }],
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
})
//
const addSignFormRef = ref<FormInstance>()
const addSignForm = reactive({
addSignUserIds: undefined,
reason: ''
})
const addSignFormRule = reactive<FormRules<typeof addSignForm>>({
addSignUserIds: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }],
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
})
//
const deleteSignFormRef = ref<FormInstance>()
const deleteSignForm = reactive({
deleteSignTaskId: undefined,
reason: ''
})
const deleteSignFormRule = reactive<FormRules<typeof deleteSignForm>>({
deleteSignTaskId: [{ required: true, message: '减签人员不能为空', trigger: 'change' }],
targetTaskDefinitionKey: [{ required: true, message: '退回节点不能为空', trigger: 'change' }]
}) //
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
})
// 退
const returnFormRef = ref<FormInstance>()
const returnForm = reactive({
targetTaskDefinitionKey: undefined,
returnReason: ''
})
const returnFormRule = reactive<FormRules<typeof returnForm>>({
targetTaskDefinitionKey: [{ required: true, message: '退回节点不能为空', trigger: 'change' }],
returnReason: [{ required: true, message: '退回理由不能为空', trigger: 'blur' }]
})
//
const cancelFormRef = ref<FormInstance>()
const cancelForm = reactive({
cancelReason: ''
})
const cancelFormRule = reactive<FormRules<typeof cancelForm>>({
cancelReason: [{ required: true, message: '取消理由不能为空', trigger: 'blur' }],
})
/** 监听 approveFormFApis实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */
watch(
@ -553,43 +623,57 @@ watch(
}
)
/** 弹出退回气泡卡 */
const openReturnPopover = async () => {
/** 弹出气泡卡 */
const openPopover = async (type: string) => {
if (type === 'approve') {
//
const valid = await validateNormalForm();
if (!valid) {
message.warning('表单校验不通过,请先完善表单!!')
return;
}
}
if (type === 'return') {
// 退
returnList.value = await TaskApi.getTaskListByReturn(runningTask.value.id)
if (returnList.value.length === 0) {
message.warning('当前没有可退回的节点')
return
}
await openPopover('return')
}
/** 弹出气泡卡 */
const openPopover = async (type: string) => {
}
Object.keys(popOverVisible.value).forEach((item) => {
popOverVisible.value[item] = item === type
})
await nextTick()
formRef.value.resetFields()
// await nextTick()
// formRef.value.resetFields()
}
/** 关闭气泡卡 */
const closePropover = (type: string, formRef: FormInstance | undefined) => {
if (formRef) {
formRef.resetFields()
}
popOverVisible.value[type] = false
}
/** 处理审批通过和不通过的操作 */
const handleAudit = async (pass: boolean) => {
const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) => {
formLoading.value = true
try {
const genericFormRef = proxy.$refs['formRef']
// 1.2
const elForm = unref(genericFormRef)
if (!elForm) return
const valid = await elForm.validate()
if (!valid) return
// 2.1
//
if (!formRef) return
await formRef.validate()
if (pass) {
// ,
const variables = getUpdatedProcessInstanceVaiables();
//
const data = {
id: runningTask.value.id,
reason: genericForm.value.reason
reason: approveReasonForm.reason,
variables // ,
}
if (pass) {
// approveForm + data
// approveForm + data
// TODO
const formCreateApi = approveFormFApi.value
if (Object.keys(formCreateApi)?.length > 0) {
await formCreateApi.validate()
@ -600,11 +684,18 @@ const handleAudit = async (pass: boolean) => {
popOverVisible.value.approve = false
message.success('审批通过成功')
} else {
//
const data = {
id: runningTask.value.id,
reason: rejectReasonForm.reason,
}
await TaskApi.rejectTask(data)
popOverVisible.value.reject = false
message.success('审批不通过成功')
}
// 2.2
//
formRef.resetFields()
//
reload()
} finally {
formLoading.value = false
@ -615,19 +706,17 @@ const handleAudit = async (pass: boolean) => {
const handleCopy = async () => {
formLoading.value = true
try {
const copyFormRef = proxy.$refs['formRef']
// 1.
const elForm = unref(copyFormRef)
if (!elForm) return
const valid = await elForm.validate()
if (!valid) return
if (!copyFormRef.value) return
await copyFormRef.value.validate()
// 2.
const data = {
id: runningTask.value.id,
reason: genericForm.value.copyReason,
copyUserIds: genericForm.value.copyUserIds
reason: copyForm.copyReason,
copyUserIds:copyForm.copyUserIds
}
await TaskApi.copyTask(data)
copyFormRef.value.resetFields()
popOverVisible.value.copy = false
message.success('操作成功')
} finally {
@ -639,20 +728,17 @@ const handleCopy = async () => {
const handleTransfer = async () => {
formLoading.value = true
try {
const transferFormRef = proxy.$refs['formRef']
// 1.1
const elForm = unref(transferFormRef)
if (!elForm) return
const valid = await elForm.validate()
if (!valid) return
if (!transferFormRef.value) return
await transferFormRef.value.validate()
// 1.2
const data = {
id: runningTask.value.id,
reason: genericForm.value.reason,
assigneeUserId: genericForm.value.assigneeUserId
reason: transferForm.reason,
assigneeUserId: transferForm.assigneeUserId
}
await TaskApi.transferTask(data)
transferFormRef.value.resetFields()
popOverVisible.value.transfer = false
message.success('操作成功')
// 2.
@ -666,21 +752,20 @@ const handleTransfer = async () => {
const handleDelegate = async () => {
formLoading.value = true
try {
const deletegateFormRef = proxy.$refs['formRef']
// 1.1
const elForm = unref(deletegateFormRef)
if (!elForm) return
const valid = await elForm.validate()
if (!valid) return
if (!delegateFormRef.value) return
await delegateFormRef.value.validate()
// 1.2
const data = {
id: runningTask.value.id,
reason: genericForm.value.reason,
delegateUserId: genericForm.value.delegateUserId
reason: delegateForm.reason,
delegateUserId: delegateForm.delegateUserId
}
await TaskApi.delegateTask(data)
popOverVisible.value.delegate = false
delegateFormRef.value.resetFields()
message.success('操作成功')
// 2.
reload()
@ -693,21 +778,19 @@ const handleDelegate = async () => {
const handlerAddSign = async (type: string) => {
formLoading.value = true
try {
const transferFormRef = proxy.$refs['formRef']
// 1.1
const elForm = unref(transferFormRef)
if (!elForm) return
const valid = await elForm.validate()
if (!valid) return
if (!addSignFormRef.value) return
await addSignFormRef.value.validate()
// 1.2
const data = {
id: runningTask.value.id,
type,
reason: genericForm.value.reason,
userIds: genericForm.value.addSignUserIds
reason: addSignForm.reason,
userIds: addSignForm.addSignUserIds
}
await TaskApi.signCreateTask(data)
message.success('操作成功')
addSignFormRef.value.resetFields()
popOverVisible.value.addSign = false
// 2
reload()
@ -720,21 +803,19 @@ const handlerAddSign = async (type: string) => {
const handleReturn = async () => {
formLoading.value = true
try {
const returnFormRef = proxy.$refs['formRef']
// 1.1
const elForm = unref(returnFormRef)
if (!elForm) return
const valid = await elForm.validate()
if (!valid) return
if (!returnFormRef.value) return
await returnFormRef.value.validate()
// 1.2 退
const data = {
id: runningTask.value.id,
reason: genericForm.value.returnReason,
targetTaskDefinitionKey: genericForm.value.targetTaskDefinitionKey
reason: returnForm.returnReason,
targetTaskDefinitionKey: returnForm.targetTaskDefinitionKey
}
await TaskApi.returnTask(data)
popOverVisible.value.return = false
returnFormRef.value.resetFields()
message.success('操作成功')
// 2
reload()
@ -747,19 +828,17 @@ const handleReturn = async () => {
const handleCancel = async () => {
formLoading.value = true
try {
const cancelFormRef = proxy.$refs['formRef']
// 1.1
const elForm = unref(cancelFormRef)
if (!elForm) return
const valid = await elForm.validate()
if (!valid) return
if (!cancelFormRef.value) return
await cancelFormRef.value.validate()
// 1.2
await ProcessInstanceApi.cancelProcessInstanceByStartUser(
props.processInstance.id,
genericForm.value.cancelReason
cancelForm.cancelReason
)
popOverVisible.value.return = false
message.success('操作成功')
cancelFormRef.value.resetFields()
// 2
reload()
} finally {
@ -786,19 +865,17 @@ const getDeleteSignUserLabel = (task: any): string => {
const handlerDeleteSign = async () => {
formLoading.value = true
try {
const deleteFormRef = proxy.$refs['formRef']
// 1.1
const elForm = unref(deleteFormRef)
if (!elForm) return
const valid = await elForm.validate()
if (!valid) return
if (!deleteSignFormRef.value) return
await deleteSignFormRef.value.validate()
// 1.2
const data = {
id: genericForm.value.deleteSignTaskId,
reason: genericForm.value.reason
id: deleteSignForm.deleteSignTaskId,
reason: deleteSignForm.reason
}
await TaskApi.signDeleteTask(data)
message.success('减签成功')
deleteSignFormRef.value.resetFields()
popOverVisible.value.deleteSign = false
// 2
reload()
@ -852,7 +929,6 @@ const getButtonDisplayName = (btnType: OperationButtonType) => {
}
const loadTodoTask = (task: any) => {
genericForm.value = {}
approveForm.value = {}
approveFormFApi.value = {}
runningTask.value = task
@ -866,6 +942,30 @@ const loadTodoTask = (task: any) => {
}
}
/** 校验流程表单 */
const validateNormalForm = async () => {
if (props.processDefinition?.formType === BpmModelFormType.NORMAL) {
let valid = true
try {
await props.normalFormApi?.validate()
} catch {
valid = false;
}
return valid;
} else {
return true;
}
}
/** 从可以编辑的流程表单字段,获取需要修改的流程实例的变量 */
const getUpdatedProcessInstanceVaiables = ()=> {
const variables = {}
props.writableFields.forEach( (field) => {
const fieldValue = props.normalFormApi.getValue(field)
variables[field] = fieldValue;
})
return variables
}
defineExpose({ loadTodoTask })
</script>

View File

@ -25,7 +25,7 @@
</div>
</div>
</template>
<div class="flex flex-col items-start gap2" :id="`activity-task-${activity.id}`">
<div class="flex flex-col items-start gap2" :id="`activity-task-${activity.id}-${index}`">
<!-- 第一行节点名称时间 -->
<div class="flex w-full">
<div class="font-bold"> {{ activity.name }}</div>
@ -113,7 +113,7 @@
</div>
</div>
</div>
<teleport defer :to="`#activity-task-${activity.id}`">
<teleport defer :to="`#activity-task-${activity.id}-${index}`">
<div
v-if="
task.reason &&

View File

@ -49,7 +49,7 @@
class="form-box flex flex-col mb-30px flex-1"
>
<!-- 情况一流程表单 -->
<el-col v-if="processDefinition?.formType === 10">
<el-col v-if="processDefinition?.formType === BpmModelFormType.NORMAL">
<form-create
v-model="detailForm.value"
v-model:api="fApi"
@ -58,7 +58,7 @@
/>
</el-col>
<!-- 情况二业务表单 -->
<div v-if="processDefinition?.formType === 20">
<div v-if="processDefinition?.formType === BpmModelFormType.CUSTOM">
<BusinessFormComponent :id="processInstance.businessKey" />
</div>
</div>
@ -116,6 +116,9 @@
:process-instance="processInstance"
:process-definition="processDefinition"
:userOptions="userOptions"
:normal-form="detailForm"
:normal-form-api="fApi"
:writable-fields="writableFields"
@success="refresh"
/>
</div>
@ -126,7 +129,7 @@
<script lang="ts" setup>
import { formatDate } from '@/utils/formatTime'
import { DICT_TYPE } from '@/utils/dict'
import { BpmModelType } from '@/utils/constants'
import { BpmModelType, BpmModelFormType } from '@/utils/constants'
import { setConfAndFields2 } from '@/utils/formCreate'
import { registerComponent } from '@/utils/routerHelper'
import type { ApiAttrs } from '@form-create/element-ui/types/config'
@ -171,6 +174,8 @@ const detailForm = ref({
value: {}
}) //
const writableFields: Array<string> = [] //
/** 获得详情 */
const getDetail = () => {
getApprovalDetail()
@ -202,11 +207,12 @@ const getApprovalDetail = async () => {
processDefinition.value = data.processDefinition
//
if (processDefinition.value.formType === 10) {
if (processDefinition.value.formType === BpmModelFormType.NORMAL) {
//
const formFieldsPermission = data.formFieldsPermission
if (detailForm.value.rule.length > 0) {
//
writableFields.splice(0)
if (detailForm.value.rule?.length > 0) {
// form-create
detailForm.value.value = processInstance.value.formVariables
} else {
@ -271,6 +277,8 @@ const setFieldPermission = (field: string, permission: string) => {
if (permission === FieldPermissionType.WRITE) {
//@ts-ignore
fApi.value?.disabled(false, field)
//
writableFields.push(field)
}
if (permission === FieldPermissionType.NONE) {
//@ts-ignore
@ -314,6 +322,7 @@ $process-header-height: 194px;
overflow: auto;
.form-scroll-area {
display: flex;
height: calc(
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
$process-header-height - 40px
@ -323,7 +332,6 @@ $process-header-height: 194px;
$process-header-height - 40px
);
overflow: auto;
display: flex;
flex-direction: column;
:deep(.box-card) {

View File

@ -25,13 +25,14 @@
</el-form-item>
<!-- TODO @ tuitujistyle 可以使用 unocss -->
<el-form-item label="" prop="category" :style="{ position: 'absolute', right: '130px' }">
<!-- TODO @tuituji应该选择好分类就触发搜索啦 -->
<el-form-item label="" prop="category" :style="{ position: 'absolute', right: '300px' }">
<!-- TODO @tuituji应该选择好分类就触发搜索啦 RE:done & to check-->
<el-select
v-model="queryParams.category"
placeholder="请选择流程分类"
clearable
class="!w-155px"
@change="handleQuery"
>
<el-option
v-for="category in categoryList"
@ -42,21 +43,38 @@
</el-select>
</el-form-item>
<el-form-item label="" prop="status" :style="{ position: 'absolute', right: '130px' }">
<el-select
v-model="queryParams.status"
placeholder="请选择流程状态"
clearable
class="!w-155px"
@change="handleQuery"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<!-- 高级筛选 -->
<!-- TODO @ tuitujistyle 可以使用 unocss -->
<el-form-item :style="{ position: 'absolute', right: '0px' }">
<el-button v-popover="popoverRef" v-click-outside="onClickOutside" :icon="List">
高级筛选
</el-button>
<el-popover
ref="popoverRef"
trigger="click"
virtual-triggering
:visible="showPopover"
persistent
:width="400"
:show-arrow="false"
placement="bottom-end"
>
<template #reference>
<el-button @click="showPopover = !showPopover">
<Icon icon="ep:plus" class="mr-5px" />高级筛选
</el-button>
</template>
<el-form-item label="流程发起人" class="bold-label" label-position="top" prop="category">
<el-select
v-model="queryParams.category"
@ -86,21 +104,6 @@
class="!w-390px"
/>
</el-form-item>
<el-form-item label="流程状态" class="bold-label" label-position="top" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择流程状态"
clearable
class="!w-390px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
@ -112,8 +115,13 @@
class="!w-240px"
/>
</el-form-item>
<!-- TODO tuituiji参考钉钉1按照清空取消确认排序2右对齐3确认增加 primary -->
<el-form-item class="bold-label" label-position="top">
<el-button @click="handleQuery"> 确认</el-button>
<el-button @click="showPopover = false"> 取消</el-button>
<el-button @click="resetQuery"> 清空</el-button>
</el-form-item>
</el-popover>
<!-- TODO @tuituji这里应该有确认和取消清空搜索条件三个按钮 -->
</el-form-item>
</el-form>
</ContentWrap>
@ -130,7 +138,7 @@
fixed="left"
/>
<!-- TODO @芋艿摘要 -->
<!-- TODO @tuituji流程状态可见需求文档里 -->
<!-- TODO tuituiji参考钉钉1审批中时展示审批任务2非审批中展示状态 -->
<el-table-column label="流程状态" prop="status" width="120">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
@ -198,8 +206,7 @@
</ContentWrap>
</template>
<script lang="ts" setup>
// TODO @tuitujiList <Icon icon="ep:plus" class="mr-5px" />
import { List } from '@element-plus/icons-vue'
// TODO @tuitujiList <Icon icon="ep:plus" class="mr-5px" /> RE:done & to check
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { ElMessageBox } from 'element-plus'
@ -241,6 +248,8 @@ const getList = async () => {
}
}
const showPopover = ref(false)
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
@ -273,7 +282,7 @@ const handleCreate = async (row?: ProcessInstanceVO) => {
}
/** 查看详情 */
const handleDetail = (row) => {
const handleDetail = (row: ProcessInstanceVO) => {
router.push({
name: 'BpmProcessInstanceDetail',
query: {
@ -283,7 +292,7 @@ const handleDetail = (row) => {
}
/** 取消按钮操作 */
const handleCancel = async (row) => {
const handleCancel = async (row: ProcessInstanceVO) => {
//
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
confirmButtonText: t('common.ok'),
@ -298,15 +307,6 @@ const handleCancel = async (row) => {
await getList()
}
// TODO @tuituji import
import { ClickOutside as vClickOutside } from 'element-plus'
// TODO @tuitujionClickAdvancedSearch
const popoverRef = ref()
const onClickOutside = () => {
unref(popoverRef).popperRef?.delayHide?.()
}
/** 激活时 **/
onActivated(() => {
getList()

View File

@ -1,6 +1,15 @@
<template>
<ContentWrap :bodyStyle="{ padding: '20px 16px' }">
<SimpleProcessDesigner :model-id="modelId" @success="close" />
<SimpleProcessDesigner
:model-id="modelId"
:model-key="modelKey"
:model-name="modelName"
:value="currentValue"
@success="handleSuccess"
@init-finished="handleInit"
:start-user-ids="startUserIds"
ref="designerRef"
/>
</ContentWrap>
</template>
<script setup lang="ts">
@ -9,11 +18,138 @@ import { SimpleProcessDesigner } from '@/components/SimpleProcessDesignerV2/src/
defineOptions({
name: 'SimpleModelDesign'
})
const router = useRouter() //
const { query } = useRoute() //
const modelId = query.modelId as string
const close = () => {
router.push({ path: '/bpm/manager/model' })
const props = defineProps<{
modelId?: string
modelKey?: string
modelName?: string
value?: string
startUserIds?: number[]
}>()
const emit = defineEmits(['success', 'init-finished'])
const designerRef = ref()
const isInitialized = ref(false)
const currentValue = ref('')
//
const initOrUpdateValue = async () => {
console.log('initOrUpdateValue', props.value)
if (props.value) {
currentValue.value = props.value
//
if (isInitialized.value && designerRef.value) {
try {
await designerRef.value.loadProcessData(props.value)
await nextTick()
if (designerRef.value.refresh) {
await designerRef.value.refresh()
}
} catch (error) {
console.error('加载流程数据失败:', error)
}
}
}
}
//
watch(
[() => props.modelKey, () => props.modelName, () => props.value],
async ([newKey, newName, newValue], [oldKey, oldName, oldValue]) => {
if (designerRef.value && isInitialized.value) {
try {
if (newKey && newName && (newKey !== oldKey || newName !== oldName)) {
await designerRef.value.updateModel(newKey, newName)
}
if (newValue && newValue !== oldValue) {
currentValue.value = newValue
await designerRef.value.loadProcessData(newValue)
await nextTick()
if (designerRef.value.refresh) {
await designerRef.value.refresh()
}
}
} catch (error) {
console.error('更新流程数据失败:', error)
}
}
},
{ deep: true, immediate: true }
)
//
const handleInit = async () => {
try {
isInitialized.value = true
emit('init-finished')
// tick
await nextTick()
//
if (props.modelKey && props.modelName) {
await designerRef.value.updateModel(props.modelKey, props.modelName)
}
if (props.value) {
currentValue.value = props.value
await designerRef.value.loadProcessData(props.value)
//
await nextTick()
if (designerRef.value.refresh) {
await designerRef.value.refresh()
}
}
} catch (error) {
console.error('初始化流程数据失败:', error)
}
}
//
const handleSuccess = (data?: any) => {
console.warn('handleSuccess', data)
if (data && data !== currentValue.value) {
currentValue.value = data
emit('success', data)
}
}
/** 获取当前流程数据 */
const getCurrentFlowData = async () => {
try {
if (designerRef.value) {
const data = await designerRef.value.getCurrentFlowData()
if (data) {
currentValue.value = data
}
return data
}
return currentValue.value || undefined
} catch (error) {
console.error('获取流程数据失败:', error)
return currentValue.value || undefined
}
}
//
onMounted(() => {
initOrUpdateValue()
})
//
onBeforeUnmount(async () => {
try {
const data = await getCurrentFlowData()
if (data) {
emit('success', data)
}
} catch (error) {
console.error('保存数据失败:', error)
}
})
defineExpose({
getCurrentFlowData,
refresh: () => designerRef.value?.refresh?.()
})
</script>
<style lang="scss" scoped></style>

View File

@ -16,7 +16,7 @@
class="-mb-15px"
label-width="68px"
>
<el-form-item label="任务名称" prop="name">
<el-form-item label="" prop="name">
<el-input
v-model="queryParams.name"
class="!w-240px"
@ -25,27 +25,96 @@
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
end-placeholder="结束日期"
start-placeholder="开始日期"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon class="mr-5px" icon="ep:search" />
搜索
</el-button>
<el-button @click="resetQuery">
<Icon class="mr-5px" icon="ep:refresh" />
重置
</el-button>
</el-form-item>
<el-form-item label="" prop="category" :style="{ position: 'absolute', right: '300px' }">
<el-select
v-model="queryParams.category"
placeholder="请选择流程分类"
clearable
class="!w-155px"
@change="handleQuery"
>
<el-option
v-for="category in categoryList"
:key="category.code"
:label="category.name"
:value="category.code"
/>
</el-select>
</el-form-item>
<el-form-item label="" prop="status" :style="{ position: 'absolute', right: '130px' }">
<el-select
v-model="queryParams.status"
placeholder="请选择流程状态"
clearable
class="!w-155px"
@change="handleQuery"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<!-- 高级筛选 -->
<el-form-item :style="{ position: 'absolute', right: '0px' }">
<el-popover
:visible="showPopover"
persistent
:width="400"
:show-arrow="false"
placement="bottom-end"
>
<template #reference>
<el-button @click="showPopover = !showPopover" >
<Icon icon="ep:plus" class="mr-5px" />高级筛选
</el-button>
</template>
<el-form-item label="流程发起人" class="bold-label" label-position="top" prop="category">
<el-select
v-model="queryParams.category"
placeholder="请选择流程发起人"
clearable
class="!w-390px"
>
<el-option
v-for="category in categoryList"
:key="category.code"
:label="category.name"
:value="category.code"
/>
</el-select>
</el-form-item>
<el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item class="bold-label" label-position="top">
<el-button @click="handleQuery"> 确认</el-button>
<el-button @click="showPopover = false"> 取消</el-button>
<el-button @click="resetQuery"> 清空</el-button>
</el-form-item>
</el-popover>
</el-form-item>
</el-form>
</ContentWrap>
@ -110,9 +179,10 @@
</ContentWrap>
</template>
<script lang="ts" setup>
import { DICT_TYPE } from '@/utils/dict'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
import * as TaskApi from '@/api/bpm/task'
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
defineOptions({ name: 'BpmTodoTask' })
@ -125,9 +195,13 @@ const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: '',
category: undefined,
status: undefined,
createTime: []
})
const queryFormRef = ref() //
const categoryList = ref<CategoryVO[]>([]) //
const showPopover = ref(false)
/** 查询任务列表 */
const getList = async () => {
@ -165,7 +239,8 @@ const handleAudit = (row: any) => {
}
/** 初始化 **/
onMounted(() => {
getList()
onMounted(async () => {
await getList()
categoryList.value = await CategoryApi.getCategorySimpleList()
})
</script>

View File

@ -16,7 +16,7 @@
class="-mb-15px"
label-width="68px"
>
<el-form-item label="任务名称" prop="name">
<el-form-item label="" prop="name">
<el-input
v-model="queryParams.name"
class="!w-240px"
@ -25,27 +25,79 @@
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
end-placeholder="结束日期"
start-placeholder="开始日期"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon class="mr-5px" icon="ep:search" />
搜索
</el-button>
<el-button @click="resetQuery">
<Icon class="mr-5px" icon="ep:refresh" />
重置
</el-button>
</el-form-item>
<el-form-item label="" prop="category" :style="{ position: 'absolute', right: '130px' }">
<el-select
v-model="queryParams.category"
placeholder="请选择流程分类"
clearable
class="!w-155px"
@change="handleQuery"
>
<el-option
v-for="category in categoryList"
:key="category.code"
:label="category.name"
:value="category.code"
/>
</el-select>
</el-form-item>
<!-- 高级筛选 -->
<el-form-item :style="{ position: 'absolute', right: '0px' }">
<el-popover
:visible="showPopover"
persistent
:width="400"
:show-arrow="false"
placement="bottom-end"
>
<template #reference>
<el-button @click="showPopover = !showPopover" >
<Icon icon="ep:plus" class="mr-5px" />高级筛选
</el-button>
</template>
<el-form-item label="流程发起人" class="bold-label" label-position="top" prop="category">
<el-select
v-model="queryParams.category"
placeholder="请选择流程发起人"
clearable
class="!w-390px"
>
<el-option
v-for="category in categoryList"
:key="category.code"
:label="category.name"
:value="category.code"
/>
</el-select>
</el-form-item>
<el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item class="bold-label" label-position="top">
<el-button @click="handleQuery"> 确认</el-button>
<el-button @click="showPopover = false"> 取消</el-button>
<el-button @click="resetQuery"> 清空</el-button>
</el-form-item>
</el-popover>
</el-form-item>
</el-form>
</ContentWrap>
@ -95,6 +147,7 @@
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import * as TaskApi from '@/api/bpm/task'
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
defineOptions({ name: 'BpmTodoTask' })
@ -107,9 +160,11 @@ const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: '',
category: undefined,
createTime: []
})
const queryFormRef = ref() //
const categoryList = ref<CategoryVO[]>([]) //
/** 查询任务列表 */
const getList = async () => {
@ -123,6 +178,8 @@ const getList = async () => {
}
}
const showPopover = ref(false)
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
@ -147,7 +204,8 @@ const handleAudit = (row: any) => {
}
/** 初始化 **/
onMounted(() => {
getList()
onMounted(async () => {
await getList()
categoryList.value = await CategoryApi.getCategorySimpleList()
})
</script>

View File

@ -101,7 +101,7 @@
<el-input
disabled
v-model="formData.totalProductPrice"
:formatter="erpPriceTableColumnFormatter"
:formatter="erpPriceInputFormatter"
/>
</el-form-item>
</el-col>
@ -123,7 +123,7 @@
disabled
v-model="formData.totalPrice"
placeholder="请输入商机金额"
:formatter="erpPriceTableColumnFormatter"
:formatter="erpPriceInputFormatter"
/>
</el-form-item>
</el-col>
@ -142,7 +142,7 @@ import * as CustomerApi from '@/api/crm/customer'
import * as UserApi from '@/api/system/user'
import { useUserStore } from '@/store/modules/user'
import BusinessProductForm from './components/BusinessProductForm.vue'
import { erpPriceMultiply, erpPriceTableColumnFormatter } from '@/utils'
import { erpPriceMultiply, erpPriceInputFormatter } from '@/utils'
const { t } = useI18n() //
const message = useMessage() //

View File

@ -159,7 +159,7 @@
<el-input
disabled
v-model="formData.totalProductPrice"
:formatter="erpPriceTableColumnFormatter"
:formatter="erpPriceInputFormatter"
/>
</el-form-item>
</el-col>
@ -181,7 +181,7 @@
disabled
v-model="formData.totalPrice"
placeholder="请输入商机金额"
:formatter="erpPriceTableColumnFormattere"
:formatter="erpPriceInputFormatter"
/>
</el-form-item>
</el-col>
@ -199,7 +199,7 @@ import * as ContractApi from '@/api/crm/contract'
import * as UserApi from '@/api/system/user'
import * as ContactApi from '@/api/crm/contact'
import * as BusinessApi from '@/api/crm/business'
import { erpPriceMultiply, erpPriceTableColumnFormatter } from '@/utils'
import { erpPriceMultiply, erpPriceInputFormatter } from '@/utils'
import { useUserStore } from '@/store/modules/user'
import ContractProductForm from '@/views/crm/contract/components/ContractProductForm.vue'

View File

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

View File

@ -151,7 +151,6 @@ import * as BargainActivityApi from '@/api/mall/promotion/bargain/bargainActivit
import BargainActivityForm from './BargainActivityForm.vue'
import { formatDate } from '@/utils/formatTime'
import { fenToYuanFormat } from '@/utils/formatter'
import { closeBargainActivity } from '@/api/mall/promotion/bargain/bargainActivity'
defineOptions({ name: 'PromotionBargainActivity' })

View File

@ -2,18 +2,18 @@
<DiyEditor
v-if="formData && !formLoading"
v-model="currentFormData!.property"
:title="templateItems[selectedTemplateItem].name"
:libs="libs"
:preview-url="previewUrl"
:show-navigation-bar="selectedTemplateItem !== 0"
:show-page-config="selectedTemplateItem !== 0"
:show-tab-bar="selectedTemplateItem === 0"
:show-navigation-bar="selectedTemplateItem !== 0"
:preview-url="previewUrl"
@save="submitForm"
:title="templateItems[selectedTemplateItem].name"
@reset="handleEditorReset"
@save="submitForm"
>
<template #toolBarLeft>
<el-radio-group
v-model="selectedTemplateItem"
:model-value="selectedTemplateItem"
class="h-full!"
@change="handleTemplateItemChange"
>
@ -26,13 +26,14 @@
</template>
</DiyEditor>
</template>
<script setup lang="ts">
<script lang="ts" setup>
// TODO @ decorate index.vue
import * as DiyTemplateApi from '@/api/mall/promotion/diy/template'
import * as DiyPageApi from '@/api/mall/promotion/diy/page'
import { useTagsViewStore } from '@/store/modules/tagsView'
import { DiyComponentLibrary, PAGE_LIBS } from '@/components/DiyEditor/util' // DIY DiyEditor
import { toNumber } from 'lodash-es'
import { isEmpty } from '@/utils/is'
/** 装修模板表单 */
defineOptions({ name: 'DiyTemplateDecorate' })
@ -52,6 +53,10 @@ const formData = ref<DiyTemplateApi.DiyTemplatePropertyVO>()
const formRef = ref() // Ref
//
const currentFormData = ref<DiyTemplateApi.DiyTemplatePropertyVO | DiyPageApi.DiyPageVO>()
// templateItem
const currentFormDataMap = ref<
Map<string, DiyTemplateApi.DiyTemplatePropertyVO | DiyPageApi.DiyPageVO>
>(new Map())
// H5
const previewUrl = ref('')
@ -60,8 +65,6 @@ const getPageDetail = async (id: any) => {
formLoading.value = true
try {
formData.value = await DiyTemplateApi.getDiyTemplateProperty(id)
currentFormData.value = formData.value
//
const domain = import.meta.env.VITE_MALL_H5_DOMAIN
previewUrl.value = `${domain}/#/pages/index/index?templateId=${formData.value.id}`
@ -75,19 +78,31 @@ const templateLibs = [] as DiyComponentLibrary[]
//
const libs = ref<DiyComponentLibrary[]>(templateLibs)
//
const handleTemplateItemChange = () => {
const handleTemplateItemChange = (val: number) => {
//
currentFormDataMap.value.set(
templateItems[selectedTemplateItem.value].name,
currentFormData.value!
)
//
const data = currentFormDataMap.value.get(templateItems[val].name)
//
selectedTemplateItem.value = val
//
if (selectedTemplateItem.value === 0) {
if (val === 0) {
libs.value = templateLibs
currentFormData.value = formData.value
currentFormData.value = isEmpty(data) ? formData.value : data
return
}
//
libs.value = PAGE_LIBS
currentFormData.value = formData.value!.pages.find(
(page: DiyPageApi.DiyPageVO) => page.name === templateItems[selectedTemplateItem.value].name
currentFormData.value = isEmpty(data)
? formData.value!.pages.find(
(page: DiyPageApi.DiyPageVO) => page.name === templateItems[val].name
)
: data
}
//
@ -97,12 +112,25 @@ const submitForm = async () => {
//
formLoading.value = true
try {
if (selectedTemplateItem.value === 0) {
// templateItems templateItem
for (let i = 0; i < templateItems.length; i++) {
const data = currentFormDataMap.value.get(templateItems[i].name) as any
//
if (i === 0) {
//
await DiyTemplateApi.updateDiyTemplateProperty(unref(formData)!)
} else {
await DiyTemplateApi.updateDiyTemplateProperty(isEmpty(data) ? unref(formData)! : data)
continue
}
//
//
if (currentFormData.value?.name.includes(templateItems[i].name)) {
await DiyPageApi.updateDiyPageProperty(unref(currentFormData)!)
continue
}
//
if (!isEmpty(data)) {
await DiyPageApi.updateDiyPageProperty(data!)
}
}
message.success('保存成功')
} finally {
@ -140,10 +168,16 @@ const recoverPageIndex = () => {
const pageIndex = toNumber(sessionStorage.getItem(DIY_PAGE_INDEX_KEY)) || 0
//
sessionStorage.removeItem(DIY_PAGE_INDEX_KEY)
//
currentFormData.value = formData.value
currentFormDataMap.value = new Map<
string,
DiyTemplateApi.DiyTemplatePropertyVO | DiyPageApi.DiyPageVO
>()
//
if (pageIndex !== selectedTemplateItem.value) {
selectedTemplateItem.value = pageIndex
handleTemplateItemChange()
handleTemplateItemChange(pageIndex)
}
}
//#endregion

View File

@ -10,7 +10,7 @@
</template>
<!-- 排行列表 -->
<el-table v-loading="loading" :data="list" @sort-change="handleSortChange">
<el-table-column label="商品ID" prop="spuId" min-width="70" />
<el-table-column label="商品 ID" prop="spuId" min-width="70" />
<el-table-column label="商品图片" align="center" prop="picUrl" width="80">
<template #default="{ row }">
<el-image
@ -27,7 +27,13 @@
<el-table-column label="加购件数" prop="cartCount" min-width="105" sortable="custom" />
<el-table-column label="下单件数" prop="orderCount" min-width="105" sortable="custom" />
<el-table-column label="支付件数" prop="orderPayCount" min-width="105" sortable="custom" />
<el-table-column label="支付金额" prop="orderPayPrice" min-width="105" sortable="custom" />
<el-table-column
label="支付金额"
prop="orderPayPrice"
min-width="105"
sortable="custom"
:formatter="fenToYuanFormat"
/>
<el-table-column label="收藏数" prop="favoriteCount" min-width="90" sortable="custom" />
<el-table-column
label="访客-支付转化率(%)"
@ -50,6 +56,7 @@
import { ProductStatisticsApi, ProductStatisticsVO } from '@/api/mall/statistics/product'
import { CardTitle } from '@/components/Card'
import { buildSortingField } from '@/utils'
import { fenToYuanFormat } from '@/utils/formatter'
/** 商品排行 */
defineOptions({ name: 'ProductRank' })

View File

@ -1,27 +1,27 @@
<template>
<Dialog v-model="dialogVisible" title="推广列表" width="75%">
<Dialog v-model="dialogVisible" title="推广订单列表" width="75%">
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
:model="queryParams"
class="-mb-15px"
label-width="85px"
>
<el-form-item label="用户类型" prop="level">
<el-radio-group v-model="queryParams.level" @change="handleQuery">
<el-radio-button checked>全部</el-radio-button>
<el-radio-button value="1">一级推广人</el-radio-button>
<el-radio-button value="2">二级推广人</el-radio-button>
<el-form-item label="用户类型" prop="sourceUserLevel">
<el-radio-group v-model="queryParams.sourceUserLevel" @change="handleQuery">
<el-radio-button :value="0">全部</el-radio-button>
<el-radio-button :value="1">一级推广人</el-radio-button>
<el-radio-button :value="2">二级推广人</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择状态"
clearable
class="!w-240px"
clearable
placeholder="请选择状态"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BROKERAGE_RECORD_STATUS)"
@ -34,64 +34,70 @@
<el-form-item label="绑定时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
end-placeholder="结束日期"
start-placeholder="开始日期"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button @click="handleQuery">
<Icon class="mr-5px" icon="ep:search" />
搜索
</el-button>
<el-button @click="resetQuery">
<Icon class="mr-5px" icon="ep:refresh" />
重置
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="订单编号" align="center" prop="bizId" min-width="80px" />
<el-table-column label="用户编号" align="center" prop="sourceUserId" min-width="80px" />
<el-table-column label="头像" align="center" prop="sourceUserAvatar" width="70px">
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
<el-table-column align="center" label="订单编号" min-width="80px" prop="bizId" />
<el-table-column align="center" label="用户编号" min-width="80px" prop="sourceUserId" />
<el-table-column align="center" label="头像" prop="sourceUserAvatar" width="70px">
<template #default="scope">
<el-avatar :src="scope.row.sourceUserAvatar" />
</template>
</el-table-column>
<el-table-column label="昵称" align="center" prop="sourceUserNickname" min-width="80px" />
<el-table-column align="center" label="昵称" min-width="80px" prop="sourceUserNickname" />
<el-table-column
label="佣金"
align="center"
prop="price"
min-width="100px"
:formatter="fenToYuanFormat"
align="center"
label="佣金"
min-width="100px"
prop="price"
/>
<el-table-column label="状态" align="center" prop="status" min-width="85">
<el-table-column align="center" label="状态" min-width="85" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BROKERAGE_RECORD_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
align="center"
label="创建时间"
prop="createTime"
width="180px"
/>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList"
/>
</ContentWrap>
</Dialog>
</template>
<script setup lang="ts">
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import * as BrokerageRecordApi from '@/api/mall/trade/brokerage/record'
import { BrokerageRecordBizTypeEnum } from '@/utils/constants'
@ -101,8 +107,6 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
/** 推广订单列表 */
defineOptions({ name: 'BrokerageOrderListDialog' })
const message = useMessage() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
@ -111,7 +115,7 @@ const queryParams = reactive({
pageSize: 10,
userId: null,
bizType: BrokerageRecordBizTypeEnum.ORDER.type,
level: '',
sourceUserLevel: 0,
createTime: [],
status: null
})
@ -130,7 +134,11 @@ defineExpose({ open }) // 提供 open 方法,用于打开弹窗
const getList = async () => {
loading.value = true
try {
const data = await BrokerageRecordApi.getBrokerageRecordPage(queryParams)
//
const data = await BrokerageRecordApi.getBrokerageRecordPage({
...queryParams,
sourceUserLevel: queryParams.sourceUserLevel === 0 ? undefined : queryParams.sourceUserLevel
})
list.value = data.list
total.value = data.total
} finally {

View File

@ -0,0 +1,161 @@
<template>
<Dialog v-model="dialogVisible" title="创建分销员" width="800">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="90"
>
<el-row :gutter="20">
<el-col :span="12" :xs="24">
<el-form-item label="分销员" prop="userId">
<el-input
v-model="formData.userId"
v-loading="formLoading"
placeholder="请输入分销员编号"
>
<template #append>
<el-button @click="handleGetUser(formData.userId, '分销员')">
<Icon class="mr-5px" icon="ep:search" />
</el-button>
</template>
</el-input>
</el-form-item>
<!-- 展示分销员的信息 -->
<el-descriptions v-if="userInfo.user" :column="1" border>
<el-descriptions-item label="头像">
<el-avatar :src="userInfo.user?.avatar" />
</el-descriptions-item>
<el-descriptions-item label="昵称">{{ userInfo.user?.nickname }}</el-descriptions-item>
</el-descriptions>
</el-col>
<el-col :span="12" :xs="24">
<el-form-item label="上级推广人" prop="bindUserId">
<el-input
v-model="formData.bindUserId"
v-loading="formLoading"
placeholder="请输入推广员编号"
>
<template #append>
<el-button @click="handleGetUser(formData.bindUserId, '推广员')">
<Icon class="mr-5px" icon="ep:search" />
</el-button>
</template>
</el-input>
</el-form-item>
<!-- 展示上级推广人的信息 -->
<el-descriptions v-if="userInfo.bindUser" :column="1" border>
<el-descriptions-item label="头像">
<el-avatar :src="userInfo.bindUser?.avatar" />
</el-descriptions-item>
<el-descriptions-item label="昵称"
>{{ userInfo.bindUser?.nickname }}
</el-descriptions-item>
<el-descriptions-item label="推广资格">
<el-tag v-if="userInfo.bindUser?.brokerageEnabled"></el-tag>
<el-tag v-else type="info"></el-tag>
</el-descriptions-item>
<el-descriptions-item label="成为推广员的时间">
{{ formatDate(userInfo.bindUser?.brokerageTime) }}
</el-descriptions-item>
</el-descriptions>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import * as BrokerageUserApi from '@/api/mall/trade/brokerage/user'
import * as UserApi from '@/api/member/user'
import { formatDate } from '@/utils/formatTime'
defineOptions({ name: 'BrokerageUserCreateForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const formLoading = ref(false) // 12
const formData = ref({
userId: undefined,
bindUserId: undefined
})
const formRef = ref() // Ref
const formRules = reactive({
userId: [{ required: true, message: '分销员不能为空', trigger: 'blur' }]
})
/** 打开弹窗 */
const open = async () => {
resetForm()
dialogVisible.value = true
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
/** 创建分销员 */
const submitForm = async () => {
if (formLoading.value) return
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
//
await BrokerageUserApi.createBrokerageUser(formData.value)
message.success(t('common.createSuccess'))
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formRef.value?.resetFields()
formData.value = {
userId: undefined,
bindUserId: undefined
}
userInfo.bindUser = undefined
userInfo.user = undefined
}
/** 查询推广员和分销员 */
const userInfo = reactive<{
bindUser: BrokerageUserApi.BrokerageUserVO | undefined
user: BrokerageUserApi.BrokerageUserVO | undefined
}>({
bindUser: undefined,
user: undefined
})
const handleGetUser = async (id: any, userType: string) => {
if (!id) {
message.warning(`请先输入${userType}编号后重试!!!`)
return
}
if (userType === '推广员' && formData.value.bindUserId == formData.value.userId) {
message.error('不能绑定自己为推广人')
return
}
const user =
userType === '推广员' ? await BrokerageUserApi.getBrokerageUser(id) : await UserApi.getUser(id)
userType === '推广员' ? (userInfo.bindUser = user) : (userInfo.user = user)
if (!user) {
message.warning(`${userType}不存在`)
}
}
</script>

View File

@ -43,8 +43,8 @@
import * as BrokerageUserApi from '@/api/mall/trade/brokerage/user'
import { formatDate } from '@/utils/formatTime'
/** 修改上级推广人表单 */
defineOptions({ name: 'UpdateBindUserForm' })
/** 修改分销用户 */
defineOptions({ name: 'BrokerageUserUpdateForm' })
const { t } = useI18n() //
const message = useMessage() //

View File

@ -4,19 +4,19 @@
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
:model="queryParams"
class="-mb-15px"
label-width="85px"
>
<el-form-item label="推广员编号" prop="bindUserId">
<el-input
v-model="queryParams.bindUserId"
placeholder="请输入推广员编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
clearable
placeholder="请输入推广员编号"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="推广资格" prop="brokerageEnabled">
@ -26,111 +26,126 @@
clearable
placeholder="请选择推广资格"
>
<el-option label="有" :value="true" />
<el-option label="无" :value="false" />
<el-option :value="true" label="有" />
<el-option :value="false" label="无" />
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
end-placeholder="结束日期"
start-placeholder="开始日期"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button @click="handleQuery">
<Icon class="mr-5px" icon="ep:search" />
搜索
</el-button>
<el-button @click="resetQuery">
<Icon class="mr-5px" icon="ep:refresh" />
重置
</el-button>
<el-button
v-hasPermi="['trade:brokerage-user:create']"
plain
type="primary"
@click="openCreateUserForm"
>
<Icon class="mr-5px" icon="ep:plus" />
新增
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="用户编号" align="center" prop="id" min-width="80px" />
<el-table-column label="头像" align="center" prop="avatar" width="70px">
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
<el-table-column align="center" label="用户编号" min-width="80px" prop="id" />
<el-table-column align="center" label="头像" prop="avatar" width="70px">
<template #default="scope">
<el-avatar :src="scope.row.avatar" />
</template>
</el-table-column>
<el-table-column label="昵称" align="center" prop="nickname" min-width="80px" />
<el-table-column label="推广人数" align="center" prop="brokerageUserCount" width="80px" />
<el-table-column align="center" label="昵称" min-width="80px" prop="nickname" />
<el-table-column align="center" label="推广人数" prop="brokerageUserCount" width="80px" />
<el-table-column
align="center"
label="推广订单数量"
align="center"
min-width="110px"
prop="brokerageOrderCount"
min-width="110px"
/>
<el-table-column
:formatter="fenToYuanFormat"
align="center"
label="推广订单金额"
align="center"
prop="brokerageOrderPrice"
min-width="110px"
:formatter="fenToYuanFormat"
prop="brokerageOrderPrice"
/>
<el-table-column
:formatter="fenToYuanFormat"
align="center"
label="已提现金额"
align="center"
min-width="100px"
prop="withdrawPrice"
min-width="100px"
:formatter="fenToYuanFormat"
/>
<el-table-column label="已提现次数" align="center" prop="withdrawCount" min-width="100px" />
<el-table-column align="center" label="已提现次数" min-width="100px" prop="withdrawCount" />
<el-table-column
:formatter="fenToYuanFormat"
align="center"
label="未提现金额"
align="center"
prop="price"
min-width="100px"
:formatter="fenToYuanFormat"
prop="price"
/>
<el-table-column
label="冻结中佣金"
align="center"
prop="frozenPrice"
min-width="100px"
:formatter="fenToYuanFormat"
align="center"
label="冻结中佣金"
min-width="100px"
prop="frozenPrice"
/>
<el-table-column label="推广资格" align="center" prop="brokerageEnabled" min-width="80px">
<el-table-column align="center" label="推广资格" min-width="80px" prop="brokerageEnabled">
<template #default="scope">
<el-switch
v-model="scope.row.brokerageEnabled"
:disabled="!checkPermi(['trade:brokerage-user:update-bind-user'])"
active-text="有"
inactive-text="无"
inline-prompt
:disabled="!checkPermi(['trade:brokerage-user:update-bind-user'])"
@change="handleBrokerageEnabledChange(scope.row)"
/>
</template>
</el-table-column>
<el-table-column
:formatter="dateFormatter"
align="center"
label="成为推广员时间"
align="center"
prop="brokerageTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="上级推广员编号" align="center" prop="bindUserId" width="150px" />
<el-table-column align="center" label="上级推广员编号" prop="bindUserId" width="150px" />
<el-table-column
label="推广员绑定时间"
align="center"
prop="bindUserTime"
:formatter="dateFormatter"
align="center"
label="推广员绑定时间"
prop="bindUserTime"
width="180px"
/>
<el-table-column label="操作" align="center" width="150px" fixed="right">
<el-table-column align="center" fixed="right" label="操作" width="150px">
<template #default="scope">
<el-dropdown
@command="(command) => handleCommand(command, scope.row)"
v-hasPermi="[
'trade:brokerage-user:user-query',
'trade:brokerage-user:order-query',
'trade:brokerage-user:update-bind-user',
'trade:brokerage-user:clear-bind-user'
]"
@command="(command) => handleCommand(command, scope.row)"
>
<el-button link type="primary">
<Icon icon="ep:d-arrow-right" />
@ -139,28 +154,28 @@
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
command="openBrokerageUserTable"
v-if="checkPermi(['trade:brokerage-user:user-query'])"
command="openBrokerageUserTable"
>
推广人
</el-dropdown-item>
<el-dropdown-item
command="openBrokerageOrderTable"
v-if="checkPermi(['trade:brokerage-user:order-query'])"
command="openBrokerageOrderTable"
>
推广订单
</el-dropdown-item>
<el-dropdown-item
command="openUpdateBindUserForm"
v-if="checkPermi(['trade:brokerage-user:update-bind-user'])"
command="openUpdateBindUserForm"
>
修改上级推广人
</el-dropdown-item>
<el-dropdown-item
command="handleClearBindUser"
v-if="
scope.row.bindUserId && checkPermi(['trade:brokerage-user:clear-bind-user'])
"
command="handleClearBindUser"
>
清除上级推广人
</el-dropdown-item>
@ -172,28 +187,31 @@
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList"
/>
</ContentWrap>
<!-- 修改上级推广人表单 -->
<UpdateBindUserForm ref="updateBindUserFormRef" @success="getList" />
<BrokerageUserUpdateForm ref="updateFormRef" @success="getList" />
<!-- 推广人列表 -->
<BrokerageUserListDialog ref="brokerageUserListDialogRef" />
<BrokerageUserListDialog ref="listDialogRef" />
<!-- 推广订单列表 -->
<BrokerageOrderListDialog ref="brokerageOrderListDialogRef" />
<BrokerageOrderListDialog ref="orderDialogRef" />
<!-- 创建分销员 -->
<BrokerageUserCreateForm ref="createFormRef" @success="getList" />
</template>
<script setup lang="ts">
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import * as BrokerageUserApi from '@/api/mall/trade/brokerage/user'
import { checkPermi } from '@/utils/permission'
import { fenToYuanFormat } from '@/utils/formatter'
import UpdateBindUserForm from '@/views/mall/trade/brokerage/user/UpdateBindUserForm.vue'
import BrokerageUserUpdateForm from '@/views/mall/trade/brokerage/user/BrokerageUserUpdateForm.vue'
import BrokerageUserListDialog from '@/views/mall/trade/brokerage/user/BrokerageUserListDialog.vue'
import BrokerageOrderListDialog from '@/views/mall/trade/brokerage/user/BrokerageOrderListDialog.vue'
import BrokerageUserCreateForm from '@/views/mall/trade/brokerage/user/BrokerageUserCreateForm.vue'
defineOptions({ name: 'TradeBrokerageUser' })
@ -253,21 +271,27 @@ const handleCommand = (command: string, row: BrokerageUserApi.BrokerageUserVO) =
}
/** 打开推广人列表 */
const brokerageUserListDialogRef = ref()
const listDialogRef = ref()
const openBrokerageUserTable = (id: number) => {
brokerageUserListDialogRef.value.open(id)
listDialogRef.value.open(id)
}
/** 打开推广订单列表 */
const brokerageOrderListDialogRef = ref()
const orderDialogRef = ref()
const openBrokerageOrderTable = (id: number) => {
brokerageOrderListDialogRef.value.open(id)
orderDialogRef.value.open(id)
}
/** 打开表单:修改上级推广人 */
const updateBindUserFormRef = ref()
const updateFormRef = ref()
const openUpdateBindUserForm = (row: BrokerageUserApi.BrokerageUserVO) => {
updateBindUserFormRef.value.open(row)
updateFormRef.value.open(row)
}
/** 创建分销员 */
const createFormRef = ref<InstanceType<typeof CreateUserForm>>()
const openCreateUserForm = () => {
createFormRef.value?.open()
}
/** 清除上级推广人 */

View File

@ -144,7 +144,7 @@ const accountId = inject<number>('accountId')
// ========== ==========
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' //
const editorConfig = createEditorConfig(UPLOAD_URL, accountId)
const editorConfig = createEditorConfig(UPLOAD_URL, unref(accountId))
// v-model=newsList
const emit = defineEmits<{

View File

@ -8,5 +8,5 @@
<script lang="ts" setup>
defineOptions({ name: 'GoView' })
const src = 'http://127.0.0.1:3000'
const src = ref(import.meta.env.VITE_GOVIEW_URL)
</script>

View File

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

View File

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

1
types/env.d.ts vendored
View File

@ -25,6 +25,7 @@ interface ImportMetaEnv {
readonly VITE_DROP_CONSOLE: string
readonly VITE_SOURCEMAP: string
readonly VITE_OUT_DIR: string
readonly VITE_GOVIEW_URL: string
}
declare global {

View File

@ -44,7 +44,8 @@ export default ({command, mode}: ConfigEnv): UserConfig => {
preprocessorOptions: {
scss: {
additionalData: '@use "@/styles/variables.scss" as *;',
javascriptEnabled: true
javascriptEnabled: true,
silenceDeprecations: ["legacy-js-api"], // 参考自 https://stackoverflow.com/questions/78997907/the-legacy-js-api-is-deprecated-and-will-be-removed-in-dart-sass-2-0-0
}
}
},