Merge branch 'feature/bpm' of https://github.com/GoldenZqqq/yudao-ui-admin-vue3 into feature/bpm
						commit
						3d591912c2
					
				
							
								
								
									
										3
									
								
								.env.dev
								
								
								
								
							
							
						
						
									
										3
									
								
								.env.dev
								
								
								
								
							| 
						 | 
				
			
			@ -32,3 +32,6 @@ VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
 | 
			
		|||
 | 
			
		||||
# 验证码的开关
 | 
			
		||||
VITE_APP_CAPTCHA_ENABLE=true
 | 
			
		||||
 | 
			
		||||
# GoView域名
 | 
			
		||||
VITE_GOVIEW_URL='http://127.0.0.1:3000'
 | 
			
		||||
| 
						 | 
				
			
			@ -29,3 +29,6 @@ VITE_MALL_H5_DOMAIN='http://localhost:3000'
 | 
			
		|||
 | 
			
		||||
# 验证码的开关
 | 
			
		||||
VITE_APP_CAPTCHA_ENABLE=false
 | 
			
		||||
 | 
			
		||||
# GoView域名
 | 
			
		||||
VITE_GOVIEW_URL='http://127.0.0.1:3000'
 | 
			
		||||
| 
						 | 
				
			
			@ -29,3 +29,6 @@ VITE_OUT_DIR=dist-prod
 | 
			
		|||
 | 
			
		||||
# 商城H5会员端域名
 | 
			
		||||
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
 | 
			
		||||
 | 
			
		||||
# GoView域名
 | 
			
		||||
VITE_GOVIEW_URL='http://127.0.0.1:3000'
 | 
			
		||||
| 
						 | 
				
			
			@ -29,3 +29,6 @@ VITE_OUT_DIR=dist-stage
 | 
			
		|||
 | 
			
		||||
# 商城H5会员端域名
 | 
			
		||||
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
 | 
			
		||||
 | 
			
		||||
# GoView域名
 | 
			
		||||
VITE_GOVIEW_URL='http://127.0.0.1:3000'
 | 
			
		||||
| 
						 | 
				
			
			@ -29,3 +29,6 @@ VITE_OUT_DIR=dist-test
 | 
			
		|||
 | 
			
		||||
# 商城H5会员端域名
 | 
			
		||||
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
 | 
			
		||||
 | 
			
		||||
# GoView域名
 | 
			
		||||
VITE_GOVIEW_URL='http://127.0.0.1:3000'
 | 
			
		||||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			@ -38,7 +38,7 @@
 | 
			
		|||
    "animate.css": "^4.1.1",
 | 
			
		||||
    "axios": "^1.6.8",
 | 
			
		||||
    "benz-amr-recorder": "^1.1.5",
 | 
			
		||||
    "bpmn-js-token-simulation": "^0.10.0",
 | 
			
		||||
    "bpmn-js-token-simulation": "^0.36.0",
 | 
			
		||||
    "camunda-bpmn-moddle": "^7.0.1",
 | 
			
		||||
    "cropperjs": "^1.6.1",
 | 
			
		||||
    "crypto-js": "^4.2.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -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",
 | 
			
		||||
| 
						 | 
				
			
			@ -73,6 +73,7 @@
 | 
			
		|||
    "vue-i18n": "9.10.2",
 | 
			
		||||
    "vue-router": "4.4.5",
 | 
			
		||||
    "vue-types": "^5.1.1",
 | 
			
		||||
    "vue3-signature": "^0.2.4",
 | 
			
		||||
    "vuedraggable": "^4.1.0",
 | 
			
		||||
    "web-storage-cache": "^1.1.1",
 | 
			
		||||
    "xml-js": "^1.6.11"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										113
									
								
								pnpm-lock.yaml
								
								
								
								
							
							
						
						
									
										113
									
								
								pnpm-lock.yaml
								
								
								
								
							| 
						 | 
				
			
			@ -45,8 +45,8 @@ dependencies:
 | 
			
		|||
    specifier: ^1.1.5
 | 
			
		||||
    version: 1.1.5
 | 
			
		||||
  bpmn-js-token-simulation:
 | 
			
		||||
    specifier: ^0.10.0
 | 
			
		||||
    version: 0.10.0
 | 
			
		||||
    specifier: ^0.36.0
 | 
			
		||||
    version: 0.36.0
 | 
			
		||||
  camunda-bpmn-moddle:
 | 
			
		||||
    specifier: ^7.0.1
 | 
			
		||||
    version: 7.0.1
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -149,6 +149,9 @@ dependencies:
 | 
			
		|||
  vue-types:
 | 
			
		||||
    specifier: ^5.1.1
 | 
			
		||||
    version: 5.1.3(vue@3.5.12)
 | 
			
		||||
  vue3-signature:
 | 
			
		||||
    specifier: ^0.2.4
 | 
			
		||||
    version: 0.2.4(vue@3.5.12)
 | 
			
		||||
  vuedraggable:
 | 
			
		||||
    specifier: ^4.1.0
 | 
			
		||||
    version: 4.1.0(vue@3.5.12)
 | 
			
		||||
| 
						 | 
				
			
			@ -2122,7 +2125,7 @@ packages:
 | 
			
		|||
      '@form-create/element-ui': 3.2.14(vue@3.5.12)
 | 
			
		||||
      '@form-create/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:
 | 
			
		||||
| 
						 | 
				
			
			@ -4581,12 +4584,14 @@ packages:
 | 
			
		|||
      min-dom: 4.2.1
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /bpmn-js-token-simulation@0.10.0:
 | 
			
		||||
    resolution: {integrity: sha512-QuZQ/KVXKt9Vl+XENyOBoTW2Aw+uKjuBlKdCJL6El7AyM7DkJ5bZkSYURshId1SkBDdYg2mJ1flSmsrhGuSfwg==, tarball: https://registry.npmmirror.com/bpmn-js-token-simulation/-/bpmn-js-token-simulation-0.10.0.tgz}
 | 
			
		||||
  /bpmn-js-token-simulation@0.36.0:
 | 
			
		||||
    resolution: {integrity: sha512-vz+RHlbZCev/6dzk6FhJRz8M0aZ1GL7Xrza0ecWqeg4tHbgPozgyOm3tXTz75XdtOwRVVBzmCjcciXQX7A55wQ==, tarball: https://registry.npmmirror.com/bpmn-js-token-simulation/-/bpmn-js-token-simulation-0.36.0.tgz}
 | 
			
		||||
    engines: {node: '>= 16'}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      min-dash: 3.8.1
 | 
			
		||||
      min-dom: 0.2.0
 | 
			
		||||
      svg.js: 2.7.1
 | 
			
		||||
      inherits-browser: 0.1.0
 | 
			
		||||
      min-dash: 4.2.2
 | 
			
		||||
      min-dom: 4.2.1
 | 
			
		||||
      randomcolor: 0.6.2
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /bpmn-js@17.11.1:
 | 
			
		||||
| 
						 | 
				
			
			@ -4927,51 +4932,13 @@ packages:
 | 
			
		|||
      dot-prop: 5.3.0
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /component-classes@1.2.6:
 | 
			
		||||
    resolution: {integrity: sha512-hPFGULxdwugu1QWW3SvVOCUHLzO34+a2J6Wqy0c5ASQkfi9/8nZcBB0ZohaEbXOQlCflMAEMmEWk7u7BVs4koA==, tarball: https://registry.npmmirror.com/component-classes/-/component-classes-1.2.6.tgz}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      component-indexof: 0.0.3
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /component-closest@0.1.4:
 | 
			
		||||
    resolution: {integrity: sha512-NF9hMj6JKGM5sb6wP/dg7GdJOttaIH9PcTsUNdWcrvu7Kw/5R5swQAFpgaYEHlARrNMyn4Wf7O1PlRej+pt76Q==, tarball: https://registry.npmmirror.com/component-closest/-/component-closest-0.1.4.tgz}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      component-matches-selector: 0.1.7
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /component-delegate@0.2.4:
 | 
			
		||||
    resolution: {integrity: sha512-OlpcB/6Fi+kXQPh/TfXnSvvmrU04ghz7vcJh/jgLF0Ni+I+E3WGlKJQbBGDa5X+kVUG8WxOgjP+8iWbz902fPg==, tarball: https://registry.npmmirror.com/component-delegate/-/component-delegate-0.2.4.tgz}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      component-closest: 0.1.4
 | 
			
		||||
      component-event: 0.1.4
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /component-emitter@1.3.1:
 | 
			
		||||
    resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==, tarball: https://registry.npmmirror.com/component-emitter/-/component-emitter-1.3.1.tgz}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /component-event@0.1.4:
 | 
			
		||||
    resolution: {integrity: sha512-GMwOG8MnUHP1l8DZx1ztFO0SJTFnIzZnBDkXAj8RM2ntV2A6ALlDxgbMY1Fvxlg6WPQ+5IM/a6vg4PEYbjg/Rw==, tarball: https://registry.npmmirror.com/component-event/-/component-event-0.1.4.tgz}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /component-event@0.2.1:
 | 
			
		||||
    resolution: {integrity: sha512-wGA++isMqiDq1jPYeyv2as/Bt/u+3iLW0rEa+8NQ82jAv3TgqMiCM+B2SaBdn2DfLilLjjq736YcezihRYhfxw==, tarball: https://registry.npmmirror.com/component-event/-/component-event-0.2.1.tgz}
 | 
			
		||||
 | 
			
		||||
  /component-indexof@0.0.3:
 | 
			
		||||
    resolution: {integrity: sha512-puDQKvx/64HZXb4hBwIcvQLaLgux8o1CbWl39s41hrIIZDl1lJiD5jc22gj3RBeGK0ovxALDYpIbyjqDUUl0rw==, tarball: https://registry.npmmirror.com/component-indexof/-/component-indexof-0.0.3.tgz}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /component-matches-selector@0.1.7:
 | 
			
		||||
    resolution: {integrity: sha512-Yb2+pVBvrqkQVpPaDBF0DYXRreBveXJNrpJs9FnFu8PF6/5IIcz5oDZqiH9nB5hbD2/TmFVN5ZCxBzqu7yFFYQ==, tarball: https://registry.npmmirror.com/component-matches-selector/-/component-matches-selector-0.1.7.tgz}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      component-query: 0.0.3
 | 
			
		||||
      global-object: 1.0.0
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /component-query@0.0.3:
 | 
			
		||||
    resolution: {integrity: sha512-VgebQseT1hz1Ps7vVp2uaSg+N/gsI5ts3AZUSnN6GMA2M82JH7o+qYifWhmVE/e8w/H48SJuA3nA9uX8zRe95Q==, tarball: https://registry.npmmirror.com/component-query/-/component-query-0.0.3.tgz}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /compute-scroll-into-view@1.0.20:
 | 
			
		||||
    resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==, tarball: https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz}
 | 
			
		||||
    dev: false
 | 
			
		||||
| 
						 | 
				
			
			@ -5521,6 +5488,10 @@ packages:
 | 
			
		|||
    resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, tarball: https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /default-passive-events@2.0.0:
 | 
			
		||||
    resolution: {integrity: sha512-eMtt76GpDVngZQ3ocgvRcNCklUMwID1PaNbCNxfpDXuiOXttSh0HzBbda1HU9SIUsDc02vb7g9+3I5tlqe/qMQ==, tarball: https://registry.npmmirror.com/default-passive-events/-/default-passive-events-2.0.0.tgz}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /define-data-property@1.1.4:
 | 
			
		||||
    resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==, tarball: https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz}
 | 
			
		||||
    engines: {node: '>= 0.4'}
 | 
			
		||||
| 
						 | 
				
			
			@ -5795,8 +5766,8 @@ packages:
 | 
			
		|||
    resolution: {integrity: sha512-nz88NNBsD7kQSAGGJyp8hS6xSPtWwqNogA0mjtc2nUYeEf3nURK9qpV18TuBdDmEDgVWotS8Wkzf+V52dSQ/LQ==, tarball: https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.67.tgz}
 | 
			
		||||
    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:
 | 
			
		||||
| 
						 | 
				
			
			@ -6674,10 +6645,6 @@ packages:
 | 
			
		|||
      global-prefix: 3.0.0
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /global-object@1.0.0:
 | 
			
		||||
    resolution: {integrity: sha512-mSPSkY6UsHv6hgW0V2dfWBWTS8TnPnLx3ECVNoWp6rBI2Bg66VYoqGoTFlH/l7XhAZ/l+StYlntXlt87BEeCcg==, tarball: https://registry.npmmirror.com/global-object/-/global-object-1.0.0.tgz}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /global-prefix@3.0.0:
 | 
			
		||||
    resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==, tarball: https://registry.npmmirror.com/global-prefix/-/global-prefix-3.0.0.tgz}
 | 
			
		||||
    engines: {node: '>=6'}
 | 
			
		||||
| 
						 | 
				
			
			@ -7899,10 +7866,6 @@ packages:
 | 
			
		|||
    engines: {node: '>=18'}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /min-dash@3.8.1:
 | 
			
		||||
    resolution: {integrity: sha512-evumdlmIlg9mbRVPbC4F5FuRhNmcMS5pvuBUbqb1G9v09Ro0ImPEgz5n3khir83lFok1inKqVDjnKEg3GpDxQg==, tarball: https://registry.npmmirror.com/min-dash/-/min-dash-3.8.1.tgz}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /min-dash@4.2.2:
 | 
			
		||||
    resolution: {integrity: sha512-qbhSYUxk6mBaF096B3JOQSumXbKWHenmT97cSpdNzgkWwGjhjhE/KZODCoDNhI2I4C9Cb6R/Q13S4BYkUSXoXQ==, tarball: https://registry.npmmirror.com/min-dash/-/min-dash-4.2.2.tgz}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -7912,18 +7875,6 @@ packages:
 | 
			
		|||
      dom-walk: 0.1.2
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /min-dom@0.2.0:
 | 
			
		||||
    resolution: {integrity: sha512-VmxugbnAcVZGqvepjhOA4d4apmrpX8mMaRS+/jo0dI5Yorzrr4Ru9zc9KVALlY/+XakVCb8iQ+PYXljihQcsNw==, tarball: https://registry.npmmirror.com/min-dom/-/min-dom-0.2.0.tgz}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      component-classes: 1.2.6
 | 
			
		||||
      component-closest: 0.1.4
 | 
			
		||||
      component-delegate: 0.2.4
 | 
			
		||||
      component-event: 0.1.4
 | 
			
		||||
      component-matches-selector: 0.1.7
 | 
			
		||||
      component-query: 0.0.3
 | 
			
		||||
      domify: 1.4.2
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /min-dom@4.2.1:
 | 
			
		||||
    resolution: {integrity: sha512-TMoL8SEEIhUWYgkj7XMSgxmwSyGI+4fP2KFFGnN3FbHfbGHVdsLYSz8LoIsgPhz4dWRmLvxWWSMgzZMJW5sZuA==, tarball: https://registry.npmmirror.com/min-dom/-/min-dom-4.2.1.tgz}
 | 
			
		||||
    dependencies:
 | 
			
		||||
| 
						 | 
				
			
			@ -8714,6 +8665,10 @@ packages:
 | 
			
		|||
    resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, tarball: https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /randomcolor@0.6.2:
 | 
			
		||||
    resolution: {integrity: sha512-Mn6TbyYpFgwFuQ8KJKqf3bqqY9O1y37/0jgSK/61PUxV4QfIMv0+K2ioq8DfOjkBslcjwSzRfIDEXfzA9aCx7A==, tarball: https://registry.npmmirror.com/randomcolor/-/randomcolor-0.6.2.tgz}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /rd@2.0.1:
 | 
			
		||||
    resolution: {integrity: sha512-/XdKU4UazUZTXFmI0dpABt8jSXPWcEyaGdk340KdHnsEOdkTctlX23aAK7ChQDn39YGNlAJr1M5uvaKt4QnpNw==, tarball: https://registry.npmmirror.com/rd/-/rd-2.0.1.tgz}
 | 
			
		||||
    dependencies:
 | 
			
		||||
| 
						 | 
				
			
			@ -9128,6 +9083,10 @@ packages:
 | 
			
		|||
    engines: {node: '>=14'}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /signature_pad@3.0.0-beta.4:
 | 
			
		||||
    resolution: {integrity: sha512-cOf2NhVuTiuNqe2X/ycEmizvCDXk0DoemhsEpnkcGnA4kS5iJYTCqZ9As7tFBbsch45Q1EdX61833+6sjJ8rrw==, tarball: https://registry.npmmirror.com/signature_pad/-/signature_pad-3.0.0-beta.4.tgz}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /sirv@2.0.4:
 | 
			
		||||
    resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==, tarball: https://registry.npmmirror.com/sirv/-/sirv-2.0.4.tgz}
 | 
			
		||||
    engines: {node: '>= 10'}
 | 
			
		||||
| 
						 | 
				
			
			@ -9561,10 +9520,6 @@ packages:
 | 
			
		|||
    resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==, tarball: https://registry.npmmirror.com/svg-tags/-/svg-tags-1.0.0.tgz}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /svg.js@2.7.1:
 | 
			
		||||
    resolution: {integrity: sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==, tarball: https://registry.npmmirror.com/svg.js/-/svg.js-2.7.1.tgz}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /svgo@2.8.0:
 | 
			
		||||
    resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==, tarball: https://registry.npmmirror.com/svgo/-/svgo-2.8.0.tgz}
 | 
			
		||||
    engines: {node: '>=10.13.0'}
 | 
			
		||||
| 
						 | 
				
			
			@ -10324,6 +10279,16 @@ packages:
 | 
			
		|||
      vue: 3.5.12(typescript@5.3.3)
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /vue3-signature@0.2.4(vue@3.5.12):
 | 
			
		||||
    resolution: {integrity: sha512-XFwwFVK9OG3F085pKIq2SlNVqx32WdFH+TXbGEWc5FfEKpx8oMmZuAwZZ50K/pH2FgmJSE8IRwU9DDhrLpd6iA==, tarball: https://registry.npmmirror.com/vue3-signature/-/vue3-signature-0.2.4.tgz}
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
      vue: ^3.2.0
 | 
			
		||||
    dependencies:
 | 
			
		||||
      default-passive-events: 2.0.0
 | 
			
		||||
      signature_pad: 3.0.0-beta.4
 | 
			
		||||
      vue: 3.5.12(typescript@5.3.3)
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /vue@3.5.12(typescript@5.3.3):
 | 
			
		||||
    resolution: {integrity: sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==, tarball: https://registry.npmmirror.com/vue/-/vue-3.5.12.tgz}
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -72,3 +72,7 @@ export const deleteModel = async (id: number) => {
 | 
			
		|||
export const deployModel = async (id: number) => {
 | 
			
		||||
  return await request.post({ url: '/bpm/model/deploy?id=' + id })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const cleanModel = async (id: number) => {
 | 
			
		||||
  return await request.delete({ url: '/bpm/model/clean?id=' + id })
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,6 +36,7 @@ export type ApprovalTaskInfo = {
 | 
			
		|||
  assigneeUser: User
 | 
			
		||||
  status: number
 | 
			
		||||
  reason: string
 | 
			
		||||
  signPicUrl: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 审批节点信息
 | 
			
		||||
| 
						 | 
				
			
			@ -89,7 +90,7 @@ export const getProcessInstanceCopyPage = async (params: any) => {
 | 
			
		|||
 | 
			
		||||
// 获取审批详情
 | 
			
		||||
export const getApprovalDetail = async (params: any) => {
 | 
			
		||||
  return await request.get({ url: 'bpm/process-instance/get-approval-detail' , params })
 | 
			
		||||
  return await request.get({ url: 'bpm/process-instance/get-approval-detail', params })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取表单字段权限
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,11 +22,6 @@ export const register = (data: RegisterVO) => {
 | 
			
		|||
  return request.post({ url: '/system/auth/register', data })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 刷新访问令牌
 | 
			
		||||
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 })
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 })
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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  | 
| 
						 | 
				
			
			@ -2,17 +2,17 @@
 | 
			
		|||
  <div class="h-40px flex items-center justify-center">
 | 
			
		||||
    <MagicCubeEditor
 | 
			
		||||
      v-model="cellList"
 | 
			
		||||
      class="m-b-16px"
 | 
			
		||||
      :rows="1"
 | 
			
		||||
      :cols="cellCount"
 | 
			
		||||
      :cube-size="38"
 | 
			
		||||
      :rows="1"
 | 
			
		||||
      class="m-b-16px"
 | 
			
		||||
      @hot-area-selected="handleHotAreaSelected"
 | 
			
		||||
    />
 | 
			
		||||
    <img src="@/assets/imgs/diy/app-nav-bar-mp.png" alt="" class="h-30px w-76px" v-if="isMp" />
 | 
			
		||||
    <img v-if="isMp" alt="" class="h-30px w-76px" src="@/assets/imgs/diy/app-nav-bar-mp.png" />
 | 
			
		||||
  </div>
 | 
			
		||||
  <template v-for="(cell, cellIndex) in cellList" :key="cellIndex">
 | 
			
		||||
    <template v-if="selectedHotAreaIndex === cellIndex">
 | 
			
		||||
      <el-form-item label="类型" :prop="`cell[${cellIndex}].type`">
 | 
			
		||||
      <el-form-item :prop="`cell[${cellIndex}].type`" label="类型">
 | 
			
		||||
        <el-radio-group v-model="cell.type">
 | 
			
		||||
          <el-radio value="text">文字</el-radio>
 | 
			
		||||
          <el-radio value="image">图片</el-radio>
 | 
			
		||||
| 
						 | 
				
			
			@ -21,37 +21,40 @@
 | 
			
		|||
      </el-form-item>
 | 
			
		||||
      <!-- 1. 文字 -->
 | 
			
		||||
      <template v-if="cell.type === 'text'">
 | 
			
		||||
        <el-form-item label="内容" :prop="`cell[${cellIndex}].text`">
 | 
			
		||||
        <el-form-item :prop="`cell[${cellIndex}].text`" label="内容">
 | 
			
		||||
          <el-input v-model="cell!.text" maxlength="10" show-word-limit />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="颜色" :prop="`cell[${cellIndex}].text`">
 | 
			
		||||
        <el-form-item :prop="`cell[${cellIndex}].text`" label="颜色">
 | 
			
		||||
          <ColorInput v-model="cell!.textColor" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item :prop="`cell[${cellIndex}].url`" label="链接">
 | 
			
		||||
          <AppLinkInput v-model="cell.url" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </template>
 | 
			
		||||
      <!-- 2. 图片 -->
 | 
			
		||||
      <template v-else-if="cell.type === 'image'">
 | 
			
		||||
        <el-form-item label="图片" :prop="`cell[${cellIndex}].imgUrl`">
 | 
			
		||||
        <el-form-item :prop="`cell[${cellIndex}].imgUrl`" label="图片">
 | 
			
		||||
          <UploadImg v-model="cell.imgUrl" :limit="1" height="56px" width="56px">
 | 
			
		||||
            <template #tip>建议尺寸 56*56</template>
 | 
			
		||||
          </UploadImg>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="链接" :prop="`cell[${cellIndex}].url`">
 | 
			
		||||
        <el-form-item :prop="`cell[${cellIndex}].url`" label="链接">
 | 
			
		||||
          <AppLinkInput v-model="cell.url" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </template>
 | 
			
		||||
      <!-- 3. 搜索框 -->
 | 
			
		||||
      <template v-else>
 | 
			
		||||
        <el-form-item label="提示文字" :prop="`cell[${cellIndex}].placeholder`">
 | 
			
		||||
        <el-form-item :prop="`cell[${cellIndex}].placeholder`" label="提示文字">
 | 
			
		||||
          <el-input v-model="cell.placeholder" maxlength="10" show-word-limit />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="圆角" :prop="`cell[${cellIndex}].borderRadius`">
 | 
			
		||||
        <el-form-item :prop="`cell[${cellIndex}].borderRadius`" label="圆角">
 | 
			
		||||
          <el-slider
 | 
			
		||||
            v-model="cell.borderRadius"
 | 
			
		||||
            :max="100"
 | 
			
		||||
            :min="0"
 | 
			
		||||
            show-input
 | 
			
		||||
            input-size="small"
 | 
			
		||||
            :show-input-controls="false"
 | 
			
		||||
            input-size="small"
 | 
			
		||||
            show-input
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </template>
 | 
			
		||||
| 
						 | 
				
			
			@ -59,7 +62,7 @@
 | 
			
		|||
  </template>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { NavigationBarCellProperty } from '../config'
 | 
			
		||||
import { usePropertyForm } from '@/components/DiyEditor/util'
 | 
			
		||||
// 导航栏属性面板
 | 
			
		||||
| 
						 | 
				
			
			@ -87,4 +90,4 @@ const handleHotAreaSelected = (cellValue: NavigationBarCellProperty, index: numb
 | 
			
		|||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss"></style>
 | 
			
		||||
<style lang="scss" scoped></style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +1,7 @@
 | 
			
		|||
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
 | 
			
		||||
import {ComponentStyle, DiyComponent} from '@/components/DiyEditor/util'
 | 
			
		||||
 | 
			
		||||
/** 标题栏属性 */
 | 
			
		||||
export interface TitleBarProperty {
 | 
			
		||||
  // 背景图
 | 
			
		||||
  bgImgUrl: string
 | 
			
		||||
  // 偏移
 | 
			
		||||
  marginLeft: number
 | 
			
		||||
  // 显示位置
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,21 +1,30 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="title-bar">
 | 
			
		||||
    <el-image v-if="property.bgImgUrl" :src="property.bgImgUrl" fit="cover" class="w-full" />
 | 
			
		||||
    <div class="absolute left-0 top-0 w-full">
 | 
			
		||||
  <div
 | 
			
		||||
    :style="{
 | 
			
		||||
      background:
 | 
			
		||||
        property.style.bgType === 'color' ? property.style.bgColor : `url(${property.style.bgImg})`,
 | 
			
		||||
      backgroundSize: '100% 100%',
 | 
			
		||||
      backgroundRepeat: 'no-repeat'
 | 
			
		||||
    }"
 | 
			
		||||
    class="title-bar"
 | 
			
		||||
  >
 | 
			
		||||
    <!-- 内容 -->
 | 
			
		||||
    <div>
 | 
			
		||||
      <!-- 标题 -->
 | 
			
		||||
      <div
 | 
			
		||||
        v-if="property.title"
 | 
			
		||||
        :style="{
 | 
			
		||||
          fontSize: `${property.titleSize}px`,
 | 
			
		||||
          fontWeight: property.titleWeight,
 | 
			
		||||
          color: property.titleColor,
 | 
			
		||||
          textAlign: property.textAlign
 | 
			
		||||
        }"
 | 
			
		||||
        v-if="property.title"
 | 
			
		||||
      >
 | 
			
		||||
        {{ property.title }}
 | 
			
		||||
      </div>
 | 
			
		||||
      <!-- 副标题 -->
 | 
			
		||||
      <div
 | 
			
		||||
        v-if="property.description"
 | 
			
		||||
        :style="{
 | 
			
		||||
          fontSize: `${property.descriptionSize}px`,
 | 
			
		||||
          fontWeight: property.descriptionWeight,
 | 
			
		||||
| 
						 | 
				
			
			@ -23,25 +32,24 @@
 | 
			
		|||
          textAlign: property.textAlign
 | 
			
		||||
        }"
 | 
			
		||||
        class="m-t-8px"
 | 
			
		||||
        v-if="property.description"
 | 
			
		||||
      >
 | 
			
		||||
        {{ property.description }}
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <!-- 更多 -->
 | 
			
		||||
    <div
 | 
			
		||||
      class="more"
 | 
			
		||||
      v-show="property.more.show"
 | 
			
		||||
      :style="{
 | 
			
		||||
        color: property.descriptionColor
 | 
			
		||||
      }"
 | 
			
		||||
      class="more"
 | 
			
		||||
    >
 | 
			
		||||
      <span v-if="property.more.type !== 'icon'"> {{ property.more.text }} </span>
 | 
			
		||||
      <Icon icon="ep:arrow-right" v-if="property.more.type !== 'text'" />
 | 
			
		||||
      <Icon v-if="property.more.type !== 'text'" icon="ep:arrow-right" />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { TitleBarProperty } from './config'
 | 
			
		||||
 | 
			
		||||
/** 标题栏 */
 | 
			
		||||
| 
						 | 
				
			
			@ -49,7 +57,7 @@ defineOptions({ name: 'TitleBar' })
 | 
			
		|||
 | 
			
		||||
defineProps<{ property: TitleBarProperty }>()
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.title-bar {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +1,7 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <ComponentContainerProperty v-model="formData.style">
 | 
			
		||||
    <el-form label-width="85px" :model="formData" :rules="rules">
 | 
			
		||||
      <el-card header="风格" class="property-group" shadow="never">
 | 
			
		||||
        <el-form-item label="背景图片" prop="bgImgUrl">
 | 
			
		||||
          <UploadImg v-model="formData.bgImgUrl" width="100%" height="40px">
 | 
			
		||||
            <template #tip>建议尺寸 750*80</template>
 | 
			
		||||
          </UploadImg>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
    <el-form :model="formData" :rules="rules" label-width="85px">
 | 
			
		||||
      <el-card class="property-group" header="风格" shadow="never">
 | 
			
		||||
        <el-form-item label="标题位置" prop="textAlign">
 | 
			
		||||
          <el-radio-group v-model="formData!.textAlign">
 | 
			
		||||
            <el-tooltip content="居左" placement="top">
 | 
			
		||||
| 
						 | 
				
			
			@ -22,65 +17,65 @@
 | 
			
		|||
          </el-radio-group>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-card>
 | 
			
		||||
      <el-card header="主标题" class="property-group" shadow="never">
 | 
			
		||||
        <el-form-item label="文字" prop="title" label-width="40px">
 | 
			
		||||
      <el-card class="property-group" header="主标题" shadow="never">
 | 
			
		||||
        <el-form-item label="文字" label-width="40px" prop="title">
 | 
			
		||||
          <InputWithColor
 | 
			
		||||
            v-model="formData.title"
 | 
			
		||||
            v-model:color="formData.titleColor"
 | 
			
		||||
            show-word-limit
 | 
			
		||||
            maxlength="20"
 | 
			
		||||
            show-word-limit
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="大小" prop="titleSize" label-width="40px">
 | 
			
		||||
        <el-form-item label="大小" label-width="40px" prop="titleSize">
 | 
			
		||||
          <el-slider
 | 
			
		||||
            v-model="formData.titleSize"
 | 
			
		||||
            :max="60"
 | 
			
		||||
            :min="10"
 | 
			
		||||
            show-input
 | 
			
		||||
            input-size="small"
 | 
			
		||||
            show-input
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="粗细" prop="titleWeight" label-width="40px">
 | 
			
		||||
        <el-form-item label="粗细" label-width="40px" prop="titleWeight">
 | 
			
		||||
          <el-slider
 | 
			
		||||
            v-model="formData.titleWeight"
 | 
			
		||||
            :min="100"
 | 
			
		||||
            :max="900"
 | 
			
		||||
            :min="100"
 | 
			
		||||
            :step="100"
 | 
			
		||||
            show-input
 | 
			
		||||
            input-size="small"
 | 
			
		||||
            show-input
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-card>
 | 
			
		||||
      <el-card header="副标题" class="property-group" shadow="never">
 | 
			
		||||
        <el-form-item label="文字" prop="description" label-width="40px">
 | 
			
		||||
      <el-card class="property-group" header="副标题" shadow="never">
 | 
			
		||||
        <el-form-item label="文字" label-width="40px" prop="description">
 | 
			
		||||
          <InputWithColor
 | 
			
		||||
            v-model="formData.description"
 | 
			
		||||
            v-model:color="formData.descriptionColor"
 | 
			
		||||
            show-word-limit
 | 
			
		||||
            maxlength="50"
 | 
			
		||||
            show-word-limit
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="大小" prop="descriptionSize" label-width="40px">
 | 
			
		||||
        <el-form-item label="大小" label-width="40px" prop="descriptionSize">
 | 
			
		||||
          <el-slider
 | 
			
		||||
            v-model="formData.descriptionSize"
 | 
			
		||||
            :max="60"
 | 
			
		||||
            :min="10"
 | 
			
		||||
            show-input
 | 
			
		||||
            input-size="small"
 | 
			
		||||
            show-input
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="粗细" prop="descriptionWeight" label-width="40px">
 | 
			
		||||
        <el-form-item label="粗细" label-width="40px" prop="descriptionWeight">
 | 
			
		||||
          <el-slider
 | 
			
		||||
            v-model="formData.descriptionWeight"
 | 
			
		||||
            :min="100"
 | 
			
		||||
            :max="900"
 | 
			
		||||
            :min="100"
 | 
			
		||||
            :step="100"
 | 
			
		||||
            show-input
 | 
			
		||||
            input-size="small"
 | 
			
		||||
            show-input
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-card>
 | 
			
		||||
      <el-card header="查看更多" class="property-group" shadow="never">
 | 
			
		||||
      <el-card class="property-group" header="查看更多" shadow="never">
 | 
			
		||||
        <el-form-item label="是否显示" prop="more.show">
 | 
			
		||||
          <el-checkbox v-model="formData.more.show" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
| 
						 | 
				
			
			@ -93,7 +88,7 @@
 | 
			
		|||
              <el-radio value="all">文字+图标</el-radio>
 | 
			
		||||
            </el-radio-group>
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
          <el-form-item label="更多文字" prop="more.text" v-show="formData.more.type !== 'icon'">
 | 
			
		||||
          <el-form-item v-show="formData.more.type !== 'icon'" label="更多文字" prop="more.text">
 | 
			
		||||
            <el-input v-model="formData.more.text" />
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
          <el-form-item label="跳转链接" prop="more.url">
 | 
			
		||||
| 
						 | 
				
			
			@ -104,7 +99,7 @@
 | 
			
		|||
    </el-form>
 | 
			
		||||
  </ComponentContainerProperty>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { TitleBarProperty } from './config'
 | 
			
		||||
import { usePropertyForm } from '@/components/DiyEditor/util'
 | 
			
		||||
// 导航栏属性面板
 | 
			
		||||
| 
						 | 
				
			
			@ -118,4 +113,4 @@ const { formData } = usePropertyForm(props.modelValue, emit)
 | 
			
		|||
const rules = {}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss"></style>
 | 
			
		||||
<style lang="scss" scoped></style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,17 +12,17 @@
 | 
			
		|||
      <el-button-group class="header-right">
 | 
			
		||||
        <el-tooltip content="重置">
 | 
			
		||||
          <el-button @click="handleReset">
 | 
			
		||||
            <Icon icon="system-uicons:reset-alt" :size="24" />
 | 
			
		||||
            <Icon :size="24" icon="system-uicons:reset-alt" />
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </el-tooltip>
 | 
			
		||||
        <el-tooltip content="预览" v-if="previewUrl">
 | 
			
		||||
        <el-tooltip v-if="previewUrl" content="预览">
 | 
			
		||||
          <el-button @click="handlePreview">
 | 
			
		||||
            <Icon icon="ep:view" :size="24" />
 | 
			
		||||
            <Icon :size="24" icon="ep:view" />
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </el-tooltip>
 | 
			
		||||
        <el-tooltip content="保存">
 | 
			
		||||
          <el-button @click="handleSave">
 | 
			
		||||
            <Icon icon="ep:check" :size="24" />
 | 
			
		||||
            <Icon :size="24" icon="ep:check" />
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </el-tooltip>
 | 
			
		||||
      </el-button-group>
 | 
			
		||||
| 
						 | 
				
			
			@ -31,21 +31,21 @@
 | 
			
		|||
    <!-- 中心区域 -->
 | 
			
		||||
    <el-container class="editor-container">
 | 
			
		||||
      <!-- 左侧:组件库(ComponentLibrary) -->
 | 
			
		||||
      <ComponentLibrary ref="componentLibrary" :list="libs" v-if="libs && libs.length > 0" />
 | 
			
		||||
      <ComponentLibrary v-if="libs && libs.length > 0" ref="componentLibrary" :list="libs" />
 | 
			
		||||
      <!-- 中心:设计区域(ComponentContainer) -->
 | 
			
		||||
      <div class="editor-center page-prop-area" @click="handlePageSelected">
 | 
			
		||||
        <!-- 手机顶部 -->
 | 
			
		||||
        <div class="editor-design-top">
 | 
			
		||||
          <!-- 手机顶部状态栏 -->
 | 
			
		||||
          <img src="@/assets/imgs/diy/statusBar.png" alt="" class="status-bar" />
 | 
			
		||||
          <img alt="" class="status-bar" src="@/assets/imgs/diy/statusBar.png" />
 | 
			
		||||
          <!-- 手机顶部导航栏 -->
 | 
			
		||||
          <ComponentContainer
 | 
			
		||||
            v-if="showNavigationBar"
 | 
			
		||||
            :active="selectedComponent?.id === navigationBarComponent.id"
 | 
			
		||||
            :component="navigationBarComponent"
 | 
			
		||||
            :show-toolbar="false"
 | 
			
		||||
            :active="selectedComponent?.id === navigationBarComponent.id"
 | 
			
		||||
            @click="handleNavigationBarSelected"
 | 
			
		||||
            class="cursor-pointer!"
 | 
			
		||||
            @click="handleNavigationBarSelected"
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
        <!-- 绝对定位的组件:例如 弹窗、浮动按钮等 -->
 | 
			
		||||
| 
						 | 
				
			
			@ -55,43 +55,43 @@
 | 
			
		|||
          @click="handleComponentSelected(component, index)"
 | 
			
		||||
        >
 | 
			
		||||
          <component
 | 
			
		||||
            v-if="component.position === 'fixed' && selectedComponent?.uid === component.uid"
 | 
			
		||||
            :is="component.id"
 | 
			
		||||
            v-if="component.position === 'fixed' && selectedComponent?.uid === component.uid"
 | 
			
		||||
            :property="component.property"
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
        <!-- 手机页面编辑区域 -->
 | 
			
		||||
        <el-scrollbar
 | 
			
		||||
          height="100%"
 | 
			
		||||
          wrap-class="editor-design-center page-prop-area"
 | 
			
		||||
          view-class="phone-container"
 | 
			
		||||
          :view-style="{
 | 
			
		||||
            backgroundColor: pageConfigComponent.property.backgroundColor,
 | 
			
		||||
            backgroundImage: `url(${pageConfigComponent.property.backgroundImage})`
 | 
			
		||||
          }"
 | 
			
		||||
          height="100%"
 | 
			
		||||
          view-class="phone-container"
 | 
			
		||||
          wrap-class="editor-design-center page-prop-area"
 | 
			
		||||
        >
 | 
			
		||||
          <draggable
 | 
			
		||||
            class="page-prop-area drag-area"
 | 
			
		||||
            v-model="pageComponents"
 | 
			
		||||
            item-key="index"
 | 
			
		||||
            :animation="200"
 | 
			
		||||
            :force-fallback="true"
 | 
			
		||||
            class="page-prop-area drag-area"
 | 
			
		||||
            filter=".component-toolbar"
 | 
			
		||||
            ghost-class="draggable-ghost"
 | 
			
		||||
            :force-fallback="true"
 | 
			
		||||
            group="component"
 | 
			
		||||
            item-key="index"
 | 
			
		||||
            @change="handleComponentChange"
 | 
			
		||||
          >
 | 
			
		||||
            <template #item="{ element, index }">
 | 
			
		||||
              <ComponentContainer
 | 
			
		||||
                v-if="!element.position || element.position === 'center'"
 | 
			
		||||
                :component="element"
 | 
			
		||||
                :active="selectedComponentIndex === index"
 | 
			
		||||
                :can-move-up="index > 0"
 | 
			
		||||
                :can-move-down="index < pageComponents.length - 1"
 | 
			
		||||
                @move="(direction) => handleMoveComponent(index, direction)"
 | 
			
		||||
                :can-move-up="index > 0"
 | 
			
		||||
                :component="element"
 | 
			
		||||
                @click="handleComponentSelected(element, index)"
 | 
			
		||||
                @copy="handleCopyComponent(index)"
 | 
			
		||||
                @delete="handleDeleteComponent(index)"
 | 
			
		||||
                @click="handleComponentSelected(element, index)"
 | 
			
		||||
                @move="(direction) => handleMoveComponent(index, direction)"
 | 
			
		||||
              />
 | 
			
		||||
            </template>
 | 
			
		||||
          </draggable>
 | 
			
		||||
| 
						 | 
				
			
			@ -99,9 +99,9 @@
 | 
			
		|||
        <!-- 手机底部导航 -->
 | 
			
		||||
        <div v-if="showTabBar" :class="['editor-design-bottom', 'component', 'cursor-pointer!']">
 | 
			
		||||
          <ComponentContainer
 | 
			
		||||
            :active="selectedComponent?.id === tabBarComponent.id"
 | 
			
		||||
            :component="tabBarComponent"
 | 
			
		||||
            :show-toolbar="false"
 | 
			
		||||
            :active="selectedComponent?.id === tabBarComponent.id"
 | 
			
		||||
            @click="handleTabBarSelected"
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -109,9 +109,9 @@
 | 
			
		|||
        <div class="fixed-component-action-group">
 | 
			
		||||
          <el-tag
 | 
			
		||||
            v-if="showPageConfig"
 | 
			
		||||
            size="large"
 | 
			
		||||
            :effect="selectedComponent?.uid === pageConfigComponent.uid ? 'dark' : 'plain'"
 | 
			
		||||
            :type="selectedComponent?.uid === pageConfigComponent.uid ? '' : 'info'"
 | 
			
		||||
            size="large"
 | 
			
		||||
            @click="handleComponentSelected(pageConfigComponent)"
 | 
			
		||||
          >
 | 
			
		||||
            <Icon :icon="pageConfigComponent.icon" :size="12" />
 | 
			
		||||
| 
						 | 
				
			
			@ -120,10 +120,10 @@
 | 
			
		|||
          <template v-for="(component, index) in pageComponents" :key="index">
 | 
			
		||||
            <el-tag
 | 
			
		||||
              v-if="component.position === 'fixed'"
 | 
			
		||||
              size="large"
 | 
			
		||||
              closable
 | 
			
		||||
              :effect="selectedComponent?.uid === component.uid ? 'dark' : 'plain'"
 | 
			
		||||
              :type="selectedComponent?.uid === component.uid ? '' : 'info'"
 | 
			
		||||
              closable
 | 
			
		||||
              size="large"
 | 
			
		||||
              @click="handleComponentSelected(component)"
 | 
			
		||||
              @close="handleDeleteComponent(index)"
 | 
			
		||||
            >
 | 
			
		||||
| 
						 | 
				
			
			@ -134,11 +134,11 @@
 | 
			
		|||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <!-- 右侧:属性面板(ComponentContainerProperty) -->
 | 
			
		||||
      <el-aside class="editor-right" width="350px" v-if="selectedComponent?.property">
 | 
			
		||||
      <el-aside v-if="selectedComponent?.property" class="editor-right" width="350px">
 | 
			
		||||
        <el-card
 | 
			
		||||
          shadow="never"
 | 
			
		||||
          body-class="h-[calc(100%-var(--el-card-padding)-var(--el-card-padding))]"
 | 
			
		||||
          class="h-full"
 | 
			
		||||
          shadow="never"
 | 
			
		||||
        >
 | 
			
		||||
          <!-- 组件名称 -->
 | 
			
		||||
          <template #header>
 | 
			
		||||
| 
						 | 
				
			
			@ -152,8 +152,8 @@
 | 
			
		|||
            view-class="p-[var(--el-card-padding)] p-b-[calc(var(--el-card-padding)+var(--el-card-padding))] property"
 | 
			
		||||
          >
 | 
			
		||||
            <component
 | 
			
		||||
              :key="selectedComponent?.uid || selectedComponent?.id"
 | 
			
		||||
              :is="selectedComponent?.id + 'Property'"
 | 
			
		||||
              :key="selectedComponent?.uid || selectedComponent?.id"
 | 
			
		||||
              v-model="selectedComponent.property"
 | 
			
		||||
            />
 | 
			
		||||
          </el-scrollbar>
 | 
			
		||||
| 
						 | 
				
			
			@ -166,8 +166,8 @@
 | 
			
		|||
  <Dialog v-model="previewDialogVisible" title="预览" width="700">
 | 
			
		||||
    <div class="flex justify-around">
 | 
			
		||||
      <IFrame
 | 
			
		||||
        class="w-375px border-4px border-rounded-8px border-solid p-2px h-667px!"
 | 
			
		||||
        :src="previewUrl"
 | 
			
		||||
        class="w-375px border-4px border-rounded-8px border-solid p-2px h-667px!"
 | 
			
		||||
      />
 | 
			
		||||
      <div class="flex flex-col">
 | 
			
		||||
        <el-text>手机扫码预览</el-text>
 | 
			
		||||
| 
						 | 
				
			
			@ -179,6 +179,7 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
// 注册所有的组件
 | 
			
		||||
import { components } from './components/mobile/index'
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  components: { ...components }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -257,6 +258,11 @@ watch(
 | 
			
		|||
 | 
			
		||||
// 保存
 | 
			
		||||
const handleSave = () => {
 | 
			
		||||
  // 发送保存通知
 | 
			
		||||
  emits('save')
 | 
			
		||||
}
 | 
			
		||||
// 监听配置修改
 | 
			
		||||
const pageConfigChange = () => {
 | 
			
		||||
  const pageConfig = {
 | 
			
		||||
    page: pageConfigComponent.value.property,
 | 
			
		||||
    navigationBar: navigationBarComponent.value.property,
 | 
			
		||||
| 
						 | 
				
			
			@ -272,10 +278,19 @@ const handleSave = () => {
 | 
			
		|||
  // 发送数据更新通知
 | 
			
		||||
  const modelValue = isString(props.modelValue) ? JSON.stringify(pageConfig) : pageConfig
 | 
			
		||||
  emits('update:modelValue', modelValue)
 | 
			
		||||
  // 发送保存通知
 | 
			
		||||
  emits('save', pageConfig)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
  () => [
 | 
			
		||||
    pageConfigComponent.value.property,
 | 
			
		||||
    navigationBarComponent.value.property,
 | 
			
		||||
    tabBarComponent.value.property,
 | 
			
		||||
    pageComponents.value
 | 
			
		||||
  ],
 | 
			
		||||
  () => {
 | 
			
		||||
    pageConfigChange()
 | 
			
		||||
  },
 | 
			
		||||
  { deep: true }
 | 
			
		||||
)
 | 
			
		||||
// 处理页面选中:显示属性表单
 | 
			
		||||
const handlePageSelected = (event: any) => {
 | 
			
		||||
  if (!props.showPageConfig) return
 | 
			
		||||
| 
						 | 
				
			
			@ -547,6 +562,7 @@ $toolbar-height: 42px;
 | 
			
		|||
        :deep(.el-tag) {
 | 
			
		||||
          box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1);
 | 
			
		||||
          border: none;
 | 
			
		||||
 | 
			
		||||
          .el-tag__content {
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            display: flex;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
          },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
    // 这里可以执行相应的操作(例如打开搜索框等)
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,6 +39,20 @@
 | 
			
		|||
            </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 class="handler-item" @click="addNode(NodeType.ROUTER_BRANCH_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>
 | 
			
		||||
| 
						 | 
				
			
			@ -53,12 +67,13 @@ import {
 | 
			
		|||
  ApproveMethodType,
 | 
			
		||||
  AssignEmptyHandlerType,
 | 
			
		||||
  AssignStartUserHandlerType,
 | 
			
		||||
  ConditionType,
 | 
			
		||||
  NODE_DEFAULT_NAME,
 | 
			
		||||
  NodeType,
 | 
			
		||||
  RejectHandlerType,
 | 
			
		||||
  SimpleFlowNode
 | 
			
		||||
} from './consts'
 | 
			
		||||
import { generateUUID } from '@/utils'
 | 
			
		||||
import {generateUUID} from '@/utils'
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: 'NodeHandler'
 | 
			
		||||
| 
						 | 
				
			
			@ -113,7 +128,16 @@ const addNode = (type: number) => {
 | 
			
		|||
        type: AssignEmptyHandlerType.APPROVE
 | 
			
		||||
      },
 | 
			
		||||
      assignStartUserHandlerType: AssignStartUserHandlerType.START_USER_AUDIT,
 | 
			
		||||
      childNode: props.childNode
 | 
			
		||||
      childNode: props.childNode,
 | 
			
		||||
      taskCreateListener: {
 | 
			
		||||
        enable: false
 | 
			
		||||
      },
 | 
			
		||||
      taskAssignListener: {
 | 
			
		||||
        enable: false
 | 
			
		||||
      },
 | 
			
		||||
      taskCompleteListener: {
 | 
			
		||||
        enable: false
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    emits('update:childNode', data)
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -140,7 +164,7 @@ const addNode = (type: number) => {
 | 
			
		|||
          showText: '',
 | 
			
		||||
          type: NodeType.CONDITION_NODE,
 | 
			
		||||
          childNode: undefined,
 | 
			
		||||
          conditionType: 1,
 | 
			
		||||
          conditionType: ConditionType.RULE,
 | 
			
		||||
          defaultFlow: false
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
| 
						 | 
				
			
			@ -208,6 +232,26 @@ 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)
 | 
			
		||||
  }
 | 
			
		||||
  if (type === NodeType.ROUTER_BRANCH_NODE) {
 | 
			
		||||
    const data: SimpleFlowNode = {
 | 
			
		||||
      id: 'GateWay_' + generateUUID(),
 | 
			
		||||
      name: NODE_DEFAULT_NAME.get(NodeType.ROUTER_BRANCH_NODE) as string,
 | 
			
		||||
      showText: '',
 | 
			
		||||
      type: NodeType.ROUTER_BRANCH_NODE,
 | 
			
		||||
      childNode: props.childNode
 | 
			
		||||
    }
 | 
			
		||||
    emits('update:childNode', data)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -38,6 +38,18 @@
 | 
			
		|||
    @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"
 | 
			
		||||
  />
 | 
			
		||||
  <!-- 路由分支节点 -->
 | 
			
		||||
  <RouterNode
 | 
			
		||||
    v-if="currentNode && currentNode.type === NodeType.ROUTER_BRANCH_NODE"
 | 
			
		||||
    :flow-node="currentNode"
 | 
			
		||||
    @update:flow-node="handleModelValueUpdate"
 | 
			
		||||
  />
 | 
			
		||||
  <!-- 递归显示孩子节点  -->
 | 
			
		||||
  <ProcessNodeTree
 | 
			
		||||
    v-if="currentNode && currentNode.childNode"
 | 
			
		||||
| 
						 | 
				
			
			@ -60,6 +72,8 @@ 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 RouterNode from './nodes/RouterNode.vue'
 | 
			
		||||
import { SimpleFlowNode, NodeType } from './consts'
 | 
			
		||||
import { useWatchNode } from './node'
 | 
			
		||||
defineOptions({
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,13 +59,10 @@ const props = defineProps({
 | 
			
		|||
  startUserIds: {
 | 
			
		||||
    type: Array,
 | 
			
		||||
    required: false
 | 
			
		||||
  },
 | 
			
		||||
  value: {
 | 
			
		||||
    type: [String, Object],
 | 
			
		||||
    required: false
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const processData = inject('processData') as Ref
 | 
			
		||||
const loading = ref(false)
 | 
			
		||||
const formFields = ref<string[]>([])
 | 
			
		||||
const formType = ref(20)
 | 
			
		||||
| 
						 | 
				
			
			@ -77,9 +74,6 @@ const deptTreeOptions = ref()
 | 
			
		|||
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
 | 
			
		||||
const isDataInitialized = ref(false) // 添加标记,用于判断数据是否已初始化
 | 
			
		||||
 | 
			
		||||
// 添加当前值的引用
 | 
			
		||||
const currentValue = ref<SimpleFlowNode | undefined>()
 | 
			
		||||
 | 
			
		||||
provide('formFields', formFields)
 | 
			
		||||
provide('formType', formType)
 | 
			
		||||
provide('roleList', roleOptions)
 | 
			
		||||
| 
						 | 
				
			
			@ -89,9 +83,11 @@ provide('deptList', deptOptions)
 | 
			
		|||
provide('userGroupList', userGroupOptions)
 | 
			
		||||
provide('deptTree', deptTreeOptions)
 | 
			
		||||
provide('startUserIds', props.startUserIds)
 | 
			
		||||
 | 
			
		||||
provide('tasks', [])
 | 
			
		||||
provide('processInstance', {})
 | 
			
		||||
const message = useMessage() // 国际化
 | 
			
		||||
const processNodeTree = ref<SimpleFlowNode | undefined>()
 | 
			
		||||
provide('processNodeTree', processNodeTree)
 | 
			
		||||
const errorDialogVisible = ref(false)
 | 
			
		||||
let errorNodes: SimpleFlowNode[] = []
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -113,70 +109,13 @@ const updateModel = () => {
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 加载流程数据
 | 
			
		||||
const loadProcessData = async (data: any) => {
 | 
			
		||||
  try {
 | 
			
		||||
    if (data) {
 | 
			
		||||
      const parsedData = typeof data === 'string' ? JSON.parse(data) : data
 | 
			
		||||
      processNodeTree.value = parsedData
 | 
			
		||||
      currentValue.value = parsedData
 | 
			
		||||
      // 确保数据加载后刷新视图
 | 
			
		||||
      await nextTick()
 | 
			
		||||
      if (simpleProcessModelRef.value?.refresh) {
 | 
			
		||||
        await simpleProcessModelRef.value.refresh()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('加载流程数据失败:', error)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 监听属性变化
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.value,
 | 
			
		||||
  async (newValue, oldValue) => {
 | 
			
		||||
    if (newValue && newValue !== oldValue) {
 | 
			
		||||
      await loadProcessData(newValue)
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  { immediate: true, deep: true }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 监听流程节点树变化,自动保存
 | 
			
		||||
watch(
 | 
			
		||||
  () => processNodeTree.value,
 | 
			
		||||
  async (newValue, oldValue) => {
 | 
			
		||||
    if (newValue && oldValue && JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
 | 
			
		||||
      await saveSimpleFlowModel(newValue)
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  { deep: true }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const saveSimpleFlowModel = async (simpleModelNode: SimpleFlowNode) => {
 | 
			
		||||
  if (!simpleModelNode) {
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 校验节点
 | 
			
		||||
  errorNodes = []
 | 
			
		||||
  validateNode(simpleModelNode, errorNodes)
 | 
			
		||||
  if (errorNodes.length > 0) {
 | 
			
		||||
    errorDialogVisible.value = true
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    if (props.modelId) {
 | 
			
		||||
      // 编辑模式
 | 
			
		||||
      const data = {
 | 
			
		||||
        id: props.modelId,
 | 
			
		||||
        simpleModel: simpleModelNode
 | 
			
		||||
      }
 | 
			
		||||
      await updateBpmSimpleModel(data)
 | 
			
		||||
    }
 | 
			
		||||
    // 无论是编辑还是新建模式,都更新当前值并触发事件
 | 
			
		||||
    currentValue.value = simpleModelNode
 | 
			
		||||
    processData.value = simpleModelNode
 | 
			
		||||
    emits('success', simpleModelNode)
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('保存失败:', error)
 | 
			
		||||
| 
						 | 
				
			
			@ -258,16 +197,21 @@ const initializeData = async () => {
 | 
			
		|||
          formFields.value = bpmnForm?.fields
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // 获取 SIMPLE 设计器模型
 | 
			
		||||
      const result = await getBpmSimpleModel(props.modelId)
 | 
			
		||||
      if (result) {
 | 
			
		||||
        await loadProcessData(result)
 | 
			
		||||
      } else {
 | 
			
		||||
        updateModel()
 | 
			
		||||
      }
 | 
			
		||||
    } else if (props.value) {
 | 
			
		||||
      await loadProcessData(props.value)
 | 
			
		||||
    }
 | 
			
		||||
    // 获得角色列表
 | 
			
		||||
    roleOptions.value = await RoleApi.getSimpleRoleList()
 | 
			
		||||
    // 获得岗位列表
 | 
			
		||||
    postOptions.value = await PostApi.getSimplePostList()
 | 
			
		||||
    // 获得用户列表
 | 
			
		||||
    userOptions.value = await UserApi.getSimpleUserList()
 | 
			
		||||
    // 获得部门列表
 | 
			
		||||
    deptOptions.value = await DeptApi.getSimpleDeptList()
 | 
			
		||||
    deptTreeOptions.value = handleTree(deptOptions.value as DeptApi.DeptVO[], 'id')
 | 
			
		||||
    // 获取用户组列表
 | 
			
		||||
    userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
 | 
			
		||||
    // 加载流程数据
 | 
			
		||||
    if (processData.value) {
 | 
			
		||||
      processNodeTree.value = processData?.value
 | 
			
		||||
    } else {
 | 
			
		||||
      updateModel()
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -296,38 +240,5 @@ onActivated(() => {
 | 
			
		|||
 | 
			
		||||
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
 | 
			
		||||
})
 | 
			
		||||
defineExpose({})
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,22 @@
 | 
			
		|||
    <div class="position-absolute top-0px right-0px bg-#fff">
 | 
			
		||||
      <el-row type="flex" justify="end">
 | 
			
		||||
        <el-button-group key="scale-control" size="default">
 | 
			
		||||
          <el-button v-if="!readonly" size="default" @click="exportJson">
 | 
			
		||||
            <Icon icon="ep:download" /> 导出
 | 
			
		||||
          </el-button>
 | 
			
		||||
          <el-button v-if="!readonly" size="default" @click="importJson">
 | 
			
		||||
            <Icon icon="ep:upload" />导入
 | 
			
		||||
          </el-button>
 | 
			
		||||
          <!-- 用于打开本地文件-->
 | 
			
		||||
          <input
 | 
			
		||||
            v-if="!readonly"
 | 
			
		||||
            type="file"
 | 
			
		||||
            id="files"
 | 
			
		||||
            ref="refFile"
 | 
			
		||||
            style="display: none"
 | 
			
		||||
            accept=".json"
 | 
			
		||||
            @change="importLocalFile"
 | 
			
		||||
          />
 | 
			
		||||
          <el-button size="default" :icon="ScaleToOriginal" @click="processReZoom()" />
 | 
			
		||||
          <el-button size="default" :plain="true" :icon="ZoomOut" @click="zoomOut()" />
 | 
			
		||||
          <el-button size="default" class="w-80px"> {{ scaleValue }}% </el-button>
 | 
			
		||||
| 
						 | 
				
			
			@ -34,6 +50,8 @@ import ProcessNodeTree from './ProcessNodeTree.vue'
 | 
			
		|||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from './consts'
 | 
			
		||||
import { useWatchNode } from './node'
 | 
			
		||||
import { ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue'
 | 
			
		||||
import { isString } from '@/utils/is'
 | 
			
		||||
import download from '@/utils/download'
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: 'SimpleProcessModel'
 | 
			
		||||
| 
						 | 
				
			
			@ -52,7 +70,7 @@ const props = defineProps({
 | 
			
		|||
})
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits<{
 | 
			
		||||
  'save': [node: SimpleFlowNode | undefined]
 | 
			
		||||
  save: [node: SimpleFlowNode | undefined]
 | 
			
		||||
}>()
 | 
			
		||||
 | 
			
		||||
const processNodeTree = useWatchNode(props)
 | 
			
		||||
| 
						 | 
				
			
			@ -85,6 +103,16 @@ const processReZoom = () => {
 | 
			
		|||
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) {
 | 
			
		||||
| 
						 | 
				
			
			@ -143,6 +171,28 @@ const getCurrentFlowData = async () => {
 | 
			
		|||
defineExpose({
 | 
			
		||||
  getCurrentFlowData
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
/** 导出 JSON */
 | 
			
		||||
const exportJson = () => {
 | 
			
		||||
  download.json(new Blob([JSON.stringify(processNodeTree.value)]), 'model.json')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 导入 JSON */
 | 
			
		||||
const refFile = ref()
 | 
			
		||||
const importJson = () => {
 | 
			
		||||
  refFile.value.click()
 | 
			
		||||
}
 | 
			
		||||
const importLocalFile = () => {
 | 
			
		||||
  const file = refFile.value.files[0]
 | 
			
		||||
  const reader = new FileReader()
 | 
			
		||||
  reader.readAsText(file)
 | 
			
		||||
  reader.onload = function () {
 | 
			
		||||
    if (isString(this.result)) {
 | 
			
		||||
      processNodeTree.value = JSON.parse(this.result)
 | 
			
		||||
      emits('save', processNodeTree.value)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped></style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,6 +23,11 @@ export enum NodeType {
 | 
			
		|||
   */
 | 
			
		||||
  COPY_TASK_NODE = 12,
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 延迟器节点
 | 
			
		||||
   */
 | 
			
		||||
  DELAY_TIMER_NODE = 14,
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 条件节点
 | 
			
		||||
   */
 | 
			
		||||
| 
						 | 
				
			
			@ -39,7 +44,11 @@ export enum NodeType {
 | 
			
		|||
  /**
 | 
			
		||||
   * 包容分支节点 (对应包容网关)
 | 
			
		||||
   */
 | 
			
		||||
  INCLUSIVE_BRANCH_NODE = 53
 | 
			
		||||
  INCLUSIVE_BRANCH_NODE = 53,
 | 
			
		||||
  /**
 | 
			
		||||
   * 路由分支节点
 | 
			
		||||
   */
 | 
			
		||||
  ROUTER_BRANCH_NODE = 54
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum NodeId {
 | 
			
		||||
| 
						 | 
				
			
			@ -88,6 +97,12 @@ export interface SimpleFlowNode {
 | 
			
		|||
  assignEmptyHandler?: AssignEmptyHandler
 | 
			
		||||
  // 审批节点的审批人与发起人相同时,对应的处理类型
 | 
			
		||||
  assignStartUserHandlerType?: number
 | 
			
		||||
  // 创建任务监听器
 | 
			
		||||
  taskCreateListener?: ListenerHandler
 | 
			
		||||
  // 创建任务监听器
 | 
			
		||||
  taskAssignListener?: ListenerHandler
 | 
			
		||||
  // 创建任务监听器
 | 
			
		||||
  taskCompleteListener?: ListenerHandler
 | 
			
		||||
  // 条件类型
 | 
			
		||||
  conditionType?: ConditionType
 | 
			
		||||
  // 条件表达式
 | 
			
		||||
| 
						 | 
				
			
			@ -98,6 +113,13 @@ export interface SimpleFlowNode {
 | 
			
		|||
  defaultFlow?: boolean
 | 
			
		||||
  // 活动的状态,用于前端节点状态展示
 | 
			
		||||
  activityStatus?: TaskStatusEnum
 | 
			
		||||
  // 延迟设置
 | 
			
		||||
  delaySetting?: DelaySetting
 | 
			
		||||
  // 路由分支
 | 
			
		||||
  routerGroups?: RouterCondition[]
 | 
			
		||||
  defaultFlowId?: string
 | 
			
		||||
  // 签名
 | 
			
		||||
  signEnable?: boolean
 | 
			
		||||
}
 | 
			
		||||
// 候选人策略枚举 ( 用于审批节点。抄送节点 )
 | 
			
		||||
export enum CandidateStrategy {
 | 
			
		||||
| 
						 | 
				
			
			@ -215,6 +237,41 @@ export type AssignEmptyHandler = {
 | 
			
		|||
  userIds?: number[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 监听器的结构定义
 | 
			
		||||
 */
 | 
			
		||||
export type ListenerHandler = {
 | 
			
		||||
  enable: boolean
 | 
			
		||||
  path?: string
 | 
			
		||||
  header?: ListenerParam[]
 | 
			
		||||
  body?: ListenerParam[]
 | 
			
		||||
}
 | 
			
		||||
export type ListenerParam = {
 | 
			
		||||
  key: string
 | 
			
		||||
  type: number
 | 
			
		||||
  value: string
 | 
			
		||||
}
 | 
			
		||||
export enum ListenerParamTypeEnum {
 | 
			
		||||
  /**
 | 
			
		||||
   * 固定值
 | 
			
		||||
   */
 | 
			
		||||
  FIXED_VALUE = 1,
 | 
			
		||||
  /**
 | 
			
		||||
   * 表单
 | 
			
		||||
   */
 | 
			
		||||
  FROM_FORM = 2
 | 
			
		||||
}
 | 
			
		||||
export const LISTENER_MAP_TYPES = [
 | 
			
		||||
  {
 | 
			
		||||
    value: 1,
 | 
			
		||||
    label: '固定值'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    value: 2,
 | 
			
		||||
    label: '表单'
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
// 审批拒绝类型枚举
 | 
			
		||||
export enum RejectHandlerType {
 | 
			
		||||
  /**
 | 
			
		||||
| 
						 | 
				
			
			@ -382,8 +439,6 @@ export enum OperationButtonType {
 | 
			
		|||
 * 条件规则结构定义
 | 
			
		||||
 */
 | 
			
		||||
export type ConditionRule = {
 | 
			
		||||
  type: number
 | 
			
		||||
  opName: string
 | 
			
		||||
  opCode: string
 | 
			
		||||
  leftSide: string
 | 
			
		||||
  rightSide: string
 | 
			
		||||
| 
						 | 
				
			
			@ -413,12 +468,16 @@ 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, '请设置延迟器')
 | 
			
		||||
NODE_DEFAULT_TEXT.set(NodeType.ROUTER_BRANCH_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, '延迟器')
 | 
			
		||||
NODE_DEFAULT_NAME.set(NodeType.ROUTER_BRANCH_NODE, '路由分支')
 | 
			
		||||
 | 
			
		||||
// 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序
 | 
			
		||||
export const CANDIDATE_STRATEGY: DictDataVO[] = [
 | 
			
		||||
| 
						 | 
				
			
			@ -451,8 +510,8 @@ export const APPROVE_METHODS: DictDataVO[] = [
 | 
			
		|||
]
 | 
			
		||||
 | 
			
		||||
export const CONDITION_CONFIG_TYPES: DictDataVO[] = [
 | 
			
		||||
  { label: '条件表达式', value: ConditionType.EXPRESSION },
 | 
			
		||||
  { label: '条件规则', value: ConditionType.RULE }
 | 
			
		||||
  { label: '条件规则', value: ConditionType.RULE },
 | 
			
		||||
  { label: '条件表达式', value: ConditionType.EXPRESSION }
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
// 时间单位类型
 | 
			
		||||
| 
						 | 
				
			
			@ -568,3 +627,40 @@ 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 }
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 路由分支结构定义
 | 
			
		||||
 */
 | 
			
		||||
export type RouterCondition = {
 | 
			
		||||
  nodeId: string
 | 
			
		||||
  conditionType: ConditionType
 | 
			
		||||
  conditionExpression: string
 | 
			
		||||
  conditionGroups: ConditionGroup
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,3 @@
 | 
			
		|||
import { cloneDeep } from 'lodash-es'
 | 
			
		||||
import { TaskStatusEnum } from '@/api/bpm/task'
 | 
			
		||||
import * as RoleApi from '@/api/system/role'
 | 
			
		||||
import * as DeptApi from '@/api/system/dept'
 | 
			
		||||
| 
						 | 
				
			
			@ -14,7 +13,8 @@ import {
 | 
			
		|||
  NODE_DEFAULT_NAME,
 | 
			
		||||
  AssignStartUserHandlerType,
 | 
			
		||||
  AssignEmptyHandlerType,
 | 
			
		||||
  FieldPermissionType
 | 
			
		||||
  FieldPermissionType,
 | 
			
		||||
  ListenerParam
 | 
			
		||||
} from './consts'
 | 
			
		||||
import { parseFormFields } from '@/components/FormCreate/src/utils/index'
 | 
			
		||||
export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> {
 | 
			
		||||
| 
						 | 
				
			
			@ -46,9 +46,9 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType)
 | 
			
		|||
  // 字段权限配置. 需要有 field, title,  permissioin 属性
 | 
			
		||||
  const fieldsPermissionConfig = ref<Array<Record<string, any>>>([])
 | 
			
		||||
 | 
			
		||||
  const formType = inject<Ref<number>>('formType') // 表单类型
 | 
			
		||||
  const formType = inject<Ref<number|undefined>>('formType', ref()) // 表单类型
 | 
			
		||||
 | 
			
		||||
  const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
 | 
			
		||||
  const formFields = inject<Ref<string[]>>('formFields', ref([])) // 流程表单字段
 | 
			
		||||
 | 
			
		||||
  const getNodeConfigFormFields = (nodeFormFields?: Array<Record<string, string>>) => {
 | 
			
		||||
    nodeFormFields = toRaw(nodeFormFields)
 | 
			
		||||
| 
						 | 
				
			
			@ -136,6 +136,19 @@ export type UserTaskFormType = {
 | 
			
		|||
  timeDuration?: number
 | 
			
		||||
  maxRemindCount?: number
 | 
			
		||||
  buttonsSetting: any[]
 | 
			
		||||
  taskCreateListenerEnable?: boolean
 | 
			
		||||
  taskCreateListenerPath?: string
 | 
			
		||||
  taskCreateListenerHeader?: ListenerParam[]
 | 
			
		||||
  taskCreateListenerBody?: ListenerParam[]
 | 
			
		||||
  taskAssignListenerEnable?: boolean
 | 
			
		||||
  taskAssignListenerPath?: string
 | 
			
		||||
  taskAssignListenerHeader?: ListenerParam[]
 | 
			
		||||
  taskAssignListenerBody?: ListenerParam[]
 | 
			
		||||
  taskCompleteListenerEnable?: boolean
 | 
			
		||||
  taskCompleteListenerPath?: string
 | 
			
		||||
  taskCompleteListenerHeader?: ListenerParam[]
 | 
			
		||||
  taskCompleteListenerBody?: ListenerParam[]
 | 
			
		||||
  signEnable: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type CopyTaskFormType = {
 | 
			
		||||
| 
						 | 
				
			
			@ -156,13 +169,13 @@ export type CopyTaskFormType = {
 | 
			
		|||
 * @description 节点表单数据。 用于审批节点、抄送节点
 | 
			
		||||
 */
 | 
			
		||||
export function useNodeForm(nodeType: NodeType) {
 | 
			
		||||
  const roleOptions = inject<Ref<RoleApi.RoleVO[]>>('roleList') // 角色列表
 | 
			
		||||
  const postOptions = inject<Ref<PostApi.PostVO[]>>('postList') // 岗位列表
 | 
			
		||||
  const userOptions = inject<Ref<UserApi.UserVO[]>>('userList') // 用户列表
 | 
			
		||||
  const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList') // 部门列表
 | 
			
		||||
  const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList') // 用户组列表
 | 
			
		||||
  const deptTreeOptions = inject('deptTree') // 部门树
 | 
			
		||||
  const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
 | 
			
		||||
  const roleOptions = inject<Ref<RoleApi.RoleVO[]>>('roleList', ref([])) // 角色列表
 | 
			
		||||
  const postOptions = inject<Ref<PostApi.PostVO[]>>('postList', ref([])) // 岗位列表
 | 
			
		||||
  const userOptions = inject<Ref<UserApi.UserVO[]>>('userList', ref([])) // 用户列表
 | 
			
		||||
  const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList', ref([])) // 部门列表
 | 
			
		||||
  const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList',ref([])) // 用户组列表
 | 
			
		||||
  const deptTreeOptions = inject('deptTree', ref()) // 部门树
 | 
			
		||||
  const formFields = inject<Ref<string[]>>('formFields', ref([])) // 流程表单字段
 | 
			
		||||
  const configForm = ref<UserTaskFormType | CopyTaskFormType>()
 | 
			
		||||
  if (nodeType === NodeType.USER_TASK_NODE) {
 | 
			
		||||
    configForm.value = {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,117 +30,7 @@
 | 
			
		|||
        >未满足其它条件时,将进入此分支(该分支不可编辑和删除)</div
 | 
			
		||||
      >
 | 
			
		||||
      <div v-else>
 | 
			
		||||
        <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
 | 
			
		||||
                v-for="(dict, index) in conditionConfigTypes"
 | 
			
		||||
                :key="index"
 | 
			
		||||
                :value="dict.value"
 | 
			
		||||
                :label="dict.value"
 | 
			
		||||
              >
 | 
			
		||||
                {{ dict.label }}
 | 
			
		||||
              </el-radio>
 | 
			
		||||
            </el-radio-group>
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
 | 
			
		||||
          <el-form-item
 | 
			
		||||
            v-if="currentNode.conditionType === 1"
 | 
			
		||||
            label="条件表达式"
 | 
			
		||||
            prop="conditionExpression"
 | 
			
		||||
          >
 | 
			
		||||
            <el-input
 | 
			
		||||
              type="textarea"
 | 
			
		||||
              v-model="currentNode.conditionExpression"
 | 
			
		||||
              clearable
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
            />
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
          <el-form-item v-if="currentNode.conditionType === 2" label="条件规则">
 | 
			
		||||
            <div class="condition-group-tool">
 | 
			
		||||
              <div class="flex items-center">
 | 
			
		||||
                <div class="mr-4">条件组关系</div>
 | 
			
		||||
                <el-switch
 | 
			
		||||
                  v-model="conditionGroups.and"
 | 
			
		||||
                  inline-prompt
 | 
			
		||||
                  active-text="且"
 | 
			
		||||
                  inactive-text="或"
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <el-space direction="vertical" :spacer="conditionGroups.and ? '且' : '或'">
 | 
			
		||||
              <el-card
 | 
			
		||||
                class="condition-group"
 | 
			
		||||
                style="width: 530px"
 | 
			
		||||
                v-for="(condition, cIdx) in conditionGroups.conditions"
 | 
			
		||||
                :key="cIdx"
 | 
			
		||||
              >
 | 
			
		||||
                <div class="condition-group-delete" v-if="conditionGroups.conditions.length > 1">
 | 
			
		||||
                  <Icon
 | 
			
		||||
                    color="#0089ff"
 | 
			
		||||
                    icon="ep:circle-close-filled"
 | 
			
		||||
                    :size="18"
 | 
			
		||||
                    @click="deleteConditionGroup(cIdx)"
 | 
			
		||||
                  />
 | 
			
		||||
                </div>
 | 
			
		||||
                <template #header>
 | 
			
		||||
                  <div class="flex items-center justify-between">
 | 
			
		||||
                    <div>条件组</div>
 | 
			
		||||
                    <div class="flex">
 | 
			
		||||
                      <div class="mr-4">规则关系</div>
 | 
			
		||||
                      <el-switch
 | 
			
		||||
                        v-model="condition.and"
 | 
			
		||||
                        inline-prompt
 | 
			
		||||
                        active-text="且"
 | 
			
		||||
                        inactive-text="或"
 | 
			
		||||
                      />
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
                <div class="flex pt-2" v-for="(rule, rIdx) in condition.rules" :key="rIdx">
 | 
			
		||||
                  <div class="mr-2">
 | 
			
		||||
                    <el-select style="width: 160px" v-model="rule.leftSide">
 | 
			
		||||
                      <el-option
 | 
			
		||||
                        v-for="(item, index) in fieldOptions"
 | 
			
		||||
                        :key="index"
 | 
			
		||||
                        :label="item.title"
 | 
			
		||||
                        :value="item.field"
 | 
			
		||||
                        :disabled="!item.required"
 | 
			
		||||
                      />
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="mr-2">
 | 
			
		||||
                    <el-select v-model="rule.opCode" style="width: 100px">
 | 
			
		||||
                      <el-option
 | 
			
		||||
                        v-for="item in COMPARISON_OPERATORS"
 | 
			
		||||
                        :key="item.value"
 | 
			
		||||
                        :label="item.label"
 | 
			
		||||
                        :value="item.value"
 | 
			
		||||
                      />
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="mr-2">
 | 
			
		||||
                    <el-input v-model="rule.rightSide" style="width: 160px" />
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="mr-1 flex items-center" v-if="condition.rules.length > 1">
 | 
			
		||||
                    <Icon
 | 
			
		||||
                      icon="ep:delete"
 | 
			
		||||
                      :size="18"
 | 
			
		||||
                      @click="deleteConditionRule(condition, rIdx)"
 | 
			
		||||
                    />
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="flex items-center">
 | 
			
		||||
                    <Icon icon="ep:plus" :size="18" @click="addConditionRule(condition, rIdx)" />
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </el-card>
 | 
			
		||||
            </el-space>
 | 
			
		||||
            <div title="添加条件组" class="mt-4 cursor-pointer">
 | 
			
		||||
              <Icon color="#0089ff" icon="ep:plus" :size="24" @click="addConditionGroup" />
 | 
			
		||||
            </div>
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
        </el-form>
 | 
			
		||||
        <Condition ref="conditionRef" v-model="condition" />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <template #footer>
 | 
			
		||||
| 
						 | 
				
			
			@ -155,33 +45,17 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import {
 | 
			
		||||
  SimpleFlowNode,
 | 
			
		||||
  CONDITION_CONFIG_TYPES,
 | 
			
		||||
  ConditionType,
 | 
			
		||||
  COMPARISON_OPERATORS,
 | 
			
		||||
  ConditionGroup,
 | 
			
		||||
  Condition,
 | 
			
		||||
  ConditionRule,
 | 
			
		||||
  ProcessVariableEnum
 | 
			
		||||
} from '../consts'
 | 
			
		||||
import { getDefaultConditionNodeName } from '../utils'
 | 
			
		||||
import { useFormFields } from '../node'
 | 
			
		||||
import { BpmModelFormType } from '@/utils/constants'
 | 
			
		||||
import Condition from './components/Condition.vue'
 | 
			
		||||
const message = useMessage() // 消息弹窗
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: 'ConditionNodeConfig'
 | 
			
		||||
})
 | 
			
		||||
const formType = inject<Ref<number>>('formType') // 表单类型
 | 
			
		||||
const conditionConfigTypes = computed(() => {
 | 
			
		||||
  return CONDITION_CONFIG_TYPES.filter((item) => {
 | 
			
		||||
    // 业务表单暂时去掉条件规则选项
 | 
			
		||||
    if (formType?.value === BpmModelFormType.CUSTOM && item.value === ConditionType.RULE) {
 | 
			
		||||
      return false
 | 
			
		||||
    } else {
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  conditionNode: {
 | 
			
		||||
    type: Object as () => SimpleFlowNode,
 | 
			
		||||
| 
						 | 
				
			
			@ -193,11 +67,26 @@ const props = defineProps({
 | 
			
		|||
  }
 | 
			
		||||
})
 | 
			
		||||
const settingVisible = ref(false)
 | 
			
		||||
const condition = ref<any>()
 | 
			
		||||
const open = () => {
 | 
			
		||||
  if (currentNode.value.conditionType === ConditionType.RULE) {
 | 
			
		||||
    if (currentNode.value.conditionGroups) {
 | 
			
		||||
      conditionGroups.value = currentNode.value.conditionGroups
 | 
			
		||||
    }
 | 
			
		||||
  condition.value = {
 | 
			
		||||
    conditionType: currentNode.value.conditionType,
 | 
			
		||||
    conditionExpression: currentNode.value.conditionExpression ?? '',
 | 
			
		||||
    conditionGroups: currentNode.value.conditionGroups ?? {
 | 
			
		||||
      and: true,
 | 
			
		||||
      conditions: [
 | 
			
		||||
        {
 | 
			
		||||
          and: true,
 | 
			
		||||
          rules: [
 | 
			
		||||
            {
 | 
			
		||||
              opCode: '==',
 | 
			
		||||
              leftSide: '',
 | 
			
		||||
              rightSide: ''
 | 
			
		||||
            }
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
  settingVisible.value = true
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -239,31 +128,27 @@ const handleClose = async (done: (cancel?: boolean) => void) => {
 | 
			
		|||
    done()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// 表单校验规则
 | 
			
		||||
const formRules = reactive({
 | 
			
		||||
  conditionType: [{ required: true, message: '配置方式不能为空', trigger: 'blur' }],
 | 
			
		||||
  conditionExpression: [{ required: true, message: '条件表达式不能为空', trigger: 'blur' }]
 | 
			
		||||
})
 | 
			
		||||
const formRef = ref() // 表单 Ref
 | 
			
		||||
 | 
			
		||||
const conditionRef = ref()
 | 
			
		||||
// 保存配置
 | 
			
		||||
const saveConfig = async () => {
 | 
			
		||||
  if (!currentNode.value.defaultFlow) {
 | 
			
		||||
    // 校验表单
 | 
			
		||||
    if (!formRef) return false
 | 
			
		||||
    const valid = await formRef.value.validate()
 | 
			
		||||
    const valid = await conditionRef.value.validate()
 | 
			
		||||
    if (!valid) return false
 | 
			
		||||
    const showText = getShowText()
 | 
			
		||||
    if (!showText) {
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
    currentNode.value.showText = showText
 | 
			
		||||
    currentNode.value.conditionType = condition.value.conditionType
 | 
			
		||||
    if (currentNode.value.conditionType === ConditionType.EXPRESSION) {
 | 
			
		||||
      currentNode.value.conditionGroups = undefined
 | 
			
		||||
      currentNode.value.conditionExpression = condition.value.conditionExpression
 | 
			
		||||
    }
 | 
			
		||||
    if (currentNode.value.conditionType === ConditionType.RULE) {
 | 
			
		||||
      currentNode.value.conditionExpression = undefined
 | 
			
		||||
      currentNode.value.conditionGroups = conditionGroups.value
 | 
			
		||||
      currentNode.value.conditionGroups = condition.value.conditionGroups
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  settingVisible.value = false
 | 
			
		||||
| 
						 | 
				
			
			@ -271,16 +156,16 @@ const saveConfig = async () => {
 | 
			
		|||
}
 | 
			
		||||
const getShowText = (): string => {
 | 
			
		||||
  let showText = ''
 | 
			
		||||
  if (currentNode.value.conditionType === ConditionType.EXPRESSION) {
 | 
			
		||||
    if (currentNode.value.conditionExpression) {
 | 
			
		||||
      showText = `表达式:${currentNode.value.conditionExpression}`
 | 
			
		||||
  if (condition.value.conditionType === ConditionType.EXPRESSION) {
 | 
			
		||||
    if (condition.value.conditionExpression) {
 | 
			
		||||
      showText = `表达式:${condition.value.conditionExpression}`
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (currentNode.value.conditionType === ConditionType.RULE) {
 | 
			
		||||
  if (condition.value.conditionType === ConditionType.RULE) {
 | 
			
		||||
    // 条件组是否为与关系
 | 
			
		||||
    const groupAnd = conditionGroups.value.and
 | 
			
		||||
    const groupAnd = condition.value.conditionGroups.and
 | 
			
		||||
    let warningMesg: undefined | string = undefined
 | 
			
		||||
    const conditionGroup = conditionGroups.value.conditions.map((item) => {
 | 
			
		||||
    const conditionGroup = condition.value.conditionGroups.conditions.map((item) => {
 | 
			
		||||
      return (
 | 
			
		||||
        '(' +
 | 
			
		||||
        item.rules
 | 
			
		||||
| 
						 | 
				
			
			@ -309,64 +194,7 @@ const getShowText = (): string => {
 | 
			
		|||
  return showText
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 改变条件配置方式
 | 
			
		||||
const changeConditionType = () => {}
 | 
			
		||||
 | 
			
		||||
const conditionGroups = ref<ConditionGroup>({
 | 
			
		||||
  and: true,
 | 
			
		||||
  conditions: [
 | 
			
		||||
    {
 | 
			
		||||
      and: true,
 | 
			
		||||
      rules: [
 | 
			
		||||
        {
 | 
			
		||||
          type: 1,
 | 
			
		||||
          opName: '等于',
 | 
			
		||||
          opCode: '==',
 | 
			
		||||
          leftSide: '',
 | 
			
		||||
          rightSide: ''
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
})
 | 
			
		||||
// 添加条件组
 | 
			
		||||
const addConditionGroup = () => {
 | 
			
		||||
  const condition = {
 | 
			
		||||
    and: true,
 | 
			
		||||
    rules: [
 | 
			
		||||
      {
 | 
			
		||||
        type: 1,
 | 
			
		||||
        opName: '等于',
 | 
			
		||||
        opCode: '==',
 | 
			
		||||
        leftSide: '',
 | 
			
		||||
        rightSide: ''
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
  conditionGroups.value.conditions.push(condition)
 | 
			
		||||
}
 | 
			
		||||
// 删除条件组
 | 
			
		||||
const deleteConditionGroup = (idx: number) => {
 | 
			
		||||
  conditionGroups.value.conditions.splice(idx, 1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 添加条件规则
 | 
			
		||||
const addConditionRule = (condition: Condition, idx: number) => {
 | 
			
		||||
  const rule: ConditionRule = {
 | 
			
		||||
    type: 1,
 | 
			
		||||
    opName: '等于',
 | 
			
		||||
    opCode: '==',
 | 
			
		||||
    leftSide: '',
 | 
			
		||||
    rightSide: ''
 | 
			
		||||
  }
 | 
			
		||||
  condition.rules.splice(idx + 1, 0, rule)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const deleteConditionRule = (condition: Condition, idx: number) => {
 | 
			
		||||
  condition.rules.splice(idx, 1)
 | 
			
		||||
}
 | 
			
		||||
const fieldsInfo = useFormFields()
 | 
			
		||||
 | 
			
		||||
/** 条件规则可选择的表单字段 */
 | 
			
		||||
const fieldOptions = computed(() => {
 | 
			
		||||
  const fieldsCopy = fieldsInfo.slice()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,201 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <el-drawer
 | 
			
		||||
    :append-to-body="true"
 | 
			
		||||
    v-model="settingVisible"
 | 
			
		||||
    :show-close="false"
 | 
			
		||||
    :size="630"
 | 
			
		||||
    :before-close="saveConfig"
 | 
			
		||||
  >
 | 
			
		||||
    <template #header>
 | 
			
		||||
      <div class="config-header">
 | 
			
		||||
        <input
 | 
			
		||||
          v-if="showInput"
 | 
			
		||||
          type="text"
 | 
			
		||||
          class="config-editable-input"
 | 
			
		||||
          @blur="blurEvent()"
 | 
			
		||||
          v-mountedFocus
 | 
			
		||||
          v-model="nodeName"
 | 
			
		||||
          :placeholder="nodeName"
 | 
			
		||||
        />
 | 
			
		||||
        <div v-else class="node-name">
 | 
			
		||||
          {{ nodeName }} <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="divide-line"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </template>
 | 
			
		||||
    <div>
 | 
			
		||||
      <el-form label-position="top">
 | 
			
		||||
        <el-card class="mb-15px" v-for="(item, index) in routerGroups" :key="index">
 | 
			
		||||
          <template #header>
 | 
			
		||||
            <div class="flex flex-items-center">
 | 
			
		||||
              <el-text size="large">路由{{ index + 1 }}</el-text>
 | 
			
		||||
              <el-select class="ml-15px" v-model="item.nodeId" style="width: 180px">
 | 
			
		||||
                <el-option
 | 
			
		||||
                  v-for="node in nodeOptions"
 | 
			
		||||
                  :key="node.value"
 | 
			
		||||
                  :label="node.label"
 | 
			
		||||
                  :value="node.value"
 | 
			
		||||
                />
 | 
			
		||||
              </el-select>
 | 
			
		||||
              <el-button class="mla" type="danger" link @click="deleteRouterGroup(index)">
 | 
			
		||||
                删除
 | 
			
		||||
              </el-button>
 | 
			
		||||
            </div>
 | 
			
		||||
          </template>
 | 
			
		||||
          <Condition
 | 
			
		||||
            :ref="($event) => (conditionRef[index] = $event)"
 | 
			
		||||
            v-model="routerGroups[index]"
 | 
			
		||||
          />
 | 
			
		||||
        </el-card>
 | 
			
		||||
      </el-form>
 | 
			
		||||
 | 
			
		||||
      <el-button class="w-1/1" type="primary" :icon="Plus" @click="addRouterGroup">
 | 
			
		||||
        新增路由分支
 | 
			
		||||
      </el-button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <template #footer>
 | 
			
		||||
      <el-divider />
 | 
			
		||||
      <div>
 | 
			
		||||
        <el-button type="primary" @click="saveConfig">确 定</el-button>
 | 
			
		||||
        <el-button @click="closeDrawer">取 消</el-button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </template>
 | 
			
		||||
  </el-drawer>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { Plus } from '@element-plus/icons-vue'
 | 
			
		||||
import { SimpleFlowNode, NodeType, ConditionType, RouterCondition } from '../consts'
 | 
			
		||||
import { useWatchNode, useDrawer, useNodeName } from '../node'
 | 
			
		||||
import Condition from './components/Condition.vue'
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: 'RouterNodeConfig'
 | 
			
		||||
})
 | 
			
		||||
const message = useMessage() // 消息弹窗
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  flowNode: {
 | 
			
		||||
    type: Object as () => SimpleFlowNode,
 | 
			
		||||
    required: true
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
const processNodeTree = inject<Ref<SimpleFlowNode>>('processNodeTree')
 | 
			
		||||
// 抽屉配置
 | 
			
		||||
const { settingVisible, closeDrawer, openDrawer } = useDrawer()
 | 
			
		||||
// 当前节点
 | 
			
		||||
const currentNode = useWatchNode(props)
 | 
			
		||||
// 节点名称
 | 
			
		||||
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.ROUTER_BRANCH_NODE)
 | 
			
		||||
const routerGroups = ref<RouterCondition[]>([])
 | 
			
		||||
const nodeOptions = ref<any>([])
 | 
			
		||||
const conditionRef = ref([])
 | 
			
		||||
 | 
			
		||||
/** 保存配置 */
 | 
			
		||||
const saveConfig = async () => {
 | 
			
		||||
  // 校验表单
 | 
			
		||||
  let valid = true
 | 
			
		||||
  for (const item of conditionRef.value) {
 | 
			
		||||
    if (item && !(await item.validate())) {
 | 
			
		||||
      valid = false
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (!valid) return false
 | 
			
		||||
  const showText = getShowText()
 | 
			
		||||
  if (!showText) return false
 | 
			
		||||
  currentNode.value.name = nodeName.value!
 | 
			
		||||
  currentNode.value.showText = showText
 | 
			
		||||
  currentNode.value.routerGroups = routerGroups.value
 | 
			
		||||
  settingVisible.value = false
 | 
			
		||||
  return true
 | 
			
		||||
}
 | 
			
		||||
// 显示路由分支节点配置, 由父组件传过来
 | 
			
		||||
const showRouteNodeConfig = (node: SimpleFlowNode) => {
 | 
			
		||||
  getRouterNode(processNodeTree?.value)
 | 
			
		||||
  routerGroups.value = []
 | 
			
		||||
  nodeName.value = node.name
 | 
			
		||||
  if (node.routerGroups) {
 | 
			
		||||
    routerGroups.value = node.routerGroups
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getShowText = () => {
 | 
			
		||||
  if (!routerGroups.value || !Array.isArray(routerGroups.value) || routerGroups.value.length <= 0) {
 | 
			
		||||
    message.warning('请配置路由!')
 | 
			
		||||
    return ''
 | 
			
		||||
  }
 | 
			
		||||
  for (const route of routerGroups.value) {
 | 
			
		||||
    if (!route.nodeId || !route.conditionType) {
 | 
			
		||||
      message.warning('请完善路由配置项!')
 | 
			
		||||
      return ''
 | 
			
		||||
    }
 | 
			
		||||
    if (route.conditionType === ConditionType.EXPRESSION && !route.conditionExpression) {
 | 
			
		||||
      message.warning('请完善路由配置项!')
 | 
			
		||||
      return ''
 | 
			
		||||
    }
 | 
			
		||||
    if (route.conditionType === ConditionType.RULE) {
 | 
			
		||||
      for (const condition of route.conditionGroups.conditions) {
 | 
			
		||||
        for (const rule of condition.rules) {
 | 
			
		||||
          if (!rule.leftSide || !rule.rightSide) {
 | 
			
		||||
            message.warning('请完善路由配置项!')
 | 
			
		||||
            return ''
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return `${routerGroups.value.length}条路由分支`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const addRouterGroup = () => {
 | 
			
		||||
  routerGroups.value.push({
 | 
			
		||||
    nodeId: '',
 | 
			
		||||
    conditionType: ConditionType.RULE,
 | 
			
		||||
    conditionExpression: '',
 | 
			
		||||
    conditionGroups: {
 | 
			
		||||
      and: true,
 | 
			
		||||
      conditions: [
 | 
			
		||||
        {
 | 
			
		||||
          and: true,
 | 
			
		||||
          rules: [
 | 
			
		||||
            {
 | 
			
		||||
              opCode: '==',
 | 
			
		||||
              leftSide: '',
 | 
			
		||||
              rightSide: ''
 | 
			
		||||
            }
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const deleteRouterGroup = (index: number) => {
 | 
			
		||||
  routerGroups.value.splice(index, 1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 递归获取所有节点
 | 
			
		||||
const getRouterNode = (node) => {
 | 
			
		||||
  // TODO 最好还需要满足以下要求
 | 
			
		||||
  // 并行分支、包容分支内部节点不能跳转到外部节点
 | 
			
		||||
  // 条件分支节点可以向上跳转到外部节点
 | 
			
		||||
  while (true) {
 | 
			
		||||
    if (!node) break
 | 
			
		||||
    if (node.type !== NodeType.ROUTER_BRANCH_NODE && node.type !== NodeType.CONDITION_NODE) {
 | 
			
		||||
      nodeOptions.value.push({
 | 
			
		||||
        label: node.name,
 | 
			
		||||
        value: node.id
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    if (!node.childNode || node.type === NodeType.END_EVENT_NODE) {
 | 
			
		||||
      break
 | 
			
		||||
    }
 | 
			
		||||
    if (node.conditionNodes && node.conditionNodes.length) {
 | 
			
		||||
      node.conditionNodes.forEach((item) => {
 | 
			
		||||
        getRouterNode(item)
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    node = node.childNode
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ openDrawer, showRouteNodeConfig }) // 暴露方法给父组件
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -3,7 +3,7 @@
 | 
			
		|||
    :append-to-body="true"
 | 
			
		||||
    v-model="settingVisible"
 | 
			
		||||
    :show-close="false"
 | 
			
		||||
    :size="550"
 | 
			
		||||
    :size="580"
 | 
			
		||||
    :before-close="saveConfig"
 | 
			
		||||
    class="justify-start"
 | 
			
		||||
  >
 | 
			
		||||
| 
						 | 
				
			
			@ -19,7 +19,8 @@
 | 
			
		|||
          :placeholder="nodeName"
 | 
			
		||||
        />
 | 
			
		||||
        <div v-else class="node-name">
 | 
			
		||||
          {{ nodeName }} <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
 | 
			
		||||
          {{ nodeName }}
 | 
			
		||||
          <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="divide-line"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -46,14 +47,13 @@
 | 
			
		|||
                v-model="configForm.candidateStrategy"
 | 
			
		||||
                @change="changeCandidateStrategy"
 | 
			
		||||
              >
 | 
			
		||||
                <el-radio
 | 
			
		||||
                  v-for="(dict, index) in CANDIDATE_STRATEGY"
 | 
			
		||||
                  :key="index"
 | 
			
		||||
                  :value="dict.value"
 | 
			
		||||
                  :label="dict.value"
 | 
			
		||||
                >
 | 
			
		||||
                  {{ dict.label }}
 | 
			
		||||
                </el-radio>
 | 
			
		||||
                <el-row>
 | 
			
		||||
                  <el-col v-for="(dict, index) in CANDIDATE_STRATEGY" :key="index" :span="8">
 | 
			
		||||
                    <el-radio :value="dict.value" :label="dict.value">
 | 
			
		||||
                      {{ dict.label }}
 | 
			
		||||
                    </el-radio>
 | 
			
		||||
                  </el-col>
 | 
			
		||||
                </el-row>
 | 
			
		||||
              </el-radio-group>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item
 | 
			
		||||
| 
						 | 
				
			
			@ -148,7 +148,7 @@
 | 
			
		|||
                  :key="idx"
 | 
			
		||||
                  :label="item.title"
 | 
			
		||||
                  :value="item.field"
 | 
			
		||||
                  :disabled ="!item.required"
 | 
			
		||||
                  :disabled="!item.required"
 | 
			
		||||
                />
 | 
			
		||||
              </el-select>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
| 
						 | 
				
			
			@ -163,7 +163,7 @@
 | 
			
		|||
                  :key="idx"
 | 
			
		||||
                  :label="item.title"
 | 
			
		||||
                  :value="item.field"
 | 
			
		||||
                  :disabled ="!item.required"
 | 
			
		||||
                  :disabled="!item.required"
 | 
			
		||||
                />
 | 
			
		||||
              </el-select>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
| 
						 | 
				
			
			@ -356,6 +356,11 @@
 | 
			
		|||
                </div>
 | 
			
		||||
              </el-radio-group>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
 | 
			
		||||
            <el-divider content-position="left">是否需要签名</el-divider>
 | 
			
		||||
            <el-form-item prop="signEnable">
 | 
			
		||||
              <el-switch v-model="configForm.signEnable" active-text="是" inactive-text="否" />
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
          </el-form>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-tab-pane>
 | 
			
		||||
| 
						 | 
				
			
			@ -435,6 +440,9 @@
 | 
			
		|||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-tab-pane>
 | 
			
		||||
      <el-tab-pane label="监听器" name="listener">
 | 
			
		||||
        <UserTaskListener ref="userTaskListenerRef" v-model="configForm" :form-field-options="formFieldOptions" />
 | 
			
		||||
      </el-tab-pane>
 | 
			
		||||
    </el-tabs>
 | 
			
		||||
    <template #footer>
 | 
			
		||||
      <el-divider />
 | 
			
		||||
| 
						 | 
				
			
			@ -484,6 +492,7 @@ import {
 | 
			
		|||
import { defaultProps } from '@/utils/tree'
 | 
			
		||||
import { cloneDeep } from 'lodash-es'
 | 
			
		||||
import { convertTimeUnit, getApproveTypeText } from '../utils'
 | 
			
		||||
import UserTaskListener from './components/UserTaskListener.vue'
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: 'UserTaskNodeConfig'
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			@ -609,6 +618,8 @@ const {
 | 
			
		|||
  cTimeoutMaxRemindCount
 | 
			
		||||
} = useTimeoutHandler()
 | 
			
		||||
 | 
			
		||||
const userTaskListenerRef = ref()
 | 
			
		||||
 | 
			
		||||
// 保存配置
 | 
			
		||||
const saveConfig = async () => {
 | 
			
		||||
  activeTabName.value = 'user'
 | 
			
		||||
| 
						 | 
				
			
			@ -624,7 +635,8 @@ const saveConfig = async () => {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  if (!formRef) return false
 | 
			
		||||
  const valid = await formRef.value.validate()
 | 
			
		||||
  if (!userTaskListenerRef) return false
 | 
			
		||||
  const valid = (await formRef.value.validate()) && (await userTaskListenerRef.value.validate())
 | 
			
		||||
  if (!valid) return false
 | 
			
		||||
  const showText = getShowText()
 | 
			
		||||
  if (!showText) return false
 | 
			
		||||
| 
						 | 
				
			
			@ -663,6 +675,29 @@ const saveConfig = async () => {
 | 
			
		|||
  currentNode.value.fieldsPermission = fieldsPermissionConfig.value
 | 
			
		||||
  // 设置按钮权限
 | 
			
		||||
  currentNode.value.buttonsSetting = buttonsSetting.value
 | 
			
		||||
  // 创建任务监听器
 | 
			
		||||
  currentNode.value.taskCreateListener = {
 | 
			
		||||
    enable: configForm.value.taskCreateListenerEnable ?? false,
 | 
			
		||||
    path: configForm.value.taskCreateListenerPath,
 | 
			
		||||
    header: configForm.value.taskCreateListenerHeader,
 | 
			
		||||
    body: configForm.value.taskCreateListenerBody
 | 
			
		||||
  }
 | 
			
		||||
  // 指派任务监听器
 | 
			
		||||
  currentNode.value.taskAssignListener = {
 | 
			
		||||
    enable: configForm.value.taskAssignListenerEnable ?? false,
 | 
			
		||||
    path: configForm.value.taskAssignListenerPath,
 | 
			
		||||
    header: configForm.value.taskAssignListenerHeader,
 | 
			
		||||
    body: configForm.value.taskAssignListenerBody
 | 
			
		||||
  }
 | 
			
		||||
  // 完成任务监听器
 | 
			
		||||
  currentNode.value.taskCompleteListener = {
 | 
			
		||||
    enable: configForm.value.taskCompleteListenerEnable ?? false,
 | 
			
		||||
    path: configForm.value.taskCompleteListenerPath,
 | 
			
		||||
    header: configForm.value.taskCompleteListenerHeader,
 | 
			
		||||
    body: configForm.value.taskCompleteListenerBody
 | 
			
		||||
  }
 | 
			
		||||
  // 签名
 | 
			
		||||
  currentNode.value.signEnable = configForm.value.signEnable
 | 
			
		||||
 | 
			
		||||
  currentNode.value.showText = showText
 | 
			
		||||
  settingVisible.value = false
 | 
			
		||||
| 
						 | 
				
			
			@ -714,6 +749,24 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
 | 
			
		|||
  buttonsSetting.value = cloneDeep(node.buttonsSetting) || DEFAULT_BUTTON_SETTING
 | 
			
		||||
  // 4. 表单字段权限配置
 | 
			
		||||
  getNodeConfigFormFields(node.fieldsPermission)
 | 
			
		||||
  // 5. 监听器
 | 
			
		||||
  // 5.1 创建任务
 | 
			
		||||
  configForm.value.taskCreateListenerEnable = node.taskCreateListener!.enable
 | 
			
		||||
  configForm.value.taskCreateListenerPath = node.taskCreateListener!.path
 | 
			
		||||
  configForm.value.taskCreateListenerHeader = node.taskCreateListener?.header ?? []
 | 
			
		||||
  configForm.value.taskCreateListenerBody = node.taskCreateListener?.body ?? []
 | 
			
		||||
  // 5.2 指派任务
 | 
			
		||||
  configForm.value.taskAssignListenerEnable = node.taskAssignListener!.enable
 | 
			
		||||
  configForm.value.taskAssignListenerPath = node.taskAssignListener!.path
 | 
			
		||||
  configForm.value.taskAssignListenerHeader = node.taskAssignListener?.header ?? []
 | 
			
		||||
  configForm.value.taskAssignListenerBody = node.taskAssignListener?.body ?? []
 | 
			
		||||
  // 5.3 完成任务
 | 
			
		||||
  configForm.value.taskCompleteListenerEnable = node.taskCompleteListener!.enable
 | 
			
		||||
  configForm.value.taskCompleteListenerPath = node.taskCompleteListener!.path
 | 
			
		||||
  configForm.value.taskCompleteListenerHeader = node.taskCompleteListener?.header ?? []
 | 
			
		||||
  configForm.value.taskCompleteListenerBody = node.taskCompleteListener?.body ?? []
 | 
			
		||||
  // 6. 签名
 | 
			
		||||
  configForm.value.signEnable = node?.signEnable ?? false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ openDrawer, showUserTaskNodeConfig }) // 暴露方法给父组件
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,263 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <el-form ref="formRef" :model="condition" :rules="formRules" label-position="top">
 | 
			
		||||
    <el-form-item label="配置方式" prop="conditionType">
 | 
			
		||||
      <el-radio-group v-model="condition.conditionType">
 | 
			
		||||
        <el-radio
 | 
			
		||||
          v-for="(dict, indexConditionType) in conditionConfigTypes"
 | 
			
		||||
          :key="indexConditionType"
 | 
			
		||||
          :value="dict.value"
 | 
			
		||||
          :label="dict.value"
 | 
			
		||||
        >
 | 
			
		||||
          {{ dict.label }}
 | 
			
		||||
        </el-radio>
 | 
			
		||||
      </el-radio-group>
 | 
			
		||||
    </el-form-item>
 | 
			
		||||
    <el-form-item v-if="condition.conditionType === ConditionType.RULE" label="条件规则">
 | 
			
		||||
      <div class="condition-group-tool">
 | 
			
		||||
        <div class="flex items-center">
 | 
			
		||||
          <div class="mr-4">条件组关系</div>
 | 
			
		||||
          <el-switch
 | 
			
		||||
            v-model="condition.conditionGroups.and"
 | 
			
		||||
            inline-prompt
 | 
			
		||||
            active-text="且"
 | 
			
		||||
            inactive-text="或"
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <el-space direction="vertical" :spacer="condition.conditionGroups.and ? '且' : '或'">
 | 
			
		||||
        <el-card
 | 
			
		||||
          class="condition-group"
 | 
			
		||||
          style="width: 530px"
 | 
			
		||||
          v-for="(equation, cIdx) in condition.conditionGroups.conditions"
 | 
			
		||||
          :key="cIdx"
 | 
			
		||||
        >
 | 
			
		||||
          <div
 | 
			
		||||
            class="condition-group-delete"
 | 
			
		||||
            v-if="condition.conditionGroups.conditions.length > 1"
 | 
			
		||||
          >
 | 
			
		||||
            <Icon
 | 
			
		||||
              color="#0089ff"
 | 
			
		||||
              icon="ep:circle-close-filled"
 | 
			
		||||
              :size="18"
 | 
			
		||||
              @click="deleteConditionGroup(condition.conditionGroups.conditions, cIdx)"
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
          <template #header>
 | 
			
		||||
            <div class="flex items-center justify-between">
 | 
			
		||||
              <div>条件组</div>
 | 
			
		||||
              <div class="flex">
 | 
			
		||||
                <div class="mr-4">规则关系</div>
 | 
			
		||||
                <el-switch
 | 
			
		||||
                  v-model="equation.and"
 | 
			
		||||
                  inline-prompt
 | 
			
		||||
                  active-text="且"
 | 
			
		||||
                  inactive-text="或"
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </template>
 | 
			
		||||
 | 
			
		||||
          <div class="flex pt-2" v-for="(rule, rIdx) in equation.rules" :key="rIdx">
 | 
			
		||||
            <div class="mr-2">
 | 
			
		||||
              <el-form-item
 | 
			
		||||
                :prop="`conditionGroups.conditions.${cIdx}.rules.${rIdx}.leftSide`"
 | 
			
		||||
                :rules="{
 | 
			
		||||
                  required: true,
 | 
			
		||||
                  message: '左值不能为空',
 | 
			
		||||
                  trigger: 'change'
 | 
			
		||||
                }"
 | 
			
		||||
              >
 | 
			
		||||
                <el-select style="width: 160px" v-model="rule.leftSide">
 | 
			
		||||
                  <el-option
 | 
			
		||||
                    v-for="(field, fIdx) in fieldOptions"
 | 
			
		||||
                    :key="fIdx"
 | 
			
		||||
                    :label="field.title"
 | 
			
		||||
                    :value="field.field"
 | 
			
		||||
                    :disabled="!field.required"
 | 
			
		||||
                  />
 | 
			
		||||
                </el-select>
 | 
			
		||||
              </el-form-item>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="mr-2">
 | 
			
		||||
              <el-select v-model="rule.opCode" style="width: 100px">
 | 
			
		||||
                <el-option
 | 
			
		||||
                  v-for="operator in COMPARISON_OPERATORS"
 | 
			
		||||
                  :key="operator.value"
 | 
			
		||||
                  :label="operator.label"
 | 
			
		||||
                  :value="operator.value"
 | 
			
		||||
                />
 | 
			
		||||
              </el-select>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="mr-2">
 | 
			
		||||
              <el-form-item
 | 
			
		||||
                :prop="`conditionGroups.conditions.${cIdx}.rules.${rIdx}.rightSide`"
 | 
			
		||||
                :rules="{
 | 
			
		||||
                  required: true,
 | 
			
		||||
                  message: '右值不能为空',
 | 
			
		||||
                  trigger: 'blur'
 | 
			
		||||
                }"
 | 
			
		||||
              >
 | 
			
		||||
                <el-input v-model="rule.rightSide" style="width: 160px" />
 | 
			
		||||
              </el-form-item>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="mr-1 flex items-center" v-if="equation.rules.length > 1">
 | 
			
		||||
              <Icon icon="ep:delete" :size="18" @click="deleteConditionRule(equation, rIdx)" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="flex items-center">
 | 
			
		||||
              <Icon icon="ep:plus" :size="18" @click="addConditionRule(equation, rIdx)" />
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-card>
 | 
			
		||||
      </el-space>
 | 
			
		||||
      <div title="添加条件组" class="mt-4 cursor-pointer">
 | 
			
		||||
        <Icon
 | 
			
		||||
          color="#0089ff"
 | 
			
		||||
          icon="ep:plus"
 | 
			
		||||
          :size="24"
 | 
			
		||||
          @click="addConditionGroup(condition.conditionGroups.conditions)"
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    </el-form-item>
 | 
			
		||||
    <el-form-item
 | 
			
		||||
      v-if="condition.conditionType === ConditionType.EXPRESSION"
 | 
			
		||||
      label="条件表达式"
 | 
			
		||||
      prop="conditionExpression"
 | 
			
		||||
    >
 | 
			
		||||
      <el-input
 | 
			
		||||
        type="textarea"
 | 
			
		||||
        v-model="condition.conditionExpression"
 | 
			
		||||
        clearable
 | 
			
		||||
        style="width: 100%"
 | 
			
		||||
      />
 | 
			
		||||
    </el-form-item>
 | 
			
		||||
  </el-form>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import {
 | 
			
		||||
  COMPARISON_OPERATORS,
 | 
			
		||||
  CONDITION_CONFIG_TYPES,
 | 
			
		||||
  ConditionType,
 | 
			
		||||
  ProcessVariableEnum
 | 
			
		||||
} from '../../consts'
 | 
			
		||||
import { BpmModelFormType } from '@/utils/constants'
 | 
			
		||||
import { useFormFields } from '../../node'
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  modelValue: {
 | 
			
		||||
    type: Object,
 | 
			
		||||
    required: true
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
const emit = defineEmits(['update:modelValue'])
 | 
			
		||||
const condition = computed({
 | 
			
		||||
  get() {
 | 
			
		||||
    return props.modelValue
 | 
			
		||||
  },
 | 
			
		||||
  set(newValue) {
 | 
			
		||||
    emit('update:modelValue', newValue)
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
const formType = inject<Ref<number>>('formType') // 表单类型
 | 
			
		||||
const conditionConfigTypes = computed(() => {
 | 
			
		||||
  return CONDITION_CONFIG_TYPES.filter((item) => {
 | 
			
		||||
    // 业务表单暂时去掉条件规则选项
 | 
			
		||||
    if (formType?.value === BpmModelFormType.CUSTOM && item.value === ConditionType.RULE) {
 | 
			
		||||
      return false
 | 
			
		||||
    } else {
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
/** 条件规则可选择的表单字段 */
 | 
			
		||||
const fieldOptions = computed(() => {
 | 
			
		||||
  const fieldsCopy = useFormFields().slice()
 | 
			
		||||
  // 固定添加发起人 ID 字段
 | 
			
		||||
  fieldsCopy.unshift({
 | 
			
		||||
    field: ProcessVariableEnum.START_USER_ID,
 | 
			
		||||
    title: '发起人',
 | 
			
		||||
    required: true
 | 
			
		||||
  })
 | 
			
		||||
  return fieldsCopy
 | 
			
		||||
})
 | 
			
		||||
// 表单校验规则
 | 
			
		||||
const formRules = reactive({
 | 
			
		||||
  conditionType: [{ required: true, message: '配置方式不能为空', trigger: 'blur' }],
 | 
			
		||||
  conditionExpression: [{ required: true, message: '条件表达式不能为空', trigger: 'blur' }]
 | 
			
		||||
})
 | 
			
		||||
const formRef = ref() // 表单 Ref
 | 
			
		||||
 | 
			
		||||
const deleteConditionGroup = (conditions, index) => {
 | 
			
		||||
  conditions.splice(index, 1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const deleteConditionRule = (condition, index) => {
 | 
			
		||||
  condition.rules.splice(index, 1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const addConditionRule = (condition, index) => {
 | 
			
		||||
  const rule = {
 | 
			
		||||
    opCode: '==',
 | 
			
		||||
    leftSide: '',
 | 
			
		||||
    rightSide: ''
 | 
			
		||||
  }
 | 
			
		||||
  condition.rules.splice(index + 1, 0, rule)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const addConditionGroup = (conditions) => {
 | 
			
		||||
  const condition = {
 | 
			
		||||
    and: true,
 | 
			
		||||
    rules: [
 | 
			
		||||
      {
 | 
			
		||||
        opCode: '==',
 | 
			
		||||
        leftSide: '',
 | 
			
		||||
        rightSide: ''
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
  conditions.push(condition)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const validate = async () => {
 | 
			
		||||
  if (!formRef) return false
 | 
			
		||||
  return await formRef.value.validate()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ validate })
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.condition-group-tool {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  width: 500px;
 | 
			
		||||
  margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.condition-group {
 | 
			
		||||
  position: relative;
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    border-color: #0089ff;
 | 
			
		||||
 | 
			
		||||
    .condition-group-delete {
 | 
			
		||||
      opacity: 1;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .condition-group-delete {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::v-deep(.el-card__header) {
 | 
			
		||||
  padding: 8px var(--el-card-padding);
 | 
			
		||||
  border-bottom: 1px solid var(--el-card-border-color);
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,261 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <el-form ref="listenerFormRef" :model="configForm" label-position="top">
 | 
			
		||||
    <div v-for="(listener, listenerIdx) in taskListener" :key="listenerIdx">
 | 
			
		||||
      <el-divider content-position="left">
 | 
			
		||||
        <el-text tag="b" size="large">{{ listener.name }}</el-text>
 | 
			
		||||
      </el-divider>
 | 
			
		||||
      <el-form-item>
 | 
			
		||||
        <el-switch
 | 
			
		||||
          v-model="configForm[`task${listener.type}ListenerEnable`]"
 | 
			
		||||
          active-text="开启"
 | 
			
		||||
          inactive-text="关闭"
 | 
			
		||||
        />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <div v-if="configForm[`task${listener.type}ListenerEnable`]">
 | 
			
		||||
        <el-form-item>
 | 
			
		||||
          <el-alert
 | 
			
		||||
            title="仅支持 POST 请求,以请求体方式接收参数"
 | 
			
		||||
            type="warning"
 | 
			
		||||
            show-icon
 | 
			
		||||
            :closable="false"
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item
 | 
			
		||||
          label="请求地址"
 | 
			
		||||
          :prop="`task${listener.type}ListenerPath`"
 | 
			
		||||
          :rules="{
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请求地址不能为空',
 | 
			
		||||
            trigger: 'blur'
 | 
			
		||||
          }"
 | 
			
		||||
        >
 | 
			
		||||
          <el-input v-model="configForm[`task${listener.type}ListenerPath`]" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="请求头">
 | 
			
		||||
          <div
 | 
			
		||||
            class="flex pt-2"
 | 
			
		||||
            v-for="(item, index) in configForm[`task${listener.type}ListenerHeader`]"
 | 
			
		||||
            :key="index"
 | 
			
		||||
          >
 | 
			
		||||
            <div class="mr-2">
 | 
			
		||||
              <el-form-item
 | 
			
		||||
                :prop="`task${listener.type}ListenerHeader.${index}.key`"
 | 
			
		||||
                :rules="{
 | 
			
		||||
                  required: true,
 | 
			
		||||
                  message: '参数名不能为空',
 | 
			
		||||
                  trigger: 'blur'
 | 
			
		||||
                }"
 | 
			
		||||
              >
 | 
			
		||||
                <el-input class="w-160px" v-model="item.key" />
 | 
			
		||||
              </el-form-item>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="mr-2">
 | 
			
		||||
              <el-select class="w-100px!" v-model="item.type">
 | 
			
		||||
                <el-option
 | 
			
		||||
                  v-for="types in LISTENER_MAP_TYPES"
 | 
			
		||||
                  :key="types.value"
 | 
			
		||||
                  :label="types.label"
 | 
			
		||||
                  :value="types.value"
 | 
			
		||||
                />
 | 
			
		||||
              </el-select>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="mr-2">
 | 
			
		||||
              <el-form-item
 | 
			
		||||
                :prop="`task${listener.type}ListenerHeader.${index}.value`"
 | 
			
		||||
                :rules="{
 | 
			
		||||
                  required: true,
 | 
			
		||||
                  message: '参数值不能为空',
 | 
			
		||||
                  trigger: 'blur'
 | 
			
		||||
                }"
 | 
			
		||||
              >
 | 
			
		||||
                <el-input
 | 
			
		||||
                  v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
 | 
			
		||||
                  class="w-160px"
 | 
			
		||||
                  v-model="item.value"
 | 
			
		||||
                />
 | 
			
		||||
              </el-form-item>
 | 
			
		||||
              <el-form-item
 | 
			
		||||
                :prop="`task${listener.type}ListenerHeader.${index}.value`"
 | 
			
		||||
                :rules="{
 | 
			
		||||
                  required: true,
 | 
			
		||||
                  message: '参数值不能为空',
 | 
			
		||||
                  trigger: 'change'
 | 
			
		||||
                }"
 | 
			
		||||
              >
 | 
			
		||||
                <el-select
 | 
			
		||||
                  v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
 | 
			
		||||
                  class="w-160px!"
 | 
			
		||||
                  v-model="item.value"
 | 
			
		||||
                >
 | 
			
		||||
                  <el-option
 | 
			
		||||
                    v-for="(field, fIdx) in formFieldOptions"
 | 
			
		||||
                    :key="fIdx"
 | 
			
		||||
                    :label="field.title"
 | 
			
		||||
                    :value="field.field"
 | 
			
		||||
                    :disabled="!field.required"
 | 
			
		||||
                  />
 | 
			
		||||
                </el-select>
 | 
			
		||||
              </el-form-item>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="mr-1 flex items-center">
 | 
			
		||||
              <Icon
 | 
			
		||||
                icon="ep:delete"
 | 
			
		||||
                :size="18"
 | 
			
		||||
                @click="
 | 
			
		||||
                  deleteTaskListenerParam(configForm[`task${listener.type}ListenerHeader`], index)
 | 
			
		||||
                "
 | 
			
		||||
              />
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <el-button
 | 
			
		||||
            type="primary"
 | 
			
		||||
            text
 | 
			
		||||
            @click="addTaskListenerParam(configForm[`task${listener.type}ListenerHeader`])"
 | 
			
		||||
          >
 | 
			
		||||
            <Icon icon="ep:plus" class="mr-5px" />添加一行
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="请求体">
 | 
			
		||||
          <div
 | 
			
		||||
            class="flex pt-2"
 | 
			
		||||
            v-for="(item, index) in configForm[`task${listener.type}ListenerBody`]"
 | 
			
		||||
            :key="index"
 | 
			
		||||
          >
 | 
			
		||||
            <div class="mr-2">
 | 
			
		||||
              <el-form-item
 | 
			
		||||
                :prop="`task${listener.type}ListenerBody.${index}.key`"
 | 
			
		||||
                :rules="{
 | 
			
		||||
                  required: true,
 | 
			
		||||
                  message: '参数名不能为空',
 | 
			
		||||
                  trigger: 'blur'
 | 
			
		||||
                }"
 | 
			
		||||
              >
 | 
			
		||||
                <el-input class="w-160px" v-model="item.key" />
 | 
			
		||||
              </el-form-item>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="mr-2">
 | 
			
		||||
              <el-select class="w-100px!" v-model="item.type">
 | 
			
		||||
                <el-option
 | 
			
		||||
                  v-for="types in LISTENER_MAP_TYPES"
 | 
			
		||||
                  :key="types.value"
 | 
			
		||||
                  :label="types.label"
 | 
			
		||||
                  :value="types.value"
 | 
			
		||||
                />
 | 
			
		||||
              </el-select>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="mr-2">
 | 
			
		||||
              <el-form-item
 | 
			
		||||
                :prop="`task${listener.type}ListenerBody.${index}.value`"
 | 
			
		||||
                :rules="{
 | 
			
		||||
                  required: true,
 | 
			
		||||
                  message: '参数值不能为空',
 | 
			
		||||
                  trigger: 'blur'
 | 
			
		||||
                }"
 | 
			
		||||
              >
 | 
			
		||||
                <el-input
 | 
			
		||||
                  v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
 | 
			
		||||
                  class="w-160px"
 | 
			
		||||
                  v-model="item.value"
 | 
			
		||||
                />
 | 
			
		||||
              </el-form-item>
 | 
			
		||||
              <el-form-item
 | 
			
		||||
                :prop="`task${listener.type}ListenerBody.${index}.value`"
 | 
			
		||||
                :rules="{
 | 
			
		||||
                  required: true,
 | 
			
		||||
                  message: '参数值不能为空',
 | 
			
		||||
                  trigger: 'change'
 | 
			
		||||
                }"
 | 
			
		||||
              >
 | 
			
		||||
                <el-select
 | 
			
		||||
                  v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
 | 
			
		||||
                  class="w-160px!"
 | 
			
		||||
                  v-model="item.value"
 | 
			
		||||
                >
 | 
			
		||||
                  <el-option
 | 
			
		||||
                    v-for="(field, fIdx) in formFieldOptions"
 | 
			
		||||
                    :key="fIdx"
 | 
			
		||||
                    :label="field.title"
 | 
			
		||||
                    :value="field.field"
 | 
			
		||||
                    :disabled="!field.required"
 | 
			
		||||
                  />
 | 
			
		||||
                </el-select>
 | 
			
		||||
              </el-form-item>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="mr-1 flex items-center">
 | 
			
		||||
              <Icon
 | 
			
		||||
                icon="ep:delete"
 | 
			
		||||
                :size="18"
 | 
			
		||||
                @click="
 | 
			
		||||
                  deleteTaskListenerParam(configForm[`task${listener.type}ListenerBody`], index)
 | 
			
		||||
                "
 | 
			
		||||
              />
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <el-button
 | 
			
		||||
            type="primary"
 | 
			
		||||
            text
 | 
			
		||||
            @click="addTaskListenerParam(configForm[`task${listener.type}ListenerBody`])"
 | 
			
		||||
          >
 | 
			
		||||
            <Icon icon="ep:plus" class="mr-5px" />添加一行
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </el-form>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { LISTENER_MAP_TYPES, ListenerParamTypeEnum } from '../../consts'
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  modelValue: {
 | 
			
		||||
    type: Object,
 | 
			
		||||
    required: true
 | 
			
		||||
  },
 | 
			
		||||
  formFieldOptions: {
 | 
			
		||||
    type: Object,
 | 
			
		||||
    required: true
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
const emit = defineEmits(['update:modelValue'])
 | 
			
		||||
const listenerFormRef = ref()
 | 
			
		||||
const configForm = computed({
 | 
			
		||||
  get() {
 | 
			
		||||
    return props.modelValue
 | 
			
		||||
  },
 | 
			
		||||
  set(newValue) {
 | 
			
		||||
    emit('update:modelValue', newValue)
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
const taskListener = ref([
 | 
			
		||||
  {
 | 
			
		||||
    name: '创建任务',
 | 
			
		||||
    type: 'Create'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: '指派任务执行人员',
 | 
			
		||||
    type: 'Assign'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: '完成任务',
 | 
			
		||||
    type: 'Complete'
 | 
			
		||||
  }
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
const addTaskListenerParam = (arr) => {
 | 
			
		||||
  arr.push({
 | 
			
		||||
    key: '',
 | 
			
		||||
    type: 1,
 | 
			
		||||
    value: ''
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
const deleteTaskListenerParam = (arr, index) => {
 | 
			
		||||
  arr.splice(index, 1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const validate = async () => {
 | 
			
		||||
  if (!listenerFormRef) return false
 | 
			
		||||
  return await listenerFormRef.value.validate()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ validate })
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			@ -77,7 +77,7 @@ const props = defineProps({
 | 
			
		|||
const currentNode = useWatchNode(props)
 | 
			
		||||
// 是否只读
 | 
			
		||||
const readonly = inject<Boolean>('readonly')
 | 
			
		||||
const processInstance = inject<Ref<any>>('processInstance')
 | 
			
		||||
const processInstance = inject<Ref<any>>('processInstance', ref({}))
 | 
			
		||||
// 审批信息的弹窗显示,用于只读模式
 | 
			
		||||
const dialogVisible = ref(false) // 弹窗可见性
 | 
			
		||||
const processInstanceInfos = ref<any[]>([]) // 流程的审批信息
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @芋艿 需要更换一下iconfont的图标 -->
 | 
			
		||||
          <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.ROUTER_BRANCH_NODE) }}
 | 
			
		||||
          </div>
 | 
			
		||||
          <Icon v-if="!readonly" icon="ep:arrow-right-bold" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div v-if="!readonly" class="node-toolbar">
 | 
			
		||||
          <div class="toolbar-icon"
 | 
			
		||||
            ><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
 | 
			
		||||
          /></div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
 | 
			
		||||
      <NodeHandler
 | 
			
		||||
        v-if="currentNode"
 | 
			
		||||
        v-model:child-node="currentNode.childNode"
 | 
			
		||||
        :current-node="currentNode"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
    <RouterNodeConfig v-if="!readonly && currentNode" ref="nodeSetting" :flow-node="currentNode" />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
 | 
			
		||||
import NodeHandler from '../NodeHandler.vue'
 | 
			
		||||
import { useNodeName2, useWatchNode, useTaskStatusClass } from '../node'
 | 
			
		||||
import RouterNodeConfig from '../nodes-config/RouterNodeConfig.vue'
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: 'RouterNode'
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  flowNode: {
 | 
			
		||||
    type: Object as () => SimpleFlowNode,
 | 
			
		||||
    required: true
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
// 定义事件,更新父组件
 | 
			
		||||
const emits = defineEmits<{
 | 
			
		||||
  'update:flowNode': [node: SimpleFlowNode | undefined]
 | 
			
		||||
}>()
 | 
			
		||||
// 是否只读
 | 
			
		||||
const readonly = inject<Boolean>('readonly')
 | 
			
		||||
// 监控节点的变化
 | 
			
		||||
const currentNode = useWatchNode(props)
 | 
			
		||||
// 节点名称编辑
 | 
			
		||||
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.ROUTER_BRANCH_NODE)
 | 
			
		||||
 | 
			
		||||
const nodeSetting = ref()
 | 
			
		||||
// 打开节点配置
 | 
			
		||||
const openNodeConfig = () => {
 | 
			
		||||
  if (readonly) {
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
  nodeSetting.value.showRouteNodeConfig(currentNode.value)
 | 
			
		||||
  nodeSetting.value.openDrawer()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 删除节点。更新当前节点为孩子节点
 | 
			
		||||
const deleteNode = () => {
 | 
			
		||||
  emits('update:flowNode', currentNode.value.childNode)
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped></style>
 | 
			
		||||
| 
						 | 
				
			
			@ -13,7 +13,7 @@
 | 
			
		|||
            ><span class="iconfont icon-start-user"></span
 | 
			
		||||
          ></div>
 | 
			
		||||
          <input
 | 
			
		||||
            v-if="showInput"
 | 
			
		||||
            v-if="!readonly && showInput"
 | 
			
		||||
            type="text"
 | 
			
		||||
            class="editable-title-input"
 | 
			
		||||
            @blur="blurEvent()"
 | 
			
		||||
| 
						 | 
				
			
			@ -117,7 +117,7 @@ const props = defineProps({
 | 
			
		|||
  }
 | 
			
		||||
})
 | 
			
		||||
const readonly = inject<Boolean>('readonly') // 是否只读
 | 
			
		||||
const tasks = inject<Ref<any[]>>('tasks')
 | 
			
		||||
const tasks = inject<Ref<any[]>>('tasks', ref([]))
 | 
			
		||||
// 定义事件,更新父组件。
 | 
			
		||||
const emits = defineEmits<{
 | 
			
		||||
  'update:modelValue': [node: SimpleFlowNode | undefined]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -131,7 +131,7 @@ const emits = defineEmits<{
 | 
			
		|||
 | 
			
		||||
// 是否只读
 | 
			
		||||
const readonly = inject<Boolean>('readonly')
 | 
			
		||||
const tasks = inject<Ref<any[]>>('tasks')
 | 
			
		||||
const tasks = inject<Ref<any[]>>('tasks', ref([]))
 | 
			
		||||
// 监控节点变化
 | 
			
		||||
const currentNode = useWatchNode(props)
 | 
			
		||||
// 节点名称编辑
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -308,28 +308,6 @@ const props = defineProps({
 | 
			
		|||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// 监听value变化,重新加载流程图
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.value,
 | 
			
		||||
  (newValue) => {
 | 
			
		||||
    if (newValue && bpmnModeler) {
 | 
			
		||||
      createNewDiagram(newValue)
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  { immediate: true }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 监听processId和processName变化
 | 
			
		||||
watch(
 | 
			
		||||
  [() => props.processId, () => props.processName],
 | 
			
		||||
  ([newId, newName]) => {
 | 
			
		||||
    if (newId && newName && !props.value) {
 | 
			
		||||
      createNewDiagram(null)
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  { immediate: true }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
provide('configGlobal', props)
 | 
			
		||||
let bpmnModeler: any = null
 | 
			
		||||
const defaultZoom = ref(1)
 | 
			
		||||
| 
						 | 
				
			
			@ -480,6 +458,7 @@ const initModelListeners = () => {
 | 
			
		|||
      emit('commandStack-changed', event)
 | 
			
		||||
      emit('input', xml)
 | 
			
		||||
      emit('change', xml)
 | 
			
		||||
      emit('save', xml)
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
      console.error(`[Process Designer Warn]: ${e.message || e}`)
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -568,6 +547,7 @@ const importLocalFile = () => {
 | 
			
		|||
  reader.onload = function () {
 | 
			
		||||
    let xmlStr = this.result
 | 
			
		||||
    createNewDiagram(xmlStr)
 | 
			
		||||
    emit('save', xmlStr)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
/* ------------------------------------------------ refs methods ------------------------------------------------------ */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1438,6 +1438,31 @@
 | 
			
		|||
          "isBody": true
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "name": "SignEnable",
 | 
			
		||||
      "superClass": ["Element"],
 | 
			
		||||
      "meta": {
 | 
			
		||||
        "allowedIn": ["bpmn:UserTask"]
 | 
			
		||||
      },
 | 
			
		||||
      "properties": [
 | 
			
		||||
        {
 | 
			
		||||
          "name": "value",
 | 
			
		||||
          "type": "Boolean",
 | 
			
		||||
          "isBody": true
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "name": "SkipExpression",
 | 
			
		||||
      "extends": ["bpmn:UserTask"],
 | 
			
		||||
      "properties": [
 | 
			
		||||
        {
 | 
			
		||||
          "name": "skipExpression",
 | 
			
		||||
          "isAttr": true,
 | 
			
		||||
          "type": "String"
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "emumerations": []
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="process-panel__container" :style="{ width: `${width}px` }">
 | 
			
		||||
  <div class="process-panel__container" :style="{ width: `${width}px`, maxHeight: '600px' }">
 | 
			
		||||
    <el-collapse v-model="activeTab" v-if="isReady">
 | 
			
		||||
      <el-collapse-item name="base">
 | 
			
		||||
        <!-- class="panel-tab__title" -->
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -152,6 +152,9 @@ watch(
 | 
			
		|||
      handleKeyUpdate(props.model.key)
 | 
			
		||||
      handleNameUpdate(props.model.name)
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    immediate: true
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@
 | 
			
		|||
     4. 操作按钮
 | 
			
		||||
     5. 字段权限
 | 
			
		||||
     6. 审批类型
 | 
			
		||||
     7. 是否需要签名
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
| 
						 | 
				
			
			@ -161,6 +162,11 @@
 | 
			
		|||
        </el-radio-group>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <el-divider content-position="left">是否需要签名</el-divider>
 | 
			
		||||
    <el-form-item prop="signEnable">
 | 
			
		||||
      <el-switch v-model="signEnable.value" active-text="是" inactive-text="否" />
 | 
			
		||||
    </el-form-item>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -218,6 +224,9 @@ const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFie
 | 
			
		|||
// 审批类型
 | 
			
		||||
const approveType = ref({ value: ApproveType.USER })
 | 
			
		||||
 | 
			
		||||
// 是否需要签名
 | 
			
		||||
const signEnable = ref({ value: false })
 | 
			
		||||
 | 
			
		||||
const elExtensionElements = ref()
 | 
			
		||||
const otherExtensions = ref()
 | 
			
		||||
const bpmnElement = ref()
 | 
			
		||||
| 
						 | 
				
			
			@ -325,6 +334,11 @@ const resetCustomConfigList = () => {
 | 
			
		|||
        ex.$type !== `${prefix}:ApproveType`
 | 
			
		||||
    ) ?? []
 | 
			
		||||
 | 
			
		||||
  // 是否需要签名
 | 
			
		||||
  signEnable.value =
 | 
			
		||||
    elExtensionElements.value.values?.filter((ex) => ex.$type === `${prefix}:SignEnable`)?.[0] ||
 | 
			
		||||
    bpmnInstances().moddle.create(`${prefix}:SignEnable`, { value: false })
 | 
			
		||||
 | 
			
		||||
  // 更新元素扩展属性,避免后续报错
 | 
			
		||||
  updateElementExtensions()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -373,7 +387,8 @@ const updateElementExtensions = () => {
 | 
			
		|||
      assignEmptyUserIdsEl.value,
 | 
			
		||||
      approveType.value,
 | 
			
		||||
      ...buttonsSettingEl.value,
 | 
			
		||||
      ...fieldsPermissionEl.value
 | 
			
		||||
      ...fieldsPermissionEl.value,
 | 
			
		||||
      signEnable.value
 | 
			
		||||
    ]
 | 
			
		||||
  })
 | 
			
		||||
  bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -192,6 +192,16 @@
 | 
			
		|||
      <!-- 选择弹窗 -->
 | 
			
		||||
      <ProcessExpressionDialog ref="processExpressionDialogRef" @select="selectProcessExpression" />
 | 
			
		||||
    </el-form-item>
 | 
			
		||||
 | 
			
		||||
    <el-form-item label="跳过表达式" prop="skipExpression">
 | 
			
		||||
      <el-input
 | 
			
		||||
        type="textarea"
 | 
			
		||||
        v-model="userTaskForm.skipExpression"
 | 
			
		||||
        clearable
 | 
			
		||||
        style="width: 100%"
 | 
			
		||||
        @change="updateSkipExpression"
 | 
			
		||||
      />
 | 
			
		||||
    </el-form-item>
 | 
			
		||||
  </el-form>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -220,7 +230,8 @@ const props = defineProps({
 | 
			
		|||
const prefix = inject('prefix')
 | 
			
		||||
const userTaskForm = ref({
 | 
			
		||||
  candidateStrategy: undefined, // 分配规则
 | 
			
		||||
  candidateParam: [] // 分配选项
 | 
			
		||||
  candidateParam: [], // 分配选项
 | 
			
		||||
  skipExpression: '' // 跳过表达式
 | 
			
		||||
})
 | 
			
		||||
const bpmnElement = ref()
 | 
			
		||||
const bpmnInstances = () => (window as any)?.bpmnInstances
 | 
			
		||||
| 
						 | 
				
			
			@ -311,6 +322,13 @@ const resetTaskForm = () => {
 | 
			
		|||
      (ex) => ex.$type !== `${prefix}:CandidateStrategy` && ex.$type !== `${prefix}:CandidateParam`
 | 
			
		||||
    ) ?? []
 | 
			
		||||
 | 
			
		||||
  // 跳过表达式
 | 
			
		||||
  if (businessObject.skipExpression != undefined) {
 | 
			
		||||
    userTaskForm.value.skipExpression = businessObject.skipExpression
 | 
			
		||||
  } else {
 | 
			
		||||
    userTaskForm.value.skipExpression = ''
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 改用通过extensionElements来存储数据
 | 
			
		||||
  return
 | 
			
		||||
  if (businessObject.candidateStrategy != undefined) {
 | 
			
		||||
| 
						 | 
				
			
			@ -390,6 +408,18 @@ const updateElementTask = () => {
 | 
			
		|||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const updateSkipExpression = () => {
 | 
			
		||||
  if (userTaskForm.value.skipExpression && userTaskForm.value.skipExpression !== '') {
 | 
			
		||||
    bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
 | 
			
		||||
      skipExpression: userTaskForm.value.skipExpression
 | 
			
		||||
    })
 | 
			
		||||
  } else {
 | 
			
		||||
    bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
 | 
			
		||||
      skipExpression: null
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 打开监听器弹窗
 | 
			
		||||
const processExpressionDialogRef = ref()
 | 
			
		||||
const openProcessExpressionDialog = async () => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,4 @@
 | 
			
		|||
@use 'bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css';
 | 
			
		||||
@use 'bpmn-js-token-simulation/assets/css/font-awesome.min.css';
 | 
			
		||||
@use 'bpmn-js-token-simulation/assets/css/normalize.css';
 | 
			
		||||
 | 
			
		||||
// 边框被 token-simulation 样式覆盖了
 | 
			
		||||
.djs-palette {
 | 
			
		||||
| 
						 | 
				
			
			@ -97,12 +95,12 @@
 | 
			
		|||
        box-sizing: border-box;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    svg {
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      min-height: 100%;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
    }
 | 
			
		||||
    // svg {
 | 
			
		||||
    //   width: 100%;
 | 
			
		||||
    //   height: 100%;
 | 
			
		||||
    //   min-height: 100%;
 | 
			
		||||
    //   overflow: hidden;
 | 
			
		||||
    // }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,10 @@ import { useDesign } from '@/hooks/web/useDesign'
 | 
			
		|||
import { useTemplateRefsList } from '@vueuse/core'
 | 
			
		||||
import { ElScrollbar } from 'element-plus'
 | 
			
		||||
import { useScrollTo } from '@/hooks/event/useScrollTo'
 | 
			
		||||
import { useTagsView } from '@/hooks/web/useTagsView'
 | 
			
		||||
import { cloneDeep } from 'lodash-es'
 | 
			
		||||
 | 
			
		||||
defineOptions({ name: 'TagsView' })
 | 
			
		||||
 | 
			
		||||
const { getPrefixCls } = useDesign()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -19,7 +23,9 @@ const prefixCls = getPrefixCls('tags-view')
 | 
			
		|||
 | 
			
		||||
const { t } = useI18n()
 | 
			
		||||
 | 
			
		||||
const { currentRoute, push, replace } = useRouter()
 | 
			
		||||
const { currentRoute, push } = useRouter()
 | 
			
		||||
 | 
			
		||||
const { closeAll, closeLeft, closeRight, closeOther, closeCurrent, refreshPage } = useTagsView()
 | 
			
		||||
 | 
			
		||||
const permissionStore = usePermissionStore()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -31,6 +37,10 @@ const visitedViews = computed(() => tagsViewStore.getVisitedViews)
 | 
			
		|||
 | 
			
		||||
const affixTagArr = ref<RouteLocationNormalizedLoaded[]>([])
 | 
			
		||||
 | 
			
		||||
const selectedTag = computed(() => tagsViewStore.getSelectedTag)
 | 
			
		||||
 | 
			
		||||
const setSelectTag = tagsViewStore.setSelectedTag
 | 
			
		||||
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
 | 
			
		||||
const tagsViewImmerse = computed(() => appStore.getTagsViewImmerse)
 | 
			
		||||
| 
						 | 
				
			
			@ -45,66 +55,30 @@ const initTags = () => {
 | 
			
		|||
  for (const tag of unref(affixTagArr)) {
 | 
			
		||||
    // Must have tag name
 | 
			
		||||
    if (tag.name) {
 | 
			
		||||
      tagsViewStore.addVisitedView(tag)
 | 
			
		||||
      tagsViewStore.addVisitedView(cloneDeep(tag))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const selectedTag = ref<RouteLocationNormalizedLoaded>()
 | 
			
		||||
 | 
			
		||||
// 新增tag
 | 
			
		||||
const addTags = () => {
 | 
			
		||||
  const { name } = unref(currentRoute)
 | 
			
		||||
  if (name) {
 | 
			
		||||
    selectedTag.value = unref(currentRoute)
 | 
			
		||||
    setSelectTag(unref(currentRoute))
 | 
			
		||||
    tagsViewStore.addView(unref(currentRoute))
 | 
			
		||||
  }
 | 
			
		||||
  return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 关闭选中的tag
 | 
			
		||||
const closeSelectedTag = (view: RouteLocationNormalizedLoaded) => {
 | 
			
		||||
  if (view?.meta?.affix) return
 | 
			
		||||
  tagsViewStore.delView(view)
 | 
			
		||||
  if (isActive(view)) {
 | 
			
		||||
    toLastView()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 关闭全部
 | 
			
		||||
const closeAllTags = () => {
 | 
			
		||||
  tagsViewStore.delAllViews()
 | 
			
		||||
  toLastView()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 关闭其它
 | 
			
		||||
const closeOthersTags = () => {
 | 
			
		||||
  tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 重新加载
 | 
			
		||||
const refreshSelectedTag = async (view?: RouteLocationNormalizedLoaded) => {
 | 
			
		||||
  if (!view) return
 | 
			
		||||
  tagsViewStore.delCachedView()
 | 
			
		||||
  const { path, query } = view
 | 
			
		||||
  await nextTick()
 | 
			
		||||
  replace({
 | 
			
		||||
    path: '/redirect' + path,
 | 
			
		||||
    query: query
 | 
			
		||||
  closeCurrent(view, () => {
 | 
			
		||||
    if (isActive(view)) {
 | 
			
		||||
      toLastView()
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 关闭左侧
 | 
			
		||||
const closeLeftTags = () => {
 | 
			
		||||
  tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 关闭右侧
 | 
			
		||||
const closeRightTags = () => {
 | 
			
		||||
  tagsViewStore.delRightViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 跳转到最后一个
 | 
			
		||||
// 去最后一个
 | 
			
		||||
const toLastView = () => {
 | 
			
		||||
  const visitedViews = tagsViewStore.getVisitedViews
 | 
			
		||||
  const latestView = visitedViews.slice(-1)[0]
 | 
			
		||||
| 
						 | 
				
			
			@ -118,11 +92,38 @@ const toLastView = () => {
 | 
			
		|||
      addTags()
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    // TODO: You can set another route
 | 
			
		||||
    push('/')
 | 
			
		||||
    // You can set another route
 | 
			
		||||
    push(permissionStore.getAddRouters[0].path)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 关闭全部
 | 
			
		||||
const closeAllTags = () => {
 | 
			
		||||
  closeAll(() => {
 | 
			
		||||
    toLastView()
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 关闭其它
 | 
			
		||||
const closeOthersTags = () => {
 | 
			
		||||
  closeOther()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 重新加载
 | 
			
		||||
const refreshSelectedTag = async (view?: RouteLocationNormalizedLoaded) => {
 | 
			
		||||
  refreshPage(view)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 关闭左侧
 | 
			
		||||
const closeLeftTags = () => {
 | 
			
		||||
  closeLeft()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 关闭右侧
 | 
			
		||||
const closeRightTags = () => {
 | 
			
		||||
  closeRight()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 滚动到选中的tag
 | 
			
		||||
const moveToCurrentTag = async () => {
 | 
			
		||||
  await nextTick()
 | 
			
		||||
| 
						 | 
				
			
			@ -209,13 +210,14 @@ const isActive = (route: RouteLocationNormalizedLoaded): boolean => {
 | 
			
		|||
// 所有右键菜单组件的元素
 | 
			
		||||
const itemRefs = useTemplateRefsList<ComponentRef<typeof ContextMenu & ContextMenuExpose>>()
 | 
			
		||||
 | 
			
		||||
// 右键菜单装填改变的时候
 | 
			
		||||
// 右键菜单状态改变的时候
 | 
			
		||||
const visibleChange = (visible: boolean, tagItem: RouteLocationNormalizedLoaded) => {
 | 
			
		||||
  if (visible) {
 | 
			
		||||
    for (const v of unref(itemRefs)) {
 | 
			
		||||
      const elDropdownMenuRef = v.elDropdownMenuRef
 | 
			
		||||
      if (tagItem.fullPath !== v.tagItem.fullPath) {
 | 
			
		||||
        elDropdownMenuRef?.handleClose()
 | 
			
		||||
        setSelectTag(tagItem)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -243,7 +245,17 @@ const move = (to: number) => {
 | 
			
		|||
  start()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
const canShowIcon = (item: RouteLocationNormalizedLoaded) => {
 | 
			
		||||
  if (
 | 
			
		||||
    (item?.matched?.[1]?.meta?.icon && unref(tagsViewIcon)) ||
 | 
			
		||||
    (item?.meta?.affix && unref(tagsViewIcon) && item?.meta?.icon)
 | 
			
		||||
  ) {
 | 
			
		||||
    return true
 | 
			
		||||
  }
 | 
			
		||||
  return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onBeforeMount(() => {
 | 
			
		||||
  initTags()
 | 
			
		||||
  addTags()
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -197,7 +197,7 @@ export const useRenderLayout = () => {
 | 
			
		|||
              `${prefixCls}-content-scrollbar`,
 | 
			
		||||
              {
 | 
			
		||||
                '!h-[calc(100%-var(--tags-view-height))] mt-[calc(var(--tags-view-height))]':
 | 
			
		||||
                  fixedHeader.value
 | 
			
		||||
                  fixedHeader.value && tagsView.value
 | 
			
		||||
              }
 | 
			
		||||
            ]}
 | 
			
		||||
          >
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -141,7 +141,10 @@ export default {
 | 
			
		|||
    btnQRCode: '二维码登录',
 | 
			
		||||
    qrcode: '扫描二维码登录',
 | 
			
		||||
    btnRegister: '注册',
 | 
			
		||||
    SmsSendMsg: '验证码已发送'
 | 
			
		||||
    SmsSendMsg: '验证码已发送',
 | 
			
		||||
    resetPassword: "重置密码",
 | 
			
		||||
    resetPasswordSuccess: "重置密码成功",
 | 
			
		||||
    invalidTenantName: "无效的租户名称"
 | 
			
		||||
  },
 | 
			
		||||
  captcha: {
 | 
			
		||||
    verification: '请完成安全验证',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -344,7 +344,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
 | 
			
		|||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: 'manager/model/update/:id',
 | 
			
		||||
        // TODO @zws:1)建议,在加一个路由。然后标题是“复制流程”,这样体验会好点;2)复制出来的数据,在名字前面,加“副本 ”,和钉钉保持一致!
 | 
			
		||||
        path: 'manager/model/:type/:id',
 | 
			
		||||
        component: () => import('@/views/bpm/model/form/index.vue'),
 | 
			
		||||
        name: 'BpmModelUpdate',
 | 
			
		||||
        meta: {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,16 +4,19 @@ import { getRawRoute } from '@/utils/routerHelper'
 | 
			
		|||
import { defineStore } from 'pinia'
 | 
			
		||||
import { store } from '../index'
 | 
			
		||||
import { findIndex } from '@/utils'
 | 
			
		||||
import { useUserStoreWithOut } from './user'
 | 
			
		||||
 | 
			
		||||
export interface TagsViewState {
 | 
			
		||||
  visitedViews: RouteLocationNormalizedLoaded[]
 | 
			
		||||
  cachedViews: Set<string>
 | 
			
		||||
  selectedTag?: RouteLocationNormalizedLoaded
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const useTagsViewStore = defineStore('tagsView', {
 | 
			
		||||
  state: (): TagsViewState => ({
 | 
			
		||||
    visitedViews: [],
 | 
			
		||||
    cachedViews: new Set()
 | 
			
		||||
    cachedViews: new Set(),
 | 
			
		||||
    selectedTag: undefined
 | 
			
		||||
  }),
 | 
			
		||||
  getters: {
 | 
			
		||||
    getVisitedViews(): RouteLocationNormalizedLoaded[] {
 | 
			
		||||
| 
						 | 
				
			
			@ -21,6 +24,9 @@ export const useTagsViewStore = defineStore('tagsView', {
 | 
			
		|||
    },
 | 
			
		||||
    getCachedViews(): string[] {
 | 
			
		||||
      return Array.from(this.cachedViews)
 | 
			
		||||
    },
 | 
			
		||||
    getSelectedTag(): RouteLocationNormalizedLoaded | undefined {
 | 
			
		||||
      return this.selectedTag
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  actions: {
 | 
			
		||||
| 
						 | 
				
			
			@ -98,8 +104,12 @@ export const useTagsViewStore = defineStore('tagsView', {
 | 
			
		|||
    },
 | 
			
		||||
    // 删除所有tag
 | 
			
		||||
    delAllVisitedViews() {
 | 
			
		||||
      const userStore = useUserStoreWithOut()
 | 
			
		||||
 | 
			
		||||
      // const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)
 | 
			
		||||
      this.visitedViews = []
 | 
			
		||||
      this.visitedViews = userStore.getUser
 | 
			
		||||
        ? this.visitedViews.filter((tag) => tag?.meta?.affix)
 | 
			
		||||
        : []
 | 
			
		||||
    },
 | 
			
		||||
    // 删除其他
 | 
			
		||||
    delOthersViews(view: RouteLocationNormalizedLoaded) {
 | 
			
		||||
| 
						 | 
				
			
			@ -145,6 +155,18 @@ export const useTagsViewStore = defineStore('tagsView', {
 | 
			
		|||
          break
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    // 设置当前选中的 tag
 | 
			
		||||
    setSelectedTag(tag: RouteLocationNormalizedLoaded) {
 | 
			
		||||
      this.selectedTag = tag
 | 
			
		||||
    },
 | 
			
		||||
    setTitle(title: string, path?: string) {
 | 
			
		||||
      for (const v of this.visitedViews) {
 | 
			
		||||
        if (v.path === (path ?? this.selectedTag?.path)) {
 | 
			
		||||
          v.meta.title = title
 | 
			
		||||
          break
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  persist: false
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,6 +33,10 @@ const download = {
 | 
			
		|||
  markdown: (data: Blob, fileName: string) => {
 | 
			
		||||
    download0(data, fileName, 'text/markdown')
 | 
			
		||||
  },
 | 
			
		||||
  // 下载 Json 方法
 | 
			
		||||
  json: (data: Blob, fileName: string) => {
 | 
			
		||||
    download0(data, fileName, 'application/json')
 | 
			
		||||
  },
 | 
			
		||||
  // 下载图片(允许跨域)
 | 
			
		||||
  image: ({
 | 
			
		||||
    url,
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +69,31 @@ const download = {
 | 
			
		|||
      a.download = 'image.png'
 | 
			
		||||
      a.click()
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  base64ToFile: (base64: any, fileName: string) => {
 | 
			
		||||
    // 将base64按照 , 进行分割 将前缀  与后续内容分隔开
 | 
			
		||||
    const data = base64.split(',')
 | 
			
		||||
    // 利用正则表达式 从前缀中获取图片的类型信息(image/png、image/jpeg、image/webp等)
 | 
			
		||||
    const type = data[0].match(/:(.*?);/)[1]
 | 
			
		||||
    // 从图片的类型信息中 获取具体的文件格式后缀(png、jpeg、webp)
 | 
			
		||||
    const suffix = type.split('/')[1]
 | 
			
		||||
    // 使用atob()对base64数据进行解码  结果是一个文件数据流 以字符串的格式输出
 | 
			
		||||
    const bstr = window.atob(data[1])
 | 
			
		||||
    // 获取解码结果字符串的长度
 | 
			
		||||
    let n = bstr.length
 | 
			
		||||
    // 根据解码结果字符串的长度创建一个等长的整形数字数组
 | 
			
		||||
    // 但在创建时 所有元素初始值都为 0
 | 
			
		||||
    const u8arr = new Uint8Array(n)
 | 
			
		||||
    // 将整形数组的每个元素填充为解码结果字符串对应位置字符的UTF-16 编码单元
 | 
			
		||||
    while (n--) {
 | 
			
		||||
      // charCodeAt():获取给定索引处字符对应的 UTF-16 代码单元
 | 
			
		||||
      u8arr[n] = bstr.charCodeAt(n)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 将File文件对象返回给方法的调用者
 | 
			
		||||
    return new File([u8arr], `${fileName}.${suffix}`, {
 | 
			
		||||
      type: type
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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}`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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' })
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -133,6 +133,7 @@
 | 
			
		|||
                  </el-form-item>
 | 
			
		||||
                </el-col>
 | 
			
		||||
                <Verify
 | 
			
		||||
                  v-if="loginData.captchaEnable === 'true'"
 | 
			
		||||
                  ref="verify"
 | 
			
		||||
                  :captchaType="captchaType"
 | 
			
		||||
                  :imgSize="{ width: '400px', height: '200px' }"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,278 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <el-form
 | 
			
		||||
    v-show="getShow"
 | 
			
		||||
    ref="formSmsResetPassword"
 | 
			
		||||
    :model="resetPasswordData"
 | 
			
		||||
    :rules="rules"
 | 
			
		||||
    class="login-form"
 | 
			
		||||
    label-position="top"
 | 
			
		||||
    label-width="120px"
 | 
			
		||||
    size="large"
 | 
			
		||||
  >
 | 
			
		||||
    <el-row style="margin-right: -10px; margin-left: -10px">
 | 
			
		||||
      <!-- 租户名 -->
 | 
			
		||||
      <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
 | 
			
		||||
        <el-form-item>
 | 
			
		||||
          <LoginFormTitle style="width: 100%" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
 | 
			
		||||
        <el-form-item v-if="resetPasswordData.tenantEnable === 'true'" prop="tenantName">
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model="resetPasswordData.tenantName"
 | 
			
		||||
            :placeholder="t('login.tenantNamePlaceholder')"
 | 
			
		||||
            :prefix-icon="iconHouse"
 | 
			
		||||
            type="primary"
 | 
			
		||||
            link
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <!-- 手机号 -->
 | 
			
		||||
      <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
 | 
			
		||||
        <el-form-item prop="mobile">
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model="resetPasswordData.mobile"
 | 
			
		||||
            :placeholder="t('login.mobileNumberPlaceholder')"
 | 
			
		||||
            :prefix-icon="iconCellphone"
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <Verify
 | 
			
		||||
        ref="verify"
 | 
			
		||||
        :captchaType="captchaType"
 | 
			
		||||
        :imgSize="{ width: '400px', height: '200px' }"
 | 
			
		||||
        mode="pop"
 | 
			
		||||
        @success="getSmsCode"
 | 
			
		||||
      />
 | 
			
		||||
      <!-- 验证码 -->
 | 
			
		||||
      <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
 | 
			
		||||
        <el-form-item prop="code">
 | 
			
		||||
          <el-row :gutter="5" justify="space-between" style="width: 100%">
 | 
			
		||||
            <el-col :span="24">
 | 
			
		||||
              <el-input
 | 
			
		||||
                v-model="resetPasswordData.code"
 | 
			
		||||
                :placeholder="t('login.codePlaceholder')"
 | 
			
		||||
                :prefix-icon="iconCircleCheck"
 | 
			
		||||
              >
 | 
			
		||||
                <template #append>
 | 
			
		||||
                  <span
 | 
			
		||||
                    v-if="mobileCodeTimer <= 0"
 | 
			
		||||
                    class="getMobileCode"
 | 
			
		||||
                    style="cursor: pointer"
 | 
			
		||||
                    @click="getCode"
 | 
			
		||||
                  >
 | 
			
		||||
                    {{ t('login.getSmsCode') }}
 | 
			
		||||
                  </span>
 | 
			
		||||
                  <span v-if="mobileCodeTimer > 0" class="getMobileCode" style="cursor: pointer">
 | 
			
		||||
                    {{ mobileCodeTimer }}秒后可重新获取
 | 
			
		||||
                  </span>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-input>
 | 
			
		||||
              <!-- </el-button> -->
 | 
			
		||||
            </el-col>
 | 
			
		||||
          </el-row>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
 | 
			
		||||
        <el-form-item prop="password">
 | 
			
		||||
          <InputPassword
 | 
			
		||||
            v-model="resetPasswordData.password"
 | 
			
		||||
            :placeholder="t('login.passwordPlaceholder')"
 | 
			
		||||
            style="width: 100%"
 | 
			
		||||
            strength="true"
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
 | 
			
		||||
        <el-form-item prop="check_password">
 | 
			
		||||
          <InputPassword
 | 
			
		||||
            v-model="resetPasswordData.check_password"
 | 
			
		||||
            :placeholder="t('login.checkPassword')"
 | 
			
		||||
            style="width: 100%"
 | 
			
		||||
            strength="true"
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <!-- 登录按钮 / 返回按钮 -->
 | 
			
		||||
      <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
 | 
			
		||||
        <el-form-item>
 | 
			
		||||
          <XButton
 | 
			
		||||
            :loading="loginLoading"
 | 
			
		||||
            :title="t('login.resetPassword')"
 | 
			
		||||
            class="w-[100%]"
 | 
			
		||||
            type="primary"
 | 
			
		||||
            @click="resetPassword()"
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
 | 
			
		||||
        <el-form-item>
 | 
			
		||||
          <XButton
 | 
			
		||||
            :loading="loginLoading"
 | 
			
		||||
            :title="t('login.backLogin')"
 | 
			
		||||
            class="w-[100%]"
 | 
			
		||||
            @click="handleBackLogin()"
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-col>
 | 
			
		||||
    </el-row>
 | 
			
		||||
  </el-form>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
 | 
			
		||||
 | 
			
		||||
import { useIcon } from '@/hooks/web/useIcon'
 | 
			
		||||
 | 
			
		||||
import { sendSmsCode, smsResetPassword } from '@/api/login'
 | 
			
		||||
import LoginFormTitle from './LoginFormTitle.vue'
 | 
			
		||||
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
 | 
			
		||||
import { ElLoading } from 'element-plus'
 | 
			
		||||
import * as authUtil from '@/utils/auth'
 | 
			
		||||
import * as LoginApi from '@/api/login'
 | 
			
		||||
defineOptions({ name: 'ForgetPasswordForm' })
 | 
			
		||||
const verify = ref()
 | 
			
		||||
 | 
			
		||||
const { t } = useI18n()
 | 
			
		||||
const message = useMessage()
 | 
			
		||||
const { currentRoute, push } = useRouter()
 | 
			
		||||
const formSmsResetPassword = ref()
 | 
			
		||||
const loginLoading = ref(false)
 | 
			
		||||
const iconHouse = useIcon({ icon: 'ep:house' })
 | 
			
		||||
const iconCellphone = useIcon({ icon: 'ep:cellphone' })
 | 
			
		||||
const iconCircleCheck = useIcon({ icon: 'ep:circle-check' })
 | 
			
		||||
const { validForm } = useFormValid(formSmsResetPassword)
 | 
			
		||||
const { handleBackLogin, getLoginState, setLoginState } = useLoginState()
 | 
			
		||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.RESET_PASSWORD)
 | 
			
		||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
 | 
			
		||||
 | 
			
		||||
const validatePass2 = (rule, value, callback) => {
 | 
			
		||||
  if (value === '') {
 | 
			
		||||
    callback(new Error('请再次输入密码'))
 | 
			
		||||
  } else if (value !== resetPasswordData.password) {
 | 
			
		||||
    callback(new Error('两次输入密码不一致!'))
 | 
			
		||||
  } else {
 | 
			
		||||
    callback()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const rules = {
 | 
			
		||||
  tenantName: [{ required: true, min: 2, max: 20, trigger: 'blur', message: '长度为4到16位' }],
 | 
			
		||||
  mobile: [{ required: true, min: 11, max: 11, trigger: 'blur', message: '手机号长度为11位' }],
 | 
			
		||||
  password: [
 | 
			
		||||
    {
 | 
			
		||||
      required: true,
 | 
			
		||||
      min: 4,
 | 
			
		||||
      max: 16,
 | 
			
		||||
      validator: validatePass2,
 | 
			
		||||
      trigger: 'blur',
 | 
			
		||||
      message: '密码长度为4到16位'
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  check_password: [{ required: true, validator: validatePass2, trigger: 'blur' }],
 | 
			
		||||
  code: [required]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const resetPasswordData = reactive({
 | 
			
		||||
  captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
 | 
			
		||||
  tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
 | 
			
		||||
  tenantName: '',
 | 
			
		||||
  username: '',
 | 
			
		||||
  password: '',
 | 
			
		||||
  check_password: '',
 | 
			
		||||
  mobile: '',
 | 
			
		||||
  code: ''
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const smsVO = reactive({
 | 
			
		||||
  tenantName: '',
 | 
			
		||||
  mobile: '',
 | 
			
		||||
  captchaVerification: '',
 | 
			
		||||
  scene: 23
 | 
			
		||||
})
 | 
			
		||||
const mobileCodeTimer = ref(0)
 | 
			
		||||
const redirect = ref<string>('')
 | 
			
		||||
 | 
			
		||||
// 获取验证码
 | 
			
		||||
const getCode = async () => {
 | 
			
		||||
  // 情况一,未开启:则直接发送验证码
 | 
			
		||||
  if (resetPasswordData.captchaEnable === 'false') {
 | 
			
		||||
    await getSmsCode({})
 | 
			
		||||
  } else {
 | 
			
		||||
    // 情况二,已开启:则展示验证码;只有完成验证码的情况,才进行发送验证码
 | 
			
		||||
    // 弹出验证码
 | 
			
		||||
    verify.value.show()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getSmsCode = async (params) => {
 | 
			
		||||
  if (resetPasswordData.tenantEnable === 'true') {
 | 
			
		||||
    await getTenantId()
 | 
			
		||||
  }
 | 
			
		||||
  smsVO.captchaVerification = params.captchaVerification
 | 
			
		||||
  smsVO.mobile = resetPasswordData.mobile
 | 
			
		||||
  await sendSmsCode(smsVO).then(async () => {
 | 
			
		||||
    message.success(t('login.SmsSendMsg'))
 | 
			
		||||
    // 设置倒计时
 | 
			
		||||
    mobileCodeTimer.value = 60
 | 
			
		||||
    let msgTimer = setInterval(() => {
 | 
			
		||||
      mobileCodeTimer.value = mobileCodeTimer.value - 1
 | 
			
		||||
      if (mobileCodeTimer.value <= 0) {
 | 
			
		||||
        clearInterval(msgTimer)
 | 
			
		||||
      }
 | 
			
		||||
    }, 1000)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
watch(
 | 
			
		||||
  () => currentRoute.value,
 | 
			
		||||
  (route: RouteLocationNormalizedLoaded) => {
 | 
			
		||||
    redirect.value = route?.query?.redirect as string
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    immediate: true
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const getTenantId = async () => {
 | 
			
		||||
  if (resetPasswordData.tenantEnable === 'true') {
 | 
			
		||||
    const res = await LoginApi.getTenantIdByName(resetPasswordData.tenantName)
 | 
			
		||||
    if (res == null) {
 | 
			
		||||
      message.error(t('login.invalidTenantName'))
 | 
			
		||||
      throw t('login.invalidTenantName')
 | 
			
		||||
    }
 | 
			
		||||
    authUtil.setTenantId(res)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 重置密码
 | 
			
		||||
const resetPassword = async () => {
 | 
			
		||||
  const data = await validForm()
 | 
			
		||||
  if (!data) return
 | 
			
		||||
  await getTenantId()
 | 
			
		||||
  loginLoading.value = true
 | 
			
		||||
  await smsResetPassword(resetPasswordData)
 | 
			
		||||
    .then(async () => {
 | 
			
		||||
      message.success(t('login.resetPasswordSuccess'))
 | 
			
		||||
      setLoginState(LoginStateEnum.LOGIN)
 | 
			
		||||
    })
 | 
			
		||||
    .catch(() => {})
 | 
			
		||||
    .finally(() => {
 | 
			
		||||
      loginLoading.value = false
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        const loadingInstance = ElLoading.service()
 | 
			
		||||
        loadingInstance.close()
 | 
			
		||||
      }, 400)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
:deep(.anticon) {
 | 
			
		||||
  &:hover {
 | 
			
		||||
    color: var(--el-color-primary) !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.smsbtn {
 | 
			
		||||
  margin-top: 33px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -59,7 +59,13 @@
 | 
			
		|||
              </el-checkbox>
 | 
			
		||||
            </el-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(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -85,6 +85,7 @@
 | 
			
		|||
        </el-form-item>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <Verify
 | 
			
		||||
        v-if="registerData.captchaEnable === 'true'"
 | 
			
		||||
        ref="verify"
 | 
			
		||||
        :captchaType="captchaType"
 | 
			
		||||
        :imgSize="{ width: '400px', height: '200px' }"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
| 
						 | 
				
			
			@ -164,13 +165,12 @@
 | 
			
		|||
            </el-button>
 | 
			
		||||
            <el-button
 | 
			
		||||
              link
 | 
			
		||||
              class="!ml-5px"
 | 
			
		||||
              type="primary"
 | 
			
		||||
              @click="handleDesign(scope.row)"
 | 
			
		||||
              @click="openModelForm('copy', scope.row.id)"
 | 
			
		||||
              v-hasPermi="['bpm:model:update']"
 | 
			
		||||
              :disabled="!isManagerUser(scope.row)"
 | 
			
		||||
            >
 | 
			
		||||
              设计
 | 
			
		||||
              复制
 | 
			
		||||
            </el-button>
 | 
			
		||||
            <el-button
 | 
			
		||||
              link
 | 
			
		||||
| 
						 | 
				
			
			@ -203,6 +203,14 @@
 | 
			
		|||
                  >
 | 
			
		||||
                    {{ scope.row.processDefinition.suspensionState === 1 ? '停用' : '启用' }}
 | 
			
		||||
                  </el-dropdown-item>
 | 
			
		||||
                  <el-dropdown-item
 | 
			
		||||
                    type="danger"
 | 
			
		||||
                    command="handleClean"
 | 
			
		||||
                    v-if="checkPermi(['bpm:model:clean'])"
 | 
			
		||||
                    :disabled="!isManagerUser(scope.row)"
 | 
			
		||||
                  >
 | 
			
		||||
                    清理
 | 
			
		||||
                  </el-dropdown-item>
 | 
			
		||||
                  <el-dropdown-item
 | 
			
		||||
                    type="danger"
 | 
			
		||||
                    command="handleDelete"
 | 
			
		||||
| 
						 | 
				
			
			@ -235,13 +243,9 @@
 | 
			
		|||
      </div>
 | 
			
		||||
    </template>
 | 
			
		||||
  </Dialog>
 | 
			
		||||
 | 
			
		||||
  <!-- 表单弹窗:添加流程模型 -->
 | 
			
		||||
  <ModelForm :categoryId="categoryInfo.code" ref="modelFormRef" @success="emit('success')" />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import ModelForm from './ModelForm.vue'
 | 
			
		||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
 | 
			
		||||
import Sortable from 'sortablejs'
 | 
			
		||||
import { propTypes } from '@/utils/propTypes'
 | 
			
		||||
| 
						 | 
				
			
			@ -254,6 +258,7 @@ import { checkPermi } from '@/utils/permission'
 | 
			
		|||
import { useUserStoreWithOut } from '@/store/modules/user'
 | 
			
		||||
import { useAppStore } from '@/store/modules/app'
 | 
			
		||||
import { cloneDeep } from 'lodash-es'
 | 
			
		||||
import { useTagsView } from '@/hooks/web/useTagsView'
 | 
			
		||||
 | 
			
		||||
defineOptions({ name: 'BpmModel' })
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -285,6 +290,9 @@ const handleModelCommand = (command: string, row: any) => {
 | 
			
		|||
    case 'handleChangeState':
 | 
			
		||||
      handleChangeState(row)
 | 
			
		||||
      break
 | 
			
		||||
    case 'handleClean':
 | 
			
		||||
      handleClean(row)
 | 
			
		||||
      break
 | 
			
		||||
    default:
 | 
			
		||||
      break
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -318,6 +326,19 @@ const handleDelete = async (row: any) => {
 | 
			
		|||
  } catch {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 清理按钮操作 */
 | 
			
		||||
const handleClean = async (row: any) => {
 | 
			
		||||
  try {
 | 
			
		||||
    // 清理的二次确认
 | 
			
		||||
    await message.confirm('是否确认清理流程名字为"' + row.name + '"的数据项?')
 | 
			
		||||
    // 发起清理
 | 
			
		||||
    await ModelApi.cleanModel(row.id)
 | 
			
		||||
    message.success('清理成功')
 | 
			
		||||
    // 刷新列表
 | 
			
		||||
    emit('success')
 | 
			
		||||
  } catch {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 更新状态操作 */
 | 
			
		||||
const handleChangeState = async (row: any) => {
 | 
			
		||||
  const state = row.processDefinition.suspensionState
 | 
			
		||||
| 
						 | 
				
			
			@ -337,14 +358,6 @@ const handleChangeState = async (row: any) => {
 | 
			
		|||
  } catch {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 设计流程 */
 | 
			
		||||
const handleDesign = (row: any) => {
 | 
			
		||||
  push({
 | 
			
		||||
    name: 'BpmModelUpdate',
 | 
			
		||||
    params: { id: row.id }
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 发布流程 */
 | 
			
		||||
const handleDeploy = async (row: any) => {
 | 
			
		||||
  try {
 | 
			
		||||
| 
						 | 
				
			
			@ -483,15 +496,19 @@ const handleDeleteCategory = async () => {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/** 添加流程模型弹窗 */
 | 
			
		||||
const modelFormRef = ref()
 | 
			
		||||
const openModelForm = (type: string, id?: number) => {
 | 
			
		||||
const tagsView = useTagsView()
 | 
			
		||||
const openModelForm = async (type: string, id?: number) => {
 | 
			
		||||
  if (type === 'create') {
 | 
			
		||||
    push({ name: 'BpmModelCreate' })
 | 
			
		||||
    await push({ name: 'BpmModelCreate' })
 | 
			
		||||
  } else {
 | 
			
		||||
    push({
 | 
			
		||||
    await push({
 | 
			
		||||
      name: 'BpmModelUpdate',
 | 
			
		||||
      params: { id }
 | 
			
		||||
      params: { id, type }
 | 
			
		||||
    })
 | 
			
		||||
    // 设置标题
 | 
			
		||||
    if (type === 'copy') {
 | 
			
		||||
      tagsView.setTitle('复制流程')
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,440 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <Dialog v-model="dialogVisible" :title="dialogTitle" width="600">
 | 
			
		||||
    <el-form
 | 
			
		||||
      ref="formRef"
 | 
			
		||||
      v-loading="formLoading"
 | 
			
		||||
      :model="formData"
 | 
			
		||||
      :rules="formRules"
 | 
			
		||||
      label-width="110px"
 | 
			
		||||
    >
 | 
			
		||||
      <el-form-item label="流程标识" prop="key">
 | 
			
		||||
        <el-input v-model="formData.key" :disabled="!!formData.id" placeholder="请输入流标标识" />
 | 
			
		||||
        <el-tooltip
 | 
			
		||||
          v-if="!formData.id"
 | 
			
		||||
          class="item"
 | 
			
		||||
          content="新建后,流程标识不可修改!"
 | 
			
		||||
          effect="light"
 | 
			
		||||
          placement="top"
 | 
			
		||||
        >
 | 
			
		||||
          <i class="el-icon-question" style="padding-left: 5px"></i>
 | 
			
		||||
        </el-tooltip>
 | 
			
		||||
        <el-tooltip v-else class="item" content="流程标识不可修改!" effect="light" placement="top">
 | 
			
		||||
          <i class="el-icon-question" style="padding-left: 5px"></i>
 | 
			
		||||
        </el-tooltip>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="流程名称" prop="name">
 | 
			
		||||
        <el-input
 | 
			
		||||
          v-model="formData.name"
 | 
			
		||||
          :disabled="!!formData.id"
 | 
			
		||||
          clearable
 | 
			
		||||
          placeholder="请输入流程名称"
 | 
			
		||||
        />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="流程分类" prop="category">
 | 
			
		||||
        <el-select
 | 
			
		||||
          v-model="formData.category"
 | 
			
		||||
          clearable
 | 
			
		||||
          placeholder="请选择流程分类"
 | 
			
		||||
          style="width: 100%"
 | 
			
		||||
        >
 | 
			
		||||
          <el-option
 | 
			
		||||
            v-for="category in categoryList"
 | 
			
		||||
            :key="category.code"
 | 
			
		||||
            :label="category.name"
 | 
			
		||||
            :value="category.code"
 | 
			
		||||
          />
 | 
			
		||||
        </el-select>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="流程图标" prop="icon">
 | 
			
		||||
        <UploadImg v-model="formData.icon" :limit="1" height="64px" width="64px" />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="流程描述" prop="description">
 | 
			
		||||
        <el-input v-model="formData.description" clearable type="textarea" />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="流程类型" prop="type">
 | 
			
		||||
        <el-radio-group v-model="formData.type">
 | 
			
		||||
          <el-radio
 | 
			
		||||
            v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_TYPE)"
 | 
			
		||||
            :key="dict.value"
 | 
			
		||||
            :label="dict.value"
 | 
			
		||||
          >
 | 
			
		||||
            {{ dict.label }}
 | 
			
		||||
          </el-radio>
 | 
			
		||||
        </el-radio-group>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="表单类型" prop="formType">
 | 
			
		||||
        <el-radio-group v-model="formData.formType">
 | 
			
		||||
          <el-radio
 | 
			
		||||
            v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)"
 | 
			
		||||
            :key="dict.value"
 | 
			
		||||
            :label="dict.value"
 | 
			
		||||
          >
 | 
			
		||||
            {{ dict.label }}
 | 
			
		||||
          </el-radio>
 | 
			
		||||
        </el-radio-group>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item v-if="formData.formType === 10" label="流程表单" prop="formId">
 | 
			
		||||
        <el-select v-model="formData.formId" clearable style="width: 100%">
 | 
			
		||||
          <el-option v-for="form in formList" :key="form.id" :label="form.name" :value="form.id" />
 | 
			
		||||
        </el-select>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item
 | 
			
		||||
        v-if="formData.formType === 20"
 | 
			
		||||
        label="表单提交路由"
 | 
			
		||||
        prop="formCustomCreatePath"
 | 
			
		||||
      >
 | 
			
		||||
        <el-input
 | 
			
		||||
          v-model="formData.formCustomCreatePath"
 | 
			
		||||
          placeholder="请输入表单提交路由"
 | 
			
		||||
          style="width: 330px"
 | 
			
		||||
        />
 | 
			
		||||
        <el-tooltip
 | 
			
		||||
          class="item"
 | 
			
		||||
          content="自定义表单的提交路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/create.vue"
 | 
			
		||||
          effect="light"
 | 
			
		||||
          placement="top"
 | 
			
		||||
        >
 | 
			
		||||
          <i class="el-icon-question" style="padding-left: 5px"></i>
 | 
			
		||||
        </el-tooltip>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item v-if="formData.formType === 20" label="表单查看地址" prop="formCustomViewPath">
 | 
			
		||||
        <el-input
 | 
			
		||||
          v-model="formData.formCustomViewPath"
 | 
			
		||||
          placeholder="请输入表单查看的组件地址"
 | 
			
		||||
          style="width: 330px"
 | 
			
		||||
        />
 | 
			
		||||
        <el-tooltip
 | 
			
		||||
          class="item"
 | 
			
		||||
          content="自定义表单的查看组件地址,使用 Vue 的组件地址,例如说:bpm/oa/leave/detail.vue"
 | 
			
		||||
          effect="light"
 | 
			
		||||
          placement="top"
 | 
			
		||||
        >
 | 
			
		||||
          <i class="el-icon-question" style="padding-left: 5px"></i>
 | 
			
		||||
        </el-tooltip>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="是否可见" prop="visible">
 | 
			
		||||
        <el-radio-group v-model="formData.visible">
 | 
			
		||||
          <el-radio
 | 
			
		||||
            v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
 | 
			
		||||
            :key="dict.value as string"
 | 
			
		||||
            :label="dict.value"
 | 
			
		||||
          >
 | 
			
		||||
            {{ dict.label }}
 | 
			
		||||
          </el-radio>
 | 
			
		||||
        </el-radio-group>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="谁可以发起" prop="startUserType">
 | 
			
		||||
        <el-select
 | 
			
		||||
          v-model="formData.startUserType"
 | 
			
		||||
          placeholder="请选择谁可以发起"
 | 
			
		||||
          @change="handleStartUserTypeChange"
 | 
			
		||||
        >
 | 
			
		||||
          <el-option label="全员" :value="0" />
 | 
			
		||||
          <el-option label="指定人员" :value="1" />
 | 
			
		||||
          <el-option label="均不可提交" :value="2" />
 | 
			
		||||
        </el-select>
 | 
			
		||||
        <div v-if="formData.startUserType === 1" class="mt-2 flex flex-wrap gap-2">
 | 
			
		||||
          <div
 | 
			
		||||
            v-for="user in selectedStartUsers"
 | 
			
		||||
            :key="user.id"
 | 
			
		||||
            class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
 | 
			
		||||
          >
 | 
			
		||||
            <el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
 | 
			
		||||
            <el-avatar class="!m-5px" :size="28" v-else>
 | 
			
		||||
              {{ user.nickname.substring(0, 1) }}
 | 
			
		||||
            </el-avatar>
 | 
			
		||||
            {{ user.nickname }}
 | 
			
		||||
            <Icon
 | 
			
		||||
              icon="ep:close"
 | 
			
		||||
              class="ml-2 cursor-pointer hover:text-red-500"
 | 
			
		||||
              @click="handleRemoveStartUser(user)"
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
          <el-button type="primary" link @click="openStartUserSelect">
 | 
			
		||||
            <Icon icon="ep:plus" />选择人员
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="流程管理员" prop="managerUserType">
 | 
			
		||||
        <el-select
 | 
			
		||||
          v-model="formData.managerUserType"
 | 
			
		||||
          placeholder="请选择流程管理员"
 | 
			
		||||
          @change="handleManagerUserTypeChange"
 | 
			
		||||
        >
 | 
			
		||||
          <el-option label="全员" :value="0" />
 | 
			
		||||
          <el-option label="指定人员" :value="1" />
 | 
			
		||||
          <el-option label="均不可提交" :value="2" />
 | 
			
		||||
        </el-select>
 | 
			
		||||
        <div v-if="formData.managerUserType === 1" class="mt-2 flex flex-wrap gap-2">
 | 
			
		||||
          <div
 | 
			
		||||
            v-for="user in selectedManagerUsers"
 | 
			
		||||
            :key="user.id"
 | 
			
		||||
            class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
 | 
			
		||||
          >
 | 
			
		||||
            <el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
 | 
			
		||||
            <el-avatar class="!m-5px" :size="28" v-else>
 | 
			
		||||
              {{ user.nickname.substring(0, 1) }}
 | 
			
		||||
            </el-avatar>
 | 
			
		||||
            {{ user.nickname }}
 | 
			
		||||
            <Icon
 | 
			
		||||
              icon="ep:close"
 | 
			
		||||
              class="ml-2 cursor-pointer hover:text-red-500"
 | 
			
		||||
              @click="handleRemoveManagerUser(user)"
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
          <el-button type="primary" link @click="openManagerUserSelect">
 | 
			
		||||
            <Icon icon="ep:plus" />选择人员
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
    </el-form>
 | 
			
		||||
    <template #footer>
 | 
			
		||||
      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
 | 
			
		||||
      <el-button @click="dialogVisible = false">取 消</el-button>
 | 
			
		||||
    </template>
 | 
			
		||||
  </Dialog>
 | 
			
		||||
  <UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { propTypes } from '@/utils/propTypes'
 | 
			
		||||
import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
 | 
			
		||||
import { ElMessageBox } from 'element-plus'
 | 
			
		||||
import * as ModelApi from '@/api/bpm/model'
 | 
			
		||||
import * as FormApi from '@/api/bpm/form'
 | 
			
		||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
 | 
			
		||||
import { BpmModelFormType, BpmModelType } from '@/utils/constants'
 | 
			
		||||
import { UserVO } from '@/api/system/user'
 | 
			
		||||
import * as UserApi from '@/api/system/user'
 | 
			
		||||
import { useUserStoreWithOut } from '@/store/modules/user'
 | 
			
		||||
import { FormVO } from '@/api/bpm/form'
 | 
			
		||||
 | 
			
		||||
defineOptions({ name: 'ModelForm' })
 | 
			
		||||
 | 
			
		||||
const { t } = useI18n() // 国际化
 | 
			
		||||
const message = useMessage() // 消息弹窗
 | 
			
		||||
const userStore = useUserStoreWithOut() // 用户信息缓存
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  categoryId: propTypes.number
 | 
			
		||||
})
 | 
			
		||||
const dialogVisible = ref(false) // 弹窗的是否展示
 | 
			
		||||
const dialogTitle = ref('') // 弹窗的标题
 | 
			
		||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 | 
			
		||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
 | 
			
		||||
const formData: any = ref({
 | 
			
		||||
  id: undefined,
 | 
			
		||||
  name: '',
 | 
			
		||||
  key: '',
 | 
			
		||||
  category: undefined,
 | 
			
		||||
  icon: undefined,
 | 
			
		||||
  description: '',
 | 
			
		||||
  type: BpmModelType.BPMN,
 | 
			
		||||
  formType: BpmModelFormType.NORMAL,
 | 
			
		||||
  formId: '',
 | 
			
		||||
  formCustomCreatePath: '',
 | 
			
		||||
  formCustomViewPath: '',
 | 
			
		||||
  visible: true,
 | 
			
		||||
  startUserType: undefined,
 | 
			
		||||
  managerUserType: undefined,
 | 
			
		||||
  startUserIds: [],
 | 
			
		||||
  managerUserIds: []
 | 
			
		||||
})
 | 
			
		||||
const formRules = reactive({
 | 
			
		||||
  name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
 | 
			
		||||
  key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
 | 
			
		||||
  category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }],
 | 
			
		||||
  icon: [{ required: true, message: '流程图标不能为空', trigger: 'blur' }],
 | 
			
		||||
  type: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
 | 
			
		||||
  formType: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
 | 
			
		||||
  formId: [{ required: true, message: '流程表单不能为空', trigger: 'blur' }],
 | 
			
		||||
  formCustomCreatePath: [{ required: true, message: '表单提交路由不能为空', trigger: 'blur' }],
 | 
			
		||||
  formCustomViewPath: [{ required: true, message: '表单查看地址不能为空', trigger: 'blur' }],
 | 
			
		||||
  visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
 | 
			
		||||
  managerUserIds: [{ required: true, message: '流程管理员不能为空', trigger: 'blur' }]
 | 
			
		||||
})
 | 
			
		||||
const formRef = ref() // 表单 Ref
 | 
			
		||||
const formList = ref<FormVO[]>([]) // 流程表单的下拉框的数据
 | 
			
		||||
const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
 | 
			
		||||
const userList = ref<UserVO[]>([]) // 用户列表
 | 
			
		||||
const selectedStartUsers = ref<UserVO[]>([]) // 已选择的发起人列表
 | 
			
		||||
const selectedManagerUsers = ref<UserVO[]>([]) // 已选择的管理员列表
 | 
			
		||||
const userSelectFormRef = ref() // 用户选择弹窗 ref
 | 
			
		||||
const currentSelectType = ref<'start' | 'manager'>('start') // 当前选择的是发起人还是管理员
 | 
			
		||||
 | 
			
		||||
/** 打开弹窗 */
 | 
			
		||||
const open = async (type: string, id?: string) => {
 | 
			
		||||
  dialogVisible.value = true
 | 
			
		||||
  dialogTitle.value = t('action.' + type)
 | 
			
		||||
  formType.value = type
 | 
			
		||||
  resetForm()
 | 
			
		||||
  // 修改时,设置数据
 | 
			
		||||
  if (id) {
 | 
			
		||||
    formLoading.value = true
 | 
			
		||||
    try {
 | 
			
		||||
      formData.value = await ModelApi.getModel(id)
 | 
			
		||||
    } finally {
 | 
			
		||||
      formLoading.value = false
 | 
			
		||||
    }
 | 
			
		||||
    // 加载数据时,根据已有的用户ID列表初始化已选用户
 | 
			
		||||
    if (formData.value.startUserIds?.length) {
 | 
			
		||||
      formData.value.startUserType = 1
 | 
			
		||||
      selectedStartUsers.value = userList.value.filter((user) =>
 | 
			
		||||
        formData.value.startUserIds.includes(user.id)
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
    if (formData.value.managerUserIds?.length) {
 | 
			
		||||
      formData.value.managerUserType = 1
 | 
			
		||||
      selectedManagerUsers.value = userList.value.filter((user) =>
 | 
			
		||||
        formData.value.managerUserIds.includes(user.id)
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    formData.value.managerUserIds.push(userStore.getUser.id)
 | 
			
		||||
  }
 | 
			
		||||
  // 获得流程表单的下拉框的数据
 | 
			
		||||
  formList.value = await FormApi.getFormSimpleList()
 | 
			
		||||
  // 查询流程分类列表
 | 
			
		||||
  categoryList.value = await CategoryApi.getCategorySimpleList()
 | 
			
		||||
  // 查询用户列表
 | 
			
		||||
  userList.value = await UserApi.getSimpleUserList()
 | 
			
		||||
  if (props.categoryId) {
 | 
			
		||||
    formData.value.category = props.categoryId
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 | 
			
		||||
 | 
			
		||||
/** 提交表单 */
 | 
			
		||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
 | 
			
		||||
const submitForm = async () => {
 | 
			
		||||
  // 校验表单
 | 
			
		||||
  if (!formRef) return
 | 
			
		||||
  const valid = await formRef.value.validate()
 | 
			
		||||
  if (!valid) return
 | 
			
		||||
  // 提交请求
 | 
			
		||||
  formLoading.value = true
 | 
			
		||||
  try {
 | 
			
		||||
    const data = formData.value as unknown as ModelApi.ModelVO
 | 
			
		||||
    if (formType.value === 'create') {
 | 
			
		||||
      await ModelApi.createModel(data)
 | 
			
		||||
      // 提示,引导用户做后续的操作
 | 
			
		||||
      await ElMessageBox.alert(
 | 
			
		||||
        '<strong>新建模型成功!</strong>后续需要执行如下 2 个步骤:' +
 | 
			
		||||
          '<div>1. 点击【设计流程】按钮,绘制流程图</div>' +
 | 
			
		||||
          '<div>2. 点击【发布流程】按钮,完成流程的最终发布</div>' +
 | 
			
		||||
          '另外,每次流程修改后,都需要点击【发布流程】按钮,才能正式生效!!!',
 | 
			
		||||
        '重要提示',
 | 
			
		||||
        {
 | 
			
		||||
          dangerouslyUseHTMLString: true,
 | 
			
		||||
          type: 'success'
 | 
			
		||||
        }
 | 
			
		||||
      )
 | 
			
		||||
    } else {
 | 
			
		||||
      await ModelApi.updateModel(data)
 | 
			
		||||
      message.success(t('common.updateSuccess'))
 | 
			
		||||
    }
 | 
			
		||||
    dialogVisible.value = false
 | 
			
		||||
    // 发送操作成功的事件
 | 
			
		||||
    emit('success')
 | 
			
		||||
  } finally {
 | 
			
		||||
    formLoading.value = false
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 重置表单 */
 | 
			
		||||
const resetForm = () => {
 | 
			
		||||
  formData.value = {
 | 
			
		||||
    id: undefined,
 | 
			
		||||
    name: '',
 | 
			
		||||
    key: '',
 | 
			
		||||
    category: undefined,
 | 
			
		||||
    icon: undefined,
 | 
			
		||||
    description: '',
 | 
			
		||||
    type: BpmModelType.BPMN,
 | 
			
		||||
    formType: BpmModelFormType.NORMAL,
 | 
			
		||||
    formId: '',
 | 
			
		||||
    formCustomCreatePath: '',
 | 
			
		||||
    formCustomViewPath: '',
 | 
			
		||||
    visible: true,
 | 
			
		||||
    startUserType: undefined,
 | 
			
		||||
    managerUserType: undefined,
 | 
			
		||||
    startUserIds: [],
 | 
			
		||||
    managerUserIds: []
 | 
			
		||||
  }
 | 
			
		||||
  formRef.value?.resetFields()
 | 
			
		||||
  selectedStartUsers.value = []
 | 
			
		||||
  selectedManagerUsers.value = []
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 处理发起人类型变化 */
 | 
			
		||||
const handleStartUserTypeChange = (value: number) => {
 | 
			
		||||
  if (value !== 1) {
 | 
			
		||||
    selectedStartUsers.value = []
 | 
			
		||||
    formData.value.startUserIds = []
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 处理管理员类型变化 */
 | 
			
		||||
const handleManagerUserTypeChange = (value: number) => {
 | 
			
		||||
  if (value !== 1) {
 | 
			
		||||
    selectedManagerUsers.value = []
 | 
			
		||||
    formData.value.managerUserIds = []
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 打开发起人选择 */
 | 
			
		||||
const openStartUserSelect = () => {
 | 
			
		||||
  currentSelectType.value = 'start'
 | 
			
		||||
  userSelectFormRef.value.open(0, selectedStartUsers.value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 打开管理员选择 */
 | 
			
		||||
const openManagerUserSelect = () => {
 | 
			
		||||
  currentSelectType.value = 'manager'
 | 
			
		||||
  userSelectFormRef.value.open(0, selectedManagerUsers.value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 处理用户选择确认 */
 | 
			
		||||
const handleUserSelectConfirm = (_, users: UserVO[]) => {
 | 
			
		||||
  if (currentSelectType.value === 'start') {
 | 
			
		||||
    selectedStartUsers.value = users
 | 
			
		||||
    formData.value.startUserIds = users.map((u) => u.id)
 | 
			
		||||
  } else {
 | 
			
		||||
    selectedManagerUsers.value = users
 | 
			
		||||
    formData.value.managerUserIds = users.map((u) => u.id)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 移除发起人 */
 | 
			
		||||
const handleRemoveStartUser = (user: UserVO) => {
 | 
			
		||||
  selectedStartUsers.value = selectedStartUsers.value.filter((u) => u.id !== user.id)
 | 
			
		||||
  formData.value.startUserIds = formData.value.startUserIds.filter((id: number) => id !== user.id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 移除管理员 */
 | 
			
		||||
const handleRemoveManagerUser = (user: UserVO) => {
 | 
			
		||||
  selectedManagerUsers.value = selectedManagerUsers.value.filter((u) => u.id !== user.id)
 | 
			
		||||
  formData.value.managerUserIds = formData.value.managerUserIds.filter(
 | 
			
		||||
    (id: number) => id !== user.id
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.bg-gray-100 {
 | 
			
		||||
  background-color: #f5f7fa;
 | 
			
		||||
  transition: all 0.3s;
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background-color: #e6e8eb;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .ep-close {
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    color: #909399;
 | 
			
		||||
    transition: color 0.3s;
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      color: #f56c6c;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -12,10 +12,12 @@
 | 
			
		|||
      :additionalModel="controlForm.additionalModel"
 | 
			
		||||
      :model="model"
 | 
			
		||||
      @save="save"
 | 
			
		||||
      :process-id="modelKey"
 | 
			
		||||
      :process-name="modelName"
 | 
			
		||||
    />
 | 
			
		||||
    <!-- 流程属性器,负责编辑每个流程节点的属性 -->
 | 
			
		||||
    <MyProcessPenal
 | 
			
		||||
      v-if="isModelerReady && modeler"
 | 
			
		||||
      v-if="modeler"
 | 
			
		||||
      key="penal"
 | 
			
		||||
      :bpmnModeler="modeler"
 | 
			
		||||
      :prefix="controlForm.prefix"
 | 
			
		||||
| 
						 | 
				
			
			@ -37,8 +39,8 @@ defineOptions({ name: 'BpmModelEditor' })
 | 
			
		|||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
  modelId?: string
 | 
			
		||||
  modelKey?: string
 | 
			
		||||
  modelName?: string
 | 
			
		||||
  modelKey: string
 | 
			
		||||
  modelName: string
 | 
			
		||||
  value?: string
 | 
			
		||||
}>()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -51,10 +53,13 @@ const formType = ref(20)
 | 
			
		|||
provide('formFields', formFields)
 | 
			
		||||
provide('formType', formType)
 | 
			
		||||
 | 
			
		||||
const xmlString = ref<string>('') // BPMN XML
 | 
			
		||||
// 注入流程数据
 | 
			
		||||
const xmlString = inject('processData') as Ref
 | 
			
		||||
// 注入模型数据
 | 
			
		||||
const modelData = inject('modelData') as Ref
 | 
			
		||||
 | 
			
		||||
const modeler = shallowRef() // BPMN Modeler
 | 
			
		||||
const processDesigner = ref()
 | 
			
		||||
const isModelerReady = ref(false)
 | 
			
		||||
const controlForm = ref({
 | 
			
		||||
  simulation: true,
 | 
			
		||||
  labelEditing: false,
 | 
			
		||||
| 
						 | 
				
			
			@ -65,154 +70,26 @@ 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 = async (item) => {
 | 
			
		||||
  try {
 | 
			
		||||
    modeler.value = item
 | 
			
		||||
    // 等待 modeler 初始化完成
 | 
			
		||||
    await nextTick()
 | 
			
		||||
 | 
			
		||||
    // 确保 modeler 的所有实例都已经准备好
 | 
			
		||||
    if (initBpmnInstances()) {
 | 
			
		||||
      isModelerReady.value = true
 | 
			
		||||
      emit('init-finished')
 | 
			
		||||
 | 
			
		||||
      // 初始化完成后,设置初始值
 | 
			
		||||
      if (props.modelId) {
 | 
			
		||||
        // 编辑模式
 | 
			
		||||
        const data = await ModelApi.getModel(props.modelId)
 | 
			
		||||
        model.value = {
 | 
			
		||||
          ...data,
 | 
			
		||||
          bpmnXml: undefined // 清空 bpmnXml 属性
 | 
			
		||||
        }
 | 
			
		||||
        xmlString.value = data.bpmnXml || getDefaultBpmnXml(data.key, data.name)
 | 
			
		||||
      } else if (props.modelKey && props.modelName) {
 | 
			
		||||
        // 新建模式
 | 
			
		||||
        xmlString.value = props.value || getDefaultBpmnXml(props.modelKey, props.modelName)
 | 
			
		||||
        model.value = {
 | 
			
		||||
          key: props.modelKey,
 | 
			
		||||
          name: props.modelName
 | 
			
		||||
        } as ModelApi.ModelVO
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // 导入XML并刷新视图
 | 
			
		||||
      await nextTick()
 | 
			
		||||
      try {
 | 
			
		||||
        await modeler.value.importXML(xmlString.value)
 | 
			
		||||
        if (processDesigner.value?.refresh) {
 | 
			
		||||
          processDesigner.value.refresh()
 | 
			
		||||
        }
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        console.error('导入XML失败:', error)
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      console.error('modeler 实例未完全初始化')
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('初始化 modeler 失败:', error)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 获取默认的BPMN XML */
 | 
			
		||||
const getDefaultBpmnXml = (key: string, name: string) => {
 | 
			
		||||
  return `<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.activiti.org/processdef">
 | 
			
		||||
  <process id="${key}" name="${name}" isExecutable="true" />
 | 
			
		||||
  <bpmndi:BPMNDiagram id="BPMNDiagram">
 | 
			
		||||
    <bpmndi:BPMNPlane id="${key}_di" bpmnElement="${key}" />
 | 
			
		||||
  </bpmndi:BPMNDiagram>
 | 
			
		||||
</definitions>`
 | 
			
		||||
const initModeler = async (item: any) => {
 | 
			
		||||
  //先初始化模型数据
 | 
			
		||||
  model.value = modelData.value
 | 
			
		||||
  modeler.value = item
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 添加/修改模型 */
 | 
			
		||||
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)
 | 
			
		||||
    }
 | 
			
		||||
    emit('success', bpmnXml)
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('保存失败:', error)
 | 
			
		||||
    message.error('保存失败')
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 监听 key、name 和 value 的变化
 | 
			
		||||
watch(
 | 
			
		||||
  [() => props.modelKey, () => props.modelName, () => props.value],
 | 
			
		||||
  async ([newKey, newName, newValue]) => {
 | 
			
		||||
    if (!props.modelId && isModelerReady.value) {
 | 
			
		||||
      let shouldRefresh = false
 | 
			
		||||
 | 
			
		||||
      if (newKey && newName) {
 | 
			
		||||
        const newXml = newValue || getDefaultBpmnXml(newKey, newName)
 | 
			
		||||
        if (newXml !== xmlString.value) {
 | 
			
		||||
          xmlString.value = newXml
 | 
			
		||||
          shouldRefresh = true
 | 
			
		||||
        }
 | 
			
		||||
        model.value = {
 | 
			
		||||
          ...model.value,
 | 
			
		||||
          key: newKey,
 | 
			
		||||
          name: newName
 | 
			
		||||
        } as ModelApi.ModelVO
 | 
			
		||||
      } else if (newValue && newValue !== xmlString.value) {
 | 
			
		||||
        xmlString.value = newValue
 | 
			
		||||
        shouldRefresh = true
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (shouldRefresh) {
 | 
			
		||||
        // 确保更新后重新渲染
 | 
			
		||||
        await nextTick()
 | 
			
		||||
        if (processDesigner.value?.refresh) {
 | 
			
		||||
          try {
 | 
			
		||||
            await modeler.value?.importXML(xmlString.value)
 | 
			
		||||
            processDesigner.value.refresh()
 | 
			
		||||
          } catch (error) {
 | 
			
		||||
            console.error('导入XML失败:', error)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  { deep: true }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 在组件卸载时清理
 | 
			
		||||
onBeforeUnmount(() => {
 | 
			
		||||
  isModelerReady.value = false
 | 
			
		||||
  modeler.value = null
 | 
			
		||||
  // 清理全局实例
 | 
			
		||||
  const w = window as any
 | 
			
		||||
| 
						 | 
				
			
			@ -220,55 +97,6 @@ onBeforeUnmount(() => {
 | 
			
		|||
    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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -62,7 +62,7 @@
 | 
			
		|||
      <el-radio-group v-model="modelData.visible">
 | 
			
		||||
        <el-radio
 | 
			
		||||
          v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
 | 
			
		||||
          :key="dict.value"
 | 
			
		||||
          :key="dict.value as string"
 | 
			
		||||
          :value="dict.value"
 | 
			
		||||
        >
 | 
			
		||||
          {{ dict.label }}
 | 
			
		||||
| 
						 | 
				
			
			@ -77,7 +77,6 @@
 | 
			
		|||
      >
 | 
			
		||||
        <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
 | 
			
		||||
| 
						 | 
				
			
			@ -97,21 +96,12 @@
 | 
			
		|||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-button type="primary" link @click="openStartUserSelect">
 | 
			
		||||
          <Icon icon="ep:plus" />选择人员
 | 
			
		||||
          <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">
 | 
			
		||||
    <el-form-item label="流程管理员" prop="managerUserIds" class="mb-20px">
 | 
			
		||||
      <div class="flex flex-wrap gap-2">
 | 
			
		||||
        <div
 | 
			
		||||
          v-for="user in selectedManagerUsers"
 | 
			
		||||
          :key="user.id"
 | 
			
		||||
| 
						 | 
				
			
			@ -142,14 +132,11 @@
 | 
			
		|||
<script lang="ts" setup>
 | 
			
		||||
import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
 | 
			
		||||
import { UserVO } from '@/api/system/user'
 | 
			
		||||
import { CategoryVO } from '@/api/bpm/category'
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  modelValue: {
 | 
			
		||||
    type: Object,
 | 
			
		||||
    required: true
 | 
			
		||||
  },
 | 
			
		||||
  categoryList: {
 | 
			
		||||
    type: Array,
 | 
			
		||||
    type: Array as PropType<CategoryVO[]>,
 | 
			
		||||
    required: true
 | 
			
		||||
  },
 | 
			
		||||
  userList: {
 | 
			
		||||
| 
						 | 
				
			
			@ -158,8 +145,6 @@ const props = defineProps({
 | 
			
		|||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:modelValue'])
 | 
			
		||||
 | 
			
		||||
const formRef = ref()
 | 
			
		||||
const selectedStartUsers = ref<UserVO[]>([])
 | 
			
		||||
const selectedManagerUsers = ref<UserVO[]>([])
 | 
			
		||||
| 
						 | 
				
			
			@ -177,27 +162,30 @@ const rules = {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// 创建本地数据副本
 | 
			
		||||
const modelData = computed({
 | 
			
		||||
  get: () => props.modelValue,
 | 
			
		||||
  set: (val) => emit('update:modelValue', val)
 | 
			
		||||
})
 | 
			
		||||
const modelData = defineModel<any>()
 | 
			
		||||
 | 
			
		||||
// 初始化选中的用户
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.modelValue,
 | 
			
		||||
  () => modelData.value,
 | 
			
		||||
  (newVal) => {
 | 
			
		||||
    if (newVal.startUserIds?.length) {
 | 
			
		||||
      selectedStartUsers.value = props.userList.filter((user: UserVO) =>
 | 
			
		||||
        newVal.startUserIds.includes(user.id)
 | 
			
		||||
      ) as UserVO[]
 | 
			
		||||
    } else {
 | 
			
		||||
      selectedStartUsers.value = []
 | 
			
		||||
    }
 | 
			
		||||
    if (newVal.managerUserIds?.length) {
 | 
			
		||||
      selectedManagerUsers.value = props.userList.filter((user: UserVO) =>
 | 
			
		||||
        newVal.managerUserIds.includes(user.id)
 | 
			
		||||
      ) as UserVO[]
 | 
			
		||||
    } else {
 | 
			
		||||
      selectedManagerUsers.value = []
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  { immediate: true }
 | 
			
		||||
  {
 | 
			
		||||
    immediate: true
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/** 打开发起人选择 */
 | 
			
		||||
| 
						 | 
				
			
			@ -215,58 +203,42 @@ const openManagerUserSelect = () => {
 | 
			
		|||
/** 处理用户选择确认 */
 | 
			
		||||
const handleUserSelectConfirm = (_, users: UserVO[]) => {
 | 
			
		||||
  if (currentSelectType.value === 'start') {
 | 
			
		||||
    selectedStartUsers.value = users
 | 
			
		||||
    emit('update:modelValue', {
 | 
			
		||||
    modelData.value = {
 | 
			
		||||
      ...modelData.value,
 | 
			
		||||
      startUserIds: users.map((u) => u.id)
 | 
			
		||||
    })
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    selectedManagerUsers.value = users
 | 
			
		||||
    emit('update:modelValue', {
 | 
			
		||||
    modelData.value = {
 | 
			
		||||
      ...modelData.value,
 | 
			
		||||
      managerUserIds: users.map((u) => u.id)
 | 
			
		||||
    })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 处理发起人类型变化 */
 | 
			
		||||
const handleStartUserTypeChange = (value: number) => {
 | 
			
		||||
  if (value !== 1) {
 | 
			
		||||
    selectedStartUsers.value = []
 | 
			
		||||
    emit('update:modelValue', {
 | 
			
		||||
    modelData.value = {
 | 
			
		||||
      ...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 = {
 | 
			
		||||
    ...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 = {
 | 
			
		||||
    ...modelData.value,
 | 
			
		||||
    managerUserIds: modelData.value.managerUserIds.filter((id: number) => id !== user.id)
 | 
			
		||||
  })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 表单校验 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -70,25 +70,16 @@ 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 modelData = defineModel<any>()
 | 
			
		||||
 | 
			
		||||
// 表单预览数据
 | 
			
		||||
const formPreview = ref({
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,10 +6,7 @@
 | 
			
		|||
      :model-id="modelData.id"
 | 
			
		||||
      :model-key="modelData.key"
 | 
			
		||||
      :model-name="modelData.name"
 | 
			
		||||
      :value="currentBpmnXml"
 | 
			
		||||
      ref="bpmnEditorRef"
 | 
			
		||||
      @success="handleDesignSuccess"
 | 
			
		||||
      @init-finished="handleEditorInit"
 | 
			
		||||
    />
 | 
			
		||||
  </template>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -21,10 +18,7 @@
 | 
			
		|||
      :model-key="modelData.key"
 | 
			
		||||
      :model-name="modelData.name"
 | 
			
		||||
      :start-user-ids="modelData.startUserIds"
 | 
			
		||||
      :value="currentSimpleModel"
 | 
			
		||||
      ref="simpleEditorRef"
 | 
			
		||||
      @success="handleDesignSuccess"
 | 
			
		||||
      @init-finished="handleEditorInit"
 | 
			
		||||
    />
 | 
			
		||||
  </template>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			@ -34,137 +28,16 @@ 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)
 | 
			
		||||
})
 | 
			
		||||
const modelData = defineModel<any>()
 | 
			
		||||
 | 
			
		||||
// 保存当前的流程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 processData = inject('processData') as Ref
 | 
			
		||||
 | 
			
		||||
/** 表单校验 */
 | 
			
		||||
const validate = async () => {
 | 
			
		||||
  try {
 | 
			
		||||
    // 获取最新的流程数据
 | 
			
		||||
    const processData = await getProcessData()
 | 
			
		||||
    if (!processData) {
 | 
			
		||||
    if (!processData.value) {
 | 
			
		||||
      throw new Error('请设计流程')
 | 
			
		||||
    }
 | 
			
		||||
    return true
 | 
			
		||||
| 
						 | 
				
			
			@ -172,27 +45,19 @@ const validate = async () => {
 | 
			
		|||
    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)
 | 
			
		||||
    //更新表单的模型数据部分
 | 
			
		||||
    modelData.value = newModelData
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -200,36 +65,7 @@ const handleDesignSuccess = async (data?: any) => {
 | 
			
		|||
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
 | 
			
		||||
  validate
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -67,12 +67,7 @@
 | 
			
		|||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- 第三步:流程设计 -->
 | 
			
		||||
        <ProcessDesign
 | 
			
		||||
          v-if="currentStep === 2"
 | 
			
		||||
          v-model="formData"
 | 
			
		||||
          ref="processDesignRef"
 | 
			
		||||
          @success="handleDesignSuccess"
 | 
			
		||||
        />
 | 
			
		||||
        <ProcessDesign v-if="currentStep === 2" v-model="formData" ref="processDesignRef" />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </ContentWrap>
 | 
			
		||||
| 
						 | 
				
			
			@ -83,7 +78,7 @@ 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 { CategoryApi, CategoryVO } from '@/api/bpm/category'
 | 
			
		||||
import * as UserApi from '@/api/system/user'
 | 
			
		||||
import { useUserStoreWithOut } from '@/store/modules/user'
 | 
			
		||||
import { BpmModelFormType, BpmModelType } from '@/utils/constants'
 | 
			
		||||
| 
						 | 
				
			
			@ -118,7 +113,8 @@ const validateProcess = async () => {
 | 
			
		|||
  await processDesignRef.value?.validate()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const currentStep = ref(0) // 步骤控制
 | 
			
		||||
const currentStep = ref(-1) // 步骤控制。-1 用于,一开始全部不展示等当前页面数据初始化完成
 | 
			
		||||
 | 
			
		||||
const steps = [
 | 
			
		||||
  { title: '基本信息', validator: validateBasic },
 | 
			
		||||
  { title: '表单设计', validator: validateForm },
 | 
			
		||||
| 
						 | 
				
			
			@ -140,14 +136,19 @@ const formData: any = ref({
 | 
			
		|||
  formCustomViewPath: '',
 | 
			
		||||
  visible: true,
 | 
			
		||||
  startUserType: undefined,
 | 
			
		||||
  managerUserType: undefined,
 | 
			
		||||
  startUserIds: [],
 | 
			
		||||
  managerUserIds: []
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
//流程数据
 | 
			
		||||
const processData = ref<any>()
 | 
			
		||||
 | 
			
		||||
provide('processData', processData)
 | 
			
		||||
provide('modelData', formData)
 | 
			
		||||
 | 
			
		||||
// 数据列表
 | 
			
		||||
const formList = ref([])
 | 
			
		||||
const categoryList = ref([])
 | 
			
		||||
const categoryList = ref<CategoryVO[]>([])
 | 
			
		||||
const userList = ref<UserApi.UserVO[]>([])
 | 
			
		||||
 | 
			
		||||
/** 初始化数据 */
 | 
			
		||||
| 
						 | 
				
			
			@ -156,8 +157,16 @@ const initData = async () => {
 | 
			
		|||
  if (modelId) {
 | 
			
		||||
    // 修改场景
 | 
			
		||||
    formData.value = await ModelApi.getModel(modelId)
 | 
			
		||||
    formData.value.startUserType = formData.value.startUserIds?.length > 0 ? 1 : 0
 | 
			
		||||
    // 复制场景
 | 
			
		||||
    if (route.params.type === 'copy') {
 | 
			
		||||
      delete formData.value.id
 | 
			
		||||
      formData.value.name += '副本'
 | 
			
		||||
      formData.value.key += '_copy'
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    // 新增场景
 | 
			
		||||
    formData.value.startUserType = 0 // 全体
 | 
			
		||||
    formData.value.managerUserIds.push(userStore.getUser.id)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -167,59 +176,54 @@ const initData = async () => {
 | 
			
		|||
  categoryList.value = await CategoryApi.getCategorySimpleList()
 | 
			
		||||
  // 获取用户列表
 | 
			
		||||
  userList.value = await UserApi.getSimpleUserList()
 | 
			
		||||
 | 
			
		||||
  // 最终,设置 currentStep 切换到第一步
 | 
			
		||||
  currentStep.value = 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 根据类型切换流程数据 */
 | 
			
		||||
watch(
 | 
			
		||||
  async () => formData.value.type,
 | 
			
		||||
  () => {
 | 
			
		||||
    if (formData.value.type === BpmModelType.BPMN) {
 | 
			
		||||
      processData.value = formData.value.bpmnXml
 | 
			
		||||
    } else if (formData.value.type === BpmModelType.SIMPLE) {
 | 
			
		||||
      processData.value = formData.value.simpleModel
 | 
			
		||||
    }
 | 
			
		||||
    console.log('加载流程数据', processData.value)
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    immediate: true
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/** 校验所有步骤数据是否完整 */
 | 
			
		||||
const validateAllSteps = async () => {
 | 
			
		||||
  try {
 | 
			
		||||
    // 基本信息校验
 | 
			
		||||
    await basicInfoRef.value?.validate()
 | 
			
		||||
    if (!formData.value.key || !formData.value.name || !formData.value.category) {
 | 
			
		||||
    try {
 | 
			
		||||
      await validateBasic()
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      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)
 | 
			
		||||
    ) {
 | 
			
		||||
    try {
 | 
			
		||||
      await validateForm()
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      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
 | 
			
		||||
      }
 | 
			
		||||
    // 表单设计校验
 | 
			
		||||
    try {
 | 
			
		||||
      await validateProcess()
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      currentStep.value = 2
 | 
			
		||||
      throw new Error('请设计流程')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true
 | 
			
		||||
| 
						 | 
				
			
			@ -239,20 +243,6 @@ const handleSave = async () => {
 | 
			
		|||
      ...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)
 | 
			
		||||
| 
						 | 
				
			
			@ -308,20 +298,6 @@ const handleDeploy = async () => {
 | 
			
		|||
      ...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)
 | 
			
		||||
| 
						 | 
				
			
			@ -344,60 +320,25 @@ const handleDeploy = async () => {
 | 
			
		|||
/** 步骤切换处理 */
 | 
			
		||||
const handleStepClick = async (index: number) => {
 | 
			
		||||
  try {
 | 
			
		||||
    // 如果是切换到第三步(流程设计),需要校验key和name
 | 
			
		||||
    if (index === 2) {
 | 
			
		||||
      if (!formData.value.key || !formData.value.name) {
 | 
			
		||||
        message.warning('请先填写流程标识和流程名称')
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
    console.log('index', index)
 | 
			
		||||
    if (index !== 0) {
 | 
			
		||||
      await validateBasic()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 保存当前步骤的数据
 | 
			
		||||
    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()
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    if (index !== 1) {
 | 
			
		||||
      await validateForm()
 | 
			
		||||
    }
 | 
			
		||||
    if (index !== 2) {
 | 
			
		||||
      await validateProcess()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 切换步骤
 | 
			
		||||
    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 = () => {
 | 
			
		||||
  // 先删除当前页签
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -85,8 +85,6 @@
 | 
			
		|||
    </div>
 | 
			
		||||
  </ContentWrap>
 | 
			
		||||
 | 
			
		||||
  <!-- 表单弹窗:添加/修改流程 -->
 | 
			
		||||
  <ModelForm ref="formRef" @success="getList" />
 | 
			
		||||
  <!-- 表单弹窗:添加分类 -->
 | 
			
		||||
  <CategoryForm ref="categoryFormRef" @success="getList" />
 | 
			
		||||
  <!-- 弹窗:表单详情 -->
 | 
			
		||||
| 
						 | 
				
			
			@ -99,7 +97,6 @@
 | 
			
		|||
import draggable from 'vuedraggable'
 | 
			
		||||
import { CategoryApi } from '@/api/bpm/category'
 | 
			
		||||
import * as ModelApi from '@/api/bpm/model'
 | 
			
		||||
import ModelForm from './ModelForm.vue'
 | 
			
		||||
import CategoryForm from '../category/CategoryForm.vue'
 | 
			
		||||
import { cloneDeep } from 'lodash-es'
 | 
			
		||||
import CategoryDraggableModel from './CategoryDraggableModel.vue'
 | 
			
		||||
| 
						 | 
				
			
			@ -123,7 +120,6 @@ const handleQuery = () => {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/** 添加/修改操作 */
 | 
			
		||||
const formRef = ref()
 | 
			
		||||
const openForm = (type: string, id?: number) => {
 | 
			
		||||
  if (type === 'create') {
 | 
			
		||||
    push({ name: 'BpmModelCreate' })
 | 
			
		||||
| 
						 | 
				
			
			@ -206,7 +202,7 @@ const getList = async () => {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/** 初始化 **/
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
onActivated(() => {
 | 
			
		||||
  getList()
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			@ -44,8 +44,26 @@
 | 
			
		|||
              :rows="4"
 | 
			
		||||
            />
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
          <el-form-item
 | 
			
		||||
            v-if="runningTask.signEnable"
 | 
			
		||||
            label="签名"
 | 
			
		||||
            prop="signPicUrl"
 | 
			
		||||
            ref="approveSignFormRef"
 | 
			
		||||
          >
 | 
			
		||||
            <el-button @click="signRef.open()">点击签名</el-button>
 | 
			
		||||
            <el-image
 | 
			
		||||
              class="w-90px h-40px ml-5px"
 | 
			
		||||
              v-if="approveReasonForm.signPicUrl"
 | 
			
		||||
              :src="approveReasonForm.signPicUrl"
 | 
			
		||||
              :preview-src-list="[approveReasonForm.signPicUrl]"
 | 
			
		||||
            />
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
          <el-form-item>
 | 
			
		||||
            <el-button :disabled="formLoading" type="success" @click="handleAudit(true, approveFormRef)">
 | 
			
		||||
            <el-button
 | 
			
		||||
              :disabled="formLoading"
 | 
			
		||||
              type="success"
 | 
			
		||||
              @click="handleAudit(true, approveFormRef)"
 | 
			
		||||
            >
 | 
			
		||||
              {{ getButtonDisplayName(OperationButtonType.APPROVE) }}
 | 
			
		||||
            </el-button>
 | 
			
		||||
            <el-button @click="closePropover('approve', approveFormRef)"> 取消 </el-button>
 | 
			
		||||
| 
						 | 
				
			
			@ -86,7 +104,11 @@
 | 
			
		|||
            />
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
          <el-form-item>
 | 
			
		||||
            <el-button :disabled="formLoading" type="danger" @click="handleAudit(false,rejectFormRef)">
 | 
			
		||||
            <el-button
 | 
			
		||||
              :disabled="formLoading"
 | 
			
		||||
              type="danger"
 | 
			
		||||
              @click="handleAudit(false, rejectFormRef)"
 | 
			
		||||
            >
 | 
			
		||||
              {{ getButtonDisplayName(OperationButtonType.REJECT) }}
 | 
			
		||||
            </el-button>
 | 
			
		||||
            <el-button @click="closePropover('reject', rejectFormRef)"> 取消 </el-button>
 | 
			
		||||
| 
						 | 
				
			
			@ -471,6 +493,9 @@
 | 
			
		|||
      <Icon :size="14" icon="ep:refresh" />  再次提交
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <!-- 签名弹窗 -->
 | 
			
		||||
  <SignDialog ref="signRef" @success="handleSignFinish" />
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { useUserStoreWithOut } from '@/store/modules/user'
 | 
			
		||||
| 
						 | 
				
			
			@ -479,11 +504,13 @@ import * as TaskApi from '@/api/bpm/task'
 | 
			
		|||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
 | 
			
		||||
import * as UserApi from '@/api/system/user'
 | 
			
		||||
import {
 | 
			
		||||
  OperationButtonType,
 | 
			
		||||
  OPERATION_BUTTON_NAME
 | 
			
		||||
  OPERATION_BUTTON_NAME,
 | 
			
		||||
  OperationButtonType
 | 
			
		||||
} from '@/components/SimpleProcessDesignerV2/src/consts'
 | 
			
		||||
import { BpmProcessInstanceStatus, BpmModelFormType } from '@/utils/constants'
 | 
			
		||||
import { BpmModelFormType, BpmProcessInstanceStatus } from '@/utils/constants'
 | 
			
		||||
import type { FormInstance, FormRules } from 'element-plus'
 | 
			
		||||
import SignDialog from './SignDialog.vue'
 | 
			
		||||
 | 
			
		||||
defineOptions({ name: 'ProcessInstanceBtnContainer' })
 | 
			
		||||
 | 
			
		||||
const router = useRouter() // 路由
 | 
			
		||||
| 
						 | 
				
			
			@ -492,12 +519,12 @@ const message = useMessage() // 消息弹窗
 | 
			
		|||
const userId = useUserStoreWithOut().getUser.id // 当前登录的编号
 | 
			
		||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
 | 
			
		||||
 | 
			
		||||
const props = defineProps< {
 | 
			
		||||
  processInstance: any,  // 流程实例信息
 | 
			
		||||
  processDefinition: any,  // 流程定义信息
 | 
			
		||||
  userOptions: UserApi.UserVO[],
 | 
			
		||||
  normalForm: any, // 流程表单 formCreate
 | 
			
		||||
  normalFormApi: any, // 流程表单 formCreate Api
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
  processInstance: any // 流程实例信息
 | 
			
		||||
  processDefinition: any // 流程定义信息
 | 
			
		||||
  userOptions: UserApi.UserVO[]
 | 
			
		||||
  normalForm: any // 流程表单 formCreate
 | 
			
		||||
  normalFormApi: any // 流程表单 formCreate Api
 | 
			
		||||
  writableFields: string[] // 流程表单可以编辑的字段
 | 
			
		||||
}>()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -522,11 +549,15 @@ const approveFormFApi = ref<any>({}) // approveForms 的 fAPi
 | 
			
		|||
 | 
			
		||||
// 审批通过意见表单
 | 
			
		||||
const approveFormRef = ref<FormInstance>()
 | 
			
		||||
const signRef = ref()
 | 
			
		||||
const approveSignFormRef = ref()
 | 
			
		||||
const approveReasonForm = reactive({
 | 
			
		||||
  reason: ''
 | 
			
		||||
  reason: '',
 | 
			
		||||
  signPicUrl: ''
 | 
			
		||||
})
 | 
			
		||||
const approveReasonRule = reactive<FormRules<typeof approveReasonForm>>({
 | 
			
		||||
  reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
 | 
			
		||||
  signPicUrl: [{ required: true, message: '签名不能为空', trigger: 'change' }]
 | 
			
		||||
})
 | 
			
		||||
// 拒绝表单
 | 
			
		||||
const rejectFormRef = ref<FormInstance>()
 | 
			
		||||
| 
						 | 
				
			
			@ -534,7 +565,7 @@ const rejectReasonForm = reactive({
 | 
			
		|||
  reason: ''
 | 
			
		||||
})
 | 
			
		||||
const rejectReasonRule = reactive<FormRules<typeof rejectReasonForm>>({
 | 
			
		||||
  reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
 | 
			
		||||
  reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }]
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// 抄送表单
 | 
			
		||||
| 
						 | 
				
			
			@ -555,7 +586,7 @@ const transferForm = reactive({
 | 
			
		|||
})
 | 
			
		||||
const transferFormRule = reactive<FormRules<typeof transferForm>>({
 | 
			
		||||
  assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }],
 | 
			
		||||
  reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
 | 
			
		||||
  reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }]
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// 委派表单
 | 
			
		||||
| 
						 | 
				
			
			@ -566,7 +597,7 @@ const delegateForm = reactive({
 | 
			
		|||
})
 | 
			
		||||
const delegateFormRule = reactive<FormRules<typeof delegateForm>>({
 | 
			
		||||
  delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }],
 | 
			
		||||
  reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
 | 
			
		||||
  reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }]
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// 加签表单
 | 
			
		||||
| 
						 | 
				
			
			@ -577,7 +608,7 @@ const addSignForm = reactive({
 | 
			
		|||
})
 | 
			
		||||
const addSignFormRule = reactive<FormRules<typeof addSignForm>>({
 | 
			
		||||
  addSignUserIds: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }],
 | 
			
		||||
  reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
 | 
			
		||||
  reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }]
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// 减签表单
 | 
			
		||||
| 
						 | 
				
			
			@ -588,7 +619,7 @@ const deleteSignForm = reactive({
 | 
			
		|||
})
 | 
			
		||||
const deleteSignFormRule = reactive<FormRules<typeof deleteSignForm>>({
 | 
			
		||||
  deleteSignTaskId: [{ required: true, message: '减签人员不能为空', trigger: 'change' }],
 | 
			
		||||
 reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
 | 
			
		||||
  reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }]
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// 退回表单
 | 
			
		||||
| 
						 | 
				
			
			@ -608,7 +639,7 @@ const cancelForm = reactive({
 | 
			
		|||
  cancelReason: ''
 | 
			
		||||
})
 | 
			
		||||
const cancelFormRule = reactive<FormRules<typeof cancelForm>>({
 | 
			
		||||
  cancelReason: [{ required: true, message: '取消理由不能为空', trigger: 'blur' }],
 | 
			
		||||
  cancelReason: [{ required: true, message: '取消理由不能为空', trigger: 'blur' }]
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
/** 监听 approveFormFApis,实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */
 | 
			
		||||
| 
						 | 
				
			
			@ -627,11 +658,11 @@ watch(
 | 
			
		|||
const openPopover = async (type: string) => {
 | 
			
		||||
  if (type === 'approve') {
 | 
			
		||||
    // 校验流程表单
 | 
			
		||||
     const valid = await validateNormalForm();
 | 
			
		||||
     if (!valid) {
 | 
			
		||||
    const valid = await validateNormalForm()
 | 
			
		||||
    if (!valid) {
 | 
			
		||||
      message.warning('表单校验不通过,请先完善表单!!')
 | 
			
		||||
      return;
 | 
			
		||||
     }
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (type === 'return') {
 | 
			
		||||
    // 获取退回节点
 | 
			
		||||
| 
						 | 
				
			
			@ -652,7 +683,7 @@ const openPopover = async (type: string) => {
 | 
			
		|||
const closePropover = (type: string, formRef: FormInstance | undefined) => {
 | 
			
		||||
  if (formRef) {
 | 
			
		||||
    formRef.resetFields()
 | 
			
		||||
  } 
 | 
			
		||||
  }
 | 
			
		||||
  popOverVisible.value[type] = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -664,14 +695,18 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
 | 
			
		|||
    if (!formRef) return
 | 
			
		||||
    await formRef.validate()
 | 
			
		||||
    if (pass) {
 | 
			
		||||
       // 获取修改的流程变量, 暂时只支持流程表单
 | 
			
		||||
       const variables = getUpdatedProcessInstanceVaiables();
 | 
			
		||||
      // 获取修改的流程变量, 暂时只支持流程表单
 | 
			
		||||
      const variables = getUpdatedProcessInstanceVariables()
 | 
			
		||||
      // 审批通过数据
 | 
			
		||||
      const data = {
 | 
			
		||||
        id: runningTask.value.id,
 | 
			
		||||
        reason: approveReasonForm.reason,
 | 
			
		||||
        variables // 审批通过, 把修改的字段值赋于流程实例变量
 | 
			
		||||
      }
 | 
			
		||||
      // 签名
 | 
			
		||||
      if (runningTask.value.signEnable) {
 | 
			
		||||
        data.signPicUrl = approveReasonForm.signPicUrl
 | 
			
		||||
      }
 | 
			
		||||
      // 多表单处理,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
 | 
			
		||||
      // TODO 芋艿 任务有多表单这里要如何处理,会和可编辑的字段冲突
 | 
			
		||||
      const formCreateApi = approveFormFApi.value
 | 
			
		||||
| 
						 | 
				
			
			@ -684,10 +719,10 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
 | 
			
		|||
      popOverVisible.value.approve = false
 | 
			
		||||
      message.success('审批通过成功')
 | 
			
		||||
    } else {
 | 
			
		||||
       // 审批不通过数据
 | 
			
		||||
       const data = {
 | 
			
		||||
      // 审批不通过数据
 | 
			
		||||
      const data = {
 | 
			
		||||
        id: runningTask.value.id,
 | 
			
		||||
        reason: rejectReasonForm.reason,
 | 
			
		||||
        reason: rejectReasonForm.reason
 | 
			
		||||
      }
 | 
			
		||||
      await TaskApi.rejectTask(data)
 | 
			
		||||
      popOverVisible.value.reject = false
 | 
			
		||||
| 
						 | 
				
			
			@ -713,7 +748,7 @@ const handleCopy = async () => {
 | 
			
		|||
    const data = {
 | 
			
		||||
      id: runningTask.value.id,
 | 
			
		||||
      reason: copyForm.copyReason,
 | 
			
		||||
      copyUserIds:copyForm.copyUserIds
 | 
			
		||||
      copyUserIds: copyForm.copyUserIds
 | 
			
		||||
    }
 | 
			
		||||
    await TaskApi.copyTask(data)
 | 
			
		||||
    copyFormRef.value.resetFields()
 | 
			
		||||
| 
						 | 
				
			
			@ -752,7 +787,6 @@ const handleTransfer = async () => {
 | 
			
		|||
const handleDelegate = async () => {
 | 
			
		||||
  formLoading.value = true
 | 
			
		||||
  try {
 | 
			
		||||
 
 | 
			
		||||
    // 1.1 校验表单
 | 
			
		||||
    if (!delegateFormRef.value) return
 | 
			
		||||
    await delegateFormRef.value.validate()
 | 
			
		||||
| 
						 | 
				
			
			@ -949,23 +983,29 @@ const validateNormalForm = async () => {
 | 
			
		|||
    try {
 | 
			
		||||
      await props.normalFormApi?.validate()
 | 
			
		||||
    } catch {
 | 
			
		||||
      valid = false;
 | 
			
		||||
      valid = false
 | 
			
		||||
    }
 | 
			
		||||
    return valid;
 | 
			
		||||
    return valid
 | 
			
		||||
  } else {
 | 
			
		||||
    return true;
 | 
			
		||||
    return true
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 从可以编辑的流程表单字段,获取需要修改的流程实例的变量 */
 | 
			
		||||
const getUpdatedProcessInstanceVaiables = ()=> {
 | 
			
		||||
const getUpdatedProcessInstanceVariables = () => {
 | 
			
		||||
  const variables = {}
 | 
			
		||||
  props.writableFields.forEach( (field) => {
 | 
			
		||||
    const fieldValue = props.normalFormApi.getValue(field)
 | 
			
		||||
    variables[field] = fieldValue;
 | 
			
		||||
  props.writableFields.forEach((field) => {
 | 
			
		||||
    variables[field] = props.normalFormApi.getValue(field)
 | 
			
		||||
  })
 | 
			
		||||
  return variables
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 处理签名完成 */
 | 
			
		||||
const handleSignFinish = (url: string) => {
 | 
			
		||||
  approveReasonForm.signPicUrl = url
 | 
			
		||||
  approveSignFormRef.value.validate('change')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ loadTodoTask })
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,6 @@
 | 
			
		|||
      :flow-node="simpleModel"
 | 
			
		||||
      :tasks="tasks"
 | 
			
		||||
      :process-instance="processInstance"
 | 
			
		||||
      class="process-viewer"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			@ -20,7 +19,7 @@ const props = defineProps({
 | 
			
		|||
  modelView: propTypes.object,
 | 
			
		||||
  simpleJson: propTypes.string // Simple 模型结构数据 (json 格式)
 | 
			
		||||
})
 | 
			
		||||
const simpleModel = ref()
 | 
			
		||||
const simpleModel = ref<any>({})
 | 
			
		||||
// 用户任务
 | 
			
		||||
const tasks = ref([])
 | 
			
		||||
// 流程实例
 | 
			
		||||
| 
						 | 
				
			
			@ -82,7 +81,6 @@ const setSimpleModelNodeTaskStatus = (
 | 
			
		|||
    }
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 审批节点
 | 
			
		||||
  if (
 | 
			
		||||
    simpleModel.type === NodeType.START_USER_NODE ||
 | 
			
		||||
| 
						 | 
				
			
			@ -98,31 +96,39 @@ const setSimpleModelNodeTaskStatus = (
 | 
			
		|||
    }
 | 
			
		||||
    // TODO 是不是还缺一个 cancel 的状态
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 抄送节点
 | 
			
		||||
  if (simpleModel.type === NodeType.COPY_TASK_NODE) {
 | 
			
		||||
    // 抄送节点 只有通过和未执行状态
 | 
			
		||||
    // 抄送节点,只有通过和未执行状态
 | 
			
		||||
    if (finishedActivityIds.includes(simpleModel.id)) {
 | 
			
		||||
      simpleModel.activityStatus = TaskStatusEnum.APPROVE
 | 
			
		||||
    } else {
 | 
			
		||||
      simpleModel.activityStatus = TaskStatusEnum.NOT_START
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // 条件节点 对应 SequenceFlow
 | 
			
		||||
  // 延迟器节点
 | 
			
		||||
  if (simpleModel.type === NodeType.DELAY_TIMER_NODE) {
 | 
			
		||||
    // 延迟器节点,只有通过和未执行状态
 | 
			
		||||
    if (finishedActivityIds.includes(simpleModel.id)) {
 | 
			
		||||
      simpleModel.activityStatus = TaskStatusEnum.APPROVE
 | 
			
		||||
    } else {
 | 
			
		||||
      simpleModel.activityStatus = TaskStatusEnum.NOT_START
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // 条件节点对应 SequenceFlow
 | 
			
		||||
  if (simpleModel.type === NodeType.CONDITION_NODE) {
 | 
			
		||||
    // 条件节点。只有通过和未执行状态
 | 
			
		||||
    // 条件节点,只有通过和未执行状态
 | 
			
		||||
    if (finishedSequenceFlowActivityIds.includes(simpleModel.id)) {
 | 
			
		||||
      simpleModel.activityStatus = TaskStatusEnum.APPROVE
 | 
			
		||||
    } else {
 | 
			
		||||
      simpleModel.activityStatus = TaskStatusEnum.NOT_START
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 网关节点
 | 
			
		||||
  if (
 | 
			
		||||
    simpleModel.type === NodeType.CONDITION_BRANCH_NODE ||
 | 
			
		||||
    simpleModel.type === NodeType.PARALLEL_BRANCH_NODE ||
 | 
			
		||||
    simpleModel.type === NodeType.INCLUSIVE_BRANCH_NODE
 | 
			
		||||
    simpleModel.type === NodeType.INCLUSIVE_BRANCH_NODE ||
 | 
			
		||||
    simpleModel.type === NodeType.ROUTER_BRANCH_NODE
 | 
			
		||||
  ) {
 | 
			
		||||
    // 网关节点。只有通过和未执行状态
 | 
			
		||||
    if (finishedActivityIds.includes(simpleModel.id)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -154,15 +160,4 @@ const setSimpleModelNodeTaskStatus = (
 | 
			
		|||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.process-viewer-container {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  
 | 
			
		||||
  :deep(.process-viewer) {
 | 
			
		||||
    height: 100% !important;
 | 
			
		||||
    min-height: 100%;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 &&
 | 
			
		||||
| 
						 | 
				
			
			@ -123,6 +123,17 @@
 | 
			
		|||
              >
 | 
			
		||||
                审批意见:{{ task.reason }}
 | 
			
		||||
              </div>
 | 
			
		||||
              <div
 | 
			
		||||
                v-if="task.signPicUrl && activity.nodeType === NodeType.USER_TASK_NODE"
 | 
			
		||||
                class="text-#a5a5a5 text-13px mt-1 w-full bg-#f8f8fa p2 rounded-md"
 | 
			
		||||
              >
 | 
			
		||||
                签名:
 | 
			
		||||
                <el-image
 | 
			
		||||
                  class="w-90px h-40px ml-5px"
 | 
			
		||||
                  :src="task.signPicUrl"
 | 
			
		||||
                  :preview-src-list="[task.signPicUrl]"
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
            </teleport>
 | 
			
		||||
          </div>
 | 
			
		||||
          <!-- 情况二:遍历每个审批节点下的【候选的】task 任务。例如说,1)依次审批,2)未来的审批任务等 -->
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,50 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <el-dialog v-model="signDialogVisible" title="签名" width="935">
 | 
			
		||||
    <div class="position-relative">
 | 
			
		||||
      <Vue3Signature class="b b-solid b-gray" ref="signature" w="900px" h="400px" />
 | 
			
		||||
      <el-button
 | 
			
		||||
        class="pos-absolute bottom-20px right-10px"
 | 
			
		||||
        type="primary"
 | 
			
		||||
        text
 | 
			
		||||
        size="small"
 | 
			
		||||
        @click="signature.clear()"
 | 
			
		||||
      >
 | 
			
		||||
        <Icon icon="ep:delete" class="mr-5px" />
 | 
			
		||||
        清除
 | 
			
		||||
      </el-button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <template #footer>
 | 
			
		||||
      <div class="dialog-footer">
 | 
			
		||||
        <el-button @click="signDialogVisible = false">取消</el-button>
 | 
			
		||||
        <el-button type="primary" @click="submit"> 提交 </el-button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </template>
 | 
			
		||||
  </el-dialog>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import Vue3Signature from 'vue3-signature'
 | 
			
		||||
import * as FileApi from '@/api/infra/file'
 | 
			
		||||
import download from '@/utils/download'
 | 
			
		||||
 | 
			
		||||
const message = useMessage() // 消息弹窗
 | 
			
		||||
const signDialogVisible = ref(false)
 | 
			
		||||
const signature = ref()
 | 
			
		||||
 | 
			
		||||
const open = async () => {
 | 
			
		||||
  signDialogVisible.value = true
 | 
			
		||||
}
 | 
			
		||||
defineExpose({ open })
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(['success'])
 | 
			
		||||
const submit = async () => {
 | 
			
		||||
  message.success('签名上传中请稍等。。。')
 | 
			
		||||
  const res = await FileApi.updateFile({
 | 
			
		||||
    file: download.base64ToFile(signature.value.save('image/png'), '签名')
 | 
			
		||||
  })
 | 
			
		||||
  emits('success', res.data)
 | 
			
		||||
  signDialogVisible.value = false
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
| 
						 | 
				
			
			@ -4,9 +4,7 @@
 | 
			
		|||
      :model-id="modelId"
 | 
			
		||||
      :model-key="modelKey"
 | 
			
		||||
      :model-name="modelName"
 | 
			
		||||
      :value="currentValue"
 | 
			
		||||
      @success="handleSuccess"
 | 
			
		||||
      @init-finished="handleInit"
 | 
			
		||||
      :start-user-ids="startUserIds"
 | 
			
		||||
      ref="designerRef"
 | 
			
		||||
    />
 | 
			
		||||
| 
						 | 
				
			
			@ -19,137 +17,22 @@ defineOptions({
 | 
			
		|||
  name: 'SimpleModelDesign'
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
defineProps<{
 | 
			
		||||
  modelId?: string
 | 
			
		||||
  modelKey?: string
 | 
			
		||||
  modelName?: string
 | 
			
		||||
  value?: string
 | 
			
		||||
  startUserIds?: number[]
 | 
			
		||||
}>()
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['success', 'init-finished'])
 | 
			
		||||
const emit = defineEmits(['success'])
 | 
			
		||||
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
 | 
			
		||||
  console.info('handleSuccess', data)
 | 
			
		||||
  if (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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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() // 消息弹窗
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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'
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -135,7 +135,8 @@ const makeTemplate = () => {
 | 
			
		|||
 | 
			
		||||
/** 复制 **/
 | 
			
		||||
const copy = async (text: string) => {
 | 
			
		||||
  const { copy, copied, isSupported } = useClipboard({ source: text })
 | 
			
		||||
  const textToCopy = JSON.stringify(text, null, 2)
 | 
			
		||||
  const { copy, copied, isSupported } = useClipboard({ source: textToCopy })
 | 
			
		||||
  if (!isSupported) {
 | 
			
		||||
    message.error(t('common.copyError'))
 | 
			
		||||
  } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -149,17 +150,18 @@ const copy = async (text: string) => {
 | 
			
		|||
/**
 | 
			
		||||
 * 代码高亮
 | 
			
		||||
 */
 | 
			
		||||
const highlightedCode = (code) => {
 | 
			
		||||
const highlightedCode = (code: string) => {
 | 
			
		||||
  // 处理语言和代码
 | 
			
		||||
  let language = 'json'
 | 
			
		||||
  if (formType.value === 2) {
 | 
			
		||||
    language = 'xml'
 | 
			
		||||
  }
 | 
			
		||||
  // debugger
 | 
			
		||||
  if (!isString(code)) {
 | 
			
		||||
    code = JSON.stringify(code)
 | 
			
		||||
    code = JSON.stringify(code, null, 2)
 | 
			
		||||
  }
 | 
			
		||||
  // 高亮
 | 
			
		||||
  const result = hljs.highlight(language, code, true)
 | 
			
		||||
  const result = hljs.highlight(code, { language: language, ignoreIllegals: true })
 | 
			
		||||
  return result.value || ' '
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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' })
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
      // 提交模板属性
 | 
			
		||||
      await DiyTemplateApi.updateDiyTemplateProperty(unref(formData)!)
 | 
			
		||||
    } else {
 | 
			
		||||
    // 对所有的 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(isEmpty(data) ? unref(formData)! : data)
 | 
			
		||||
        continue
 | 
			
		||||
      }
 | 
			
		||||
      // 提交页面属性
 | 
			
		||||
      await DiyPageApi.updateDiyPageProperty(unref(currentFormData)!)
 | 
			
		||||
      // 情况二:提交当前正在编辑的页面
 | 
			
		||||
      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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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' })
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 | 
			
		||||
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>
 | 
			
		||||
| 
						 | 
				
			
			@ -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() // 消息弹窗
 | 
			
		||||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 清除上级推广人 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 () => {
 | 
			
		||||
  list.value = await AreaApi.getAreaTree()
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  try {
 | 
			
		||||
    list.value = await AreaApi.getAreaTree()
 | 
			
		||||
  } finally {
 | 
			
		||||
    loading.value = false
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 添加/修改操作 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
      v-loading="loading"
 | 
			
		||||
      :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)"
 | 
			
		||||
    <div style="height: 700px">
 | 
			
		||||
      <!-- AutoResizer 自动调节大小 -->
 | 
			
		||||
      <el-auto-resizer>
 | 
			
		||||
        <template #default="{ height, width }">
 | 
			
		||||
          <!-- Virtualized Table 虚拟化表格:高性能,解决表格在大数据量下的卡顿问题 -->
 | 
			
		||||
          <el-table-v2
 | 
			
		||||
            v-loading="loading"
 | 
			
		||||
            :columns="columns"
 | 
			
		||||
            :data="list"
 | 
			
		||||
            :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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue