Compare commits
10
Commits
cce252851d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b909dd693e | ||
|
|
3ded1868a3 | ||
|
|
c36003db2f | ||
|
|
a6e4e74ad1 | ||
|
|
fc969390ae | ||
|
|
6f20fec8de | ||
|
|
e03e019f58 | ||
|
|
2de0a12d44 | ||
|
|
fa7be38e7f | ||
|
|
c5b744737d |
@@ -10,6 +10,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons-vue": "^7.0.1",
|
"@ant-design/icons-vue": "^7.0.1",
|
||||||
|
"@wangeditor/editor": "^5.1.23",
|
||||||
|
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||||
|
"@xmldom/xmldom": "^0.8.10",
|
||||||
"ant-design-vue": "^4.2.6",
|
"ant-design-vue": "^4.2.6",
|
||||||
"axios": "^1.16.0",
|
"axios": "^1.16.0",
|
||||||
"dayjs": "^1.11.20",
|
"dayjs": "^1.11.20",
|
||||||
|
|||||||
Generated
+488
@@ -11,6 +11,15 @@ importers:
|
|||||||
'@ant-design/icons-vue':
|
'@ant-design/icons-vue':
|
||||||
specifier: ^7.0.1
|
specifier: ^7.0.1
|
||||||
version: 7.0.1(vue@3.5.34)
|
version: 7.0.1(vue@3.5.34)
|
||||||
|
'@wangeditor/editor':
|
||||||
|
specifier: ^5.1.23
|
||||||
|
version: 5.1.23
|
||||||
|
'@wangeditor/editor-for-vue':
|
||||||
|
specifier: ^5.1.12
|
||||||
|
version: 5.1.12(@wangeditor/editor@5.1.23)(vue@3.5.34)
|
||||||
|
'@xmldom/xmldom':
|
||||||
|
specifier: ^0.8.10
|
||||||
|
version: 0.8.13
|
||||||
ant-design-vue:
|
ant-design-vue:
|
||||||
specifier: ^4.2.6
|
specifier: ^4.2.6
|
||||||
version: 4.2.6(vue@3.5.34)
|
version: 4.2.6(vue@3.5.34)
|
||||||
@@ -402,12 +411,35 @@ packages:
|
|||||||
'@simonwep/pickr@1.8.2':
|
'@simonwep/pickr@1.8.2':
|
||||||
resolution: {integrity: sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==}
|
resolution: {integrity: sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==}
|
||||||
|
|
||||||
|
'@transloadit/prettier-bytes@0.0.7':
|
||||||
|
resolution: {integrity: sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==}
|
||||||
|
|
||||||
'@types/estree@1.0.8':
|
'@types/estree@1.0.8':
|
||||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||||
|
|
||||||
|
'@types/event-emitter@0.3.5':
|
||||||
|
resolution: {integrity: sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ==}
|
||||||
|
|
||||||
'@types/node@25.9.1':
|
'@types/node@25.9.1':
|
||||||
resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==}
|
resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==}
|
||||||
|
|
||||||
|
'@uppy/companion-client@2.2.2':
|
||||||
|
resolution: {integrity: sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og==}
|
||||||
|
|
||||||
|
'@uppy/core@2.3.4':
|
||||||
|
resolution: {integrity: sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==}
|
||||||
|
|
||||||
|
'@uppy/store-default@2.1.1':
|
||||||
|
resolution: {integrity: sha512-xnpTxvot2SeAwGwbvmJ899ASk5tYXhmZzD/aCFsXePh/v8rNvR2pKlcQUH7cF/y4baUGq3FHO/daKCok/mpKqQ==}
|
||||||
|
|
||||||
|
'@uppy/utils@4.1.3':
|
||||||
|
resolution: {integrity: sha512-nTuMvwWYobnJcytDO3t+D6IkVq/Qs4Xv3vyoEZ+Iaf8gegZP+rEyoaFT2CK5XLRMienPyqRqNbIfRuFaOWSIFw==}
|
||||||
|
|
||||||
|
'@uppy/xhr-upload@2.1.3':
|
||||||
|
resolution: {integrity: sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@uppy/core': ^2.3.3
|
||||||
|
|
||||||
'@vitejs/plugin-vue@5.2.4':
|
'@vitejs/plugin-vue@5.2.4':
|
||||||
resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==}
|
resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==}
|
||||||
engines: {node: ^18.0.0 || >=20.0.0}
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
@@ -452,6 +484,97 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: ^3.2.0
|
vue: ^3.2.0
|
||||||
|
|
||||||
|
'@wangeditor/basic-modules@1.1.7':
|
||||||
|
resolution: {integrity: sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@wangeditor/core': 1.x
|
||||||
|
dom7: ^3.0.0
|
||||||
|
lodash.throttle: ^4.1.1
|
||||||
|
nanoid: ^3.2.0
|
||||||
|
slate: ^0.72.0
|
||||||
|
snabbdom: ^3.1.0
|
||||||
|
|
||||||
|
'@wangeditor/code-highlight@1.0.3':
|
||||||
|
resolution: {integrity: sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@wangeditor/core': 1.x
|
||||||
|
dom7: ^3.0.0
|
||||||
|
slate: ^0.72.0
|
||||||
|
snabbdom: ^3.1.0
|
||||||
|
|
||||||
|
'@wangeditor/core@1.1.19':
|
||||||
|
resolution: {integrity: sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==}
|
||||||
|
peerDependencies:
|
||||||
|
'@uppy/core': ^2.1.1
|
||||||
|
'@uppy/xhr-upload': ^2.0.3
|
||||||
|
dom7: ^3.0.0
|
||||||
|
is-hotkey: ^0.2.0
|
||||||
|
lodash.camelcase: ^4.3.0
|
||||||
|
lodash.clonedeep: ^4.5.0
|
||||||
|
lodash.debounce: ^4.0.8
|
||||||
|
lodash.foreach: ^4.5.0
|
||||||
|
lodash.isequal: ^4.5.0
|
||||||
|
lodash.throttle: ^4.1.1
|
||||||
|
lodash.toarray: ^4.4.0
|
||||||
|
nanoid: ^3.2.0
|
||||||
|
slate: ^0.72.0
|
||||||
|
snabbdom: ^3.1.0
|
||||||
|
|
||||||
|
'@wangeditor/editor-for-vue@5.1.12':
|
||||||
|
resolution: {integrity: sha512-0Ds3D8I+xnpNWezAeO7HmPRgTfUxHLMd9JKcIw+QzvSmhC5xUHbpCcLU+KLmeBKTR/zffnS5GQo6qi3GhTMJWQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@wangeditor/editor': '>=5.1.0'
|
||||||
|
vue: ^3.0.5
|
||||||
|
|
||||||
|
'@wangeditor/editor@5.1.23':
|
||||||
|
resolution: {integrity: sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ==}
|
||||||
|
|
||||||
|
'@wangeditor/list-module@1.0.5':
|
||||||
|
resolution: {integrity: sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@wangeditor/core': 1.x
|
||||||
|
dom7: ^3.0.0
|
||||||
|
slate: ^0.72.0
|
||||||
|
snabbdom: ^3.1.0
|
||||||
|
|
||||||
|
'@wangeditor/table-module@1.1.4':
|
||||||
|
resolution: {integrity: sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w==}
|
||||||
|
peerDependencies:
|
||||||
|
'@wangeditor/core': 1.x
|
||||||
|
dom7: ^3.0.0
|
||||||
|
lodash.isequal: ^4.5.0
|
||||||
|
lodash.throttle: ^4.1.1
|
||||||
|
nanoid: ^3.2.0
|
||||||
|
slate: ^0.72.0
|
||||||
|
snabbdom: ^3.1.0
|
||||||
|
|
||||||
|
'@wangeditor/upload-image-module@1.0.2':
|
||||||
|
resolution: {integrity: sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@uppy/core': ^2.0.3
|
||||||
|
'@uppy/xhr-upload': ^2.0.3
|
||||||
|
'@wangeditor/basic-modules': 1.x
|
||||||
|
'@wangeditor/core': 1.x
|
||||||
|
dom7: ^3.0.0
|
||||||
|
lodash.foreach: ^4.5.0
|
||||||
|
slate: ^0.72.0
|
||||||
|
snabbdom: ^3.1.0
|
||||||
|
|
||||||
|
'@wangeditor/video-module@1.1.4':
|
||||||
|
resolution: {integrity: sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@uppy/core': ^2.1.4
|
||||||
|
'@uppy/xhr-upload': ^2.0.7
|
||||||
|
'@wangeditor/core': 1.x
|
||||||
|
dom7: ^3.0.0
|
||||||
|
nanoid: ^3.2.0
|
||||||
|
slate: ^0.72.0
|
||||||
|
snabbdom: ^3.1.0
|
||||||
|
|
||||||
|
'@xmldom/xmldom@0.8.13':
|
||||||
|
resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
'@xmldom/xmldom@0.9.10':
|
'@xmldom/xmldom@0.9.10':
|
||||||
resolution: {integrity: sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==}
|
resolution: {integrity: sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==}
|
||||||
engines: {node: '>=14.6'}
|
engines: {node: '>=14.6'}
|
||||||
@@ -498,6 +621,10 @@ packages:
|
|||||||
csstype@3.2.3:
|
csstype@3.2.3:
|
||||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||||
|
|
||||||
|
d@1.0.2:
|
||||||
|
resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==}
|
||||||
|
engines: {node: '>=0.12'}
|
||||||
|
|
||||||
dayjs@1.11.20:
|
dayjs@1.11.20:
|
||||||
resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==}
|
resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==}
|
||||||
|
|
||||||
@@ -534,6 +661,9 @@ packages:
|
|||||||
dom-zindex@1.0.6:
|
dom-zindex@1.0.6:
|
||||||
resolution: {integrity: sha512-FKWIhiU96bi3xpP9ewRMgANsoVmMUBnMnmpCT6dPMZOunVYJQmJhSRruoI0XSPoHeIif3kyEuiHbFrOJwEJaEA==}
|
resolution: {integrity: sha512-FKWIhiU96bi3xpP9ewRMgANsoVmMUBnMnmpCT6dPMZOunVYJQmJhSRruoI0XSPoHeIif3kyEuiHbFrOJwEJaEA==}
|
||||||
|
|
||||||
|
dom7@3.0.0:
|
||||||
|
resolution: {integrity: sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==}
|
||||||
|
|
||||||
dunder-proto@1.0.1:
|
dunder-proto@1.0.1:
|
||||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -558,14 +688,35 @@ packages:
|
|||||||
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es5-ext@0.10.64:
|
||||||
|
resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==}
|
||||||
|
engines: {node: '>=0.10'}
|
||||||
|
|
||||||
|
es6-iterator@2.0.3:
|
||||||
|
resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==}
|
||||||
|
|
||||||
|
es6-symbol@3.1.4:
|
||||||
|
resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==}
|
||||||
|
engines: {node: '>=0.12'}
|
||||||
|
|
||||||
esbuild@0.25.12:
|
esbuild@0.25.12:
|
||||||
resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
|
resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
esniff@2.0.1:
|
||||||
|
resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==}
|
||||||
|
engines: {node: '>=0.10'}
|
||||||
|
|
||||||
estree-walker@2.0.2:
|
estree-walker@2.0.2:
|
||||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||||
|
|
||||||
|
event-emitter@0.3.5:
|
||||||
|
resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==}
|
||||||
|
|
||||||
|
ext@1.7.0:
|
||||||
|
resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==}
|
||||||
|
|
||||||
fdir@6.5.0:
|
fdir@6.5.0:
|
||||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
@@ -626,20 +777,39 @@ packages:
|
|||||||
resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==}
|
resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
html-void-elements@2.0.1:
|
||||||
|
resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==}
|
||||||
|
|
||||||
https-proxy-agent@5.0.1:
|
https-proxy-agent@5.0.1:
|
||||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
i18next@20.6.1:
|
||||||
|
resolution: {integrity: sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==}
|
||||||
|
|
||||||
immediate@3.0.6:
|
immediate@3.0.6:
|
||||||
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
|
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
|
||||||
|
|
||||||
|
immer@9.0.21:
|
||||||
|
resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==}
|
||||||
|
|
||||||
inherits@2.0.4:
|
inherits@2.0.4:
|
||||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||||
|
|
||||||
|
is-hotkey@0.2.0:
|
||||||
|
resolution: {integrity: sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==}
|
||||||
|
|
||||||
is-plain-object@3.0.1:
|
is-plain-object@3.0.1:
|
||||||
resolution: {integrity: sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==}
|
resolution: {integrity: sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
is-plain-object@5.0.0:
|
||||||
|
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
is-url@1.2.4:
|
||||||
|
resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==}
|
||||||
|
|
||||||
isarray@1.0.0:
|
isarray@1.0.0:
|
||||||
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
||||||
|
|
||||||
@@ -655,6 +825,28 @@ packages:
|
|||||||
lodash-es@4.18.1:
|
lodash-es@4.18.1:
|
||||||
resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==}
|
resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==}
|
||||||
|
|
||||||
|
lodash.camelcase@4.3.0:
|
||||||
|
resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
|
||||||
|
|
||||||
|
lodash.clonedeep@4.5.0:
|
||||||
|
resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==}
|
||||||
|
|
||||||
|
lodash.debounce@4.0.8:
|
||||||
|
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
|
||||||
|
|
||||||
|
lodash.foreach@4.5.0:
|
||||||
|
resolution: {integrity: sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==}
|
||||||
|
|
||||||
|
lodash.isequal@4.5.0:
|
||||||
|
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
|
||||||
|
deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
|
||||||
|
|
||||||
|
lodash.throttle@4.1.1:
|
||||||
|
resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==}
|
||||||
|
|
||||||
|
lodash.toarray@4.4.0:
|
||||||
|
resolution: {integrity: sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==}
|
||||||
|
|
||||||
lodash@4.18.1:
|
lodash@4.18.1:
|
||||||
resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
|
resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
|
||||||
|
|
||||||
@@ -673,6 +865,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
mime-match@1.0.2:
|
||||||
|
resolution: {integrity: sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==}
|
||||||
|
|
||||||
mime-types@2.1.35:
|
mime-types@2.1.35:
|
||||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@@ -683,6 +878,9 @@ packages:
|
|||||||
ms@2.1.3:
|
ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
|
namespace-emitter@2.0.1:
|
||||||
|
resolution: {integrity: sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==}
|
||||||
|
|
||||||
nanoid@3.3.12:
|
nanoid@3.3.12:
|
||||||
resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
|
resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
|
||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
@@ -696,6 +894,9 @@ packages:
|
|||||||
nanopop@2.4.2:
|
nanopop@2.4.2:
|
||||||
resolution: {integrity: sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==}
|
resolution: {integrity: sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==}
|
||||||
|
|
||||||
|
next-tick@1.1.0:
|
||||||
|
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
|
||||||
|
|
||||||
pako@1.0.11:
|
pako@1.0.11:
|
||||||
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
|
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
|
||||||
|
|
||||||
@@ -725,6 +926,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
|
resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
|
||||||
|
preact@10.29.7:
|
||||||
|
resolution: {integrity: sha512-DCHYrK/B10yUD3ZjLfhZ3WIE/9Vf9VFUODcRE2dRomTYDpJk6z6L9wecSfhfE6M9ZTHUdyQkoC46arIDhEV84Q==}
|
||||||
|
peerDependencies:
|
||||||
|
preact-render-to-string: '>=5'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
preact-render-to-string:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
prismjs@1.30.0:
|
||||||
|
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
process-nextick-args@2.0.1:
|
process-nextick-args@2.0.1:
|
||||||
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||||
|
|
||||||
@@ -759,10 +972,25 @@ packages:
|
|||||||
shallow-equal@1.2.1:
|
shallow-equal@1.2.1:
|
||||||
resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==}
|
resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==}
|
||||||
|
|
||||||
|
slate-history@0.66.0:
|
||||||
|
resolution: {integrity: sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==}
|
||||||
|
peerDependencies:
|
||||||
|
slate: '>=0.65.3'
|
||||||
|
|
||||||
|
slate@0.72.8:
|
||||||
|
resolution: {integrity: sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==}
|
||||||
|
|
||||||
|
snabbdom@3.6.4:
|
||||||
|
resolution: {integrity: sha512-VmxEfuw1/Y/eFj5VtMhYnukExpYiPkNzoo3+N3qwAOUDMl8wXgbli5ebR+j0knE3lZ/0eYskLxNcX64uy10N9w==}
|
||||||
|
engines: {node: '>=12.17.0'}
|
||||||
|
|
||||||
source-map-js@1.2.1:
|
source-map-js@1.2.1:
|
||||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
ssr-window@3.0.0:
|
||||||
|
resolution: {integrity: sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==}
|
||||||
|
|
||||||
string_decoder@1.1.1:
|
string_decoder@1.1.1:
|
||||||
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
|
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
|
||||||
|
|
||||||
@@ -773,10 +1001,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==}
|
resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==}
|
||||||
engines: {node: '>=12.22'}
|
engines: {node: '>=12.22'}
|
||||||
|
|
||||||
|
tiny-warning@1.0.3:
|
||||||
|
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
|
||||||
|
|
||||||
tinyglobby@0.2.16:
|
tinyglobby@0.2.16:
|
||||||
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
|
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
type@2.7.3:
|
||||||
|
resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
|
||||||
|
|
||||||
undici-types@7.24.6:
|
undici-types@7.24.6:
|
||||||
resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==}
|
resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==}
|
||||||
|
|
||||||
@@ -862,6 +1096,9 @@ packages:
|
|||||||
warning@4.0.3:
|
warning@4.0.3:
|
||||||
resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==}
|
resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==}
|
||||||
|
|
||||||
|
wildcard@1.1.2:
|
||||||
|
resolution: {integrity: sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==}
|
||||||
|
|
||||||
xe-utils@4.0.8:
|
xe-utils@4.0.8:
|
||||||
resolution: {integrity: sha512-l+p++9iPtSxpf/5ZCzkPXsyV0avE34KzieAC9oAhi+3e98PLJaBQHNiY2V8Lnbiux4JFAbU10p8AT6oujXkP9A==}
|
resolution: {integrity: sha512-l+p++9iPtSxpf/5ZCzkPXsyV0avE34KzieAC9oAhi+3e98PLJaBQHNiY2V8Lnbiux4JFAbU10p8AT6oujXkP9A==}
|
||||||
|
|
||||||
@@ -1067,12 +1304,47 @@ snapshots:
|
|||||||
core-js: 3.49.0
|
core-js: 3.49.0
|
||||||
nanopop: 2.4.2
|
nanopop: 2.4.2
|
||||||
|
|
||||||
|
'@transloadit/prettier-bytes@0.0.7': {}
|
||||||
|
|
||||||
'@types/estree@1.0.8': {}
|
'@types/estree@1.0.8': {}
|
||||||
|
|
||||||
|
'@types/event-emitter@0.3.5': {}
|
||||||
|
|
||||||
'@types/node@25.9.1':
|
'@types/node@25.9.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.24.6
|
undici-types: 7.24.6
|
||||||
|
|
||||||
|
'@uppy/companion-client@2.2.2':
|
||||||
|
dependencies:
|
||||||
|
'@uppy/utils': 4.1.3
|
||||||
|
namespace-emitter: 2.0.1
|
||||||
|
|
||||||
|
'@uppy/core@2.3.4':
|
||||||
|
dependencies:
|
||||||
|
'@transloadit/prettier-bytes': 0.0.7
|
||||||
|
'@uppy/store-default': 2.1.1
|
||||||
|
'@uppy/utils': 4.1.3
|
||||||
|
lodash.throttle: 4.1.1
|
||||||
|
mime-match: 1.0.2
|
||||||
|
namespace-emitter: 2.0.1
|
||||||
|
nanoid: 3.3.12
|
||||||
|
preact: 10.29.7
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- preact-render-to-string
|
||||||
|
|
||||||
|
'@uppy/store-default@2.1.1': {}
|
||||||
|
|
||||||
|
'@uppy/utils@4.1.3':
|
||||||
|
dependencies:
|
||||||
|
lodash.throttle: 4.1.1
|
||||||
|
|
||||||
|
'@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4)':
|
||||||
|
dependencies:
|
||||||
|
'@uppy/companion-client': 2.2.2
|
||||||
|
'@uppy/core': 2.3.4
|
||||||
|
'@uppy/utils': 4.1.3
|
||||||
|
nanoid: 3.3.12
|
||||||
|
|
||||||
'@vitejs/plugin-vue@5.2.4(vite@6.4.2(@types/node@25.9.1))(vue@3.5.34)':
|
'@vitejs/plugin-vue@5.2.4(vite@6.4.2(@types/node@25.9.1))(vue@3.5.34)':
|
||||||
dependencies:
|
dependencies:
|
||||||
vite: 6.4.2(@types/node@25.9.1)
|
vite: 6.4.2(@types/node@25.9.1)
|
||||||
@@ -1140,6 +1412,118 @@ snapshots:
|
|||||||
vue: 3.5.34
|
vue: 3.5.34
|
||||||
xe-utils: 4.0.8
|
xe-utils: 4.0.8
|
||||||
|
|
||||||
|
'@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4)':
|
||||||
|
dependencies:
|
||||||
|
'@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4)
|
||||||
|
dom7: 3.0.0
|
||||||
|
is-url: 1.2.4
|
||||||
|
lodash.throttle: 4.1.1
|
||||||
|
nanoid: 3.3.12
|
||||||
|
slate: 0.72.8
|
||||||
|
snabbdom: 3.6.4
|
||||||
|
|
||||||
|
'@wangeditor/code-highlight@1.0.3(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.4)':
|
||||||
|
dependencies:
|
||||||
|
'@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4)
|
||||||
|
dom7: 3.0.0
|
||||||
|
prismjs: 1.30.0
|
||||||
|
slate: 0.72.8
|
||||||
|
snabbdom: 3.6.4
|
||||||
|
|
||||||
|
'@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4)':
|
||||||
|
dependencies:
|
||||||
|
'@types/event-emitter': 0.3.5
|
||||||
|
'@uppy/core': 2.3.4
|
||||||
|
'@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4)
|
||||||
|
dom7: 3.0.0
|
||||||
|
event-emitter: 0.3.5
|
||||||
|
html-void-elements: 2.0.1
|
||||||
|
i18next: 20.6.1
|
||||||
|
is-hotkey: 0.2.0
|
||||||
|
lodash.camelcase: 4.3.0
|
||||||
|
lodash.clonedeep: 4.5.0
|
||||||
|
lodash.debounce: 4.0.8
|
||||||
|
lodash.foreach: 4.5.0
|
||||||
|
lodash.isequal: 4.5.0
|
||||||
|
lodash.throttle: 4.1.1
|
||||||
|
lodash.toarray: 4.4.0
|
||||||
|
nanoid: 3.3.12
|
||||||
|
scroll-into-view-if-needed: 2.2.31
|
||||||
|
slate: 0.72.8
|
||||||
|
slate-history: 0.66.0(slate@0.72.8)
|
||||||
|
snabbdom: 3.6.4
|
||||||
|
|
||||||
|
'@wangeditor/editor-for-vue@5.1.12(@wangeditor/editor@5.1.23)(vue@3.5.34)':
|
||||||
|
dependencies:
|
||||||
|
'@wangeditor/editor': 5.1.23
|
||||||
|
vue: 3.5.34
|
||||||
|
|
||||||
|
'@wangeditor/editor@5.1.23':
|
||||||
|
dependencies:
|
||||||
|
'@uppy/core': 2.3.4
|
||||||
|
'@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4)
|
||||||
|
'@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4)
|
||||||
|
'@wangeditor/code-highlight': 1.0.3(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.4)
|
||||||
|
'@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4)
|
||||||
|
'@wangeditor/list-module': 1.0.5(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.4)
|
||||||
|
'@wangeditor/table-module': 1.1.4(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4))(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4)
|
||||||
|
'@wangeditor/upload-image-module': 1.0.2(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4))(dom7@3.0.0)(lodash.foreach@4.5.0)(slate@0.72.8)(snabbdom@3.6.4)
|
||||||
|
'@wangeditor/video-module': 1.1.4(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4))(dom7@3.0.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4)
|
||||||
|
dom7: 3.0.0
|
||||||
|
is-hotkey: 0.2.0
|
||||||
|
lodash.camelcase: 4.3.0
|
||||||
|
lodash.clonedeep: 4.5.0
|
||||||
|
lodash.debounce: 4.0.8
|
||||||
|
lodash.foreach: 4.5.0
|
||||||
|
lodash.isequal: 4.5.0
|
||||||
|
lodash.throttle: 4.1.1
|
||||||
|
lodash.toarray: 4.4.0
|
||||||
|
nanoid: 3.3.12
|
||||||
|
slate: 0.72.8
|
||||||
|
snabbdom: 3.6.4
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- preact-render-to-string
|
||||||
|
|
||||||
|
'@wangeditor/list-module@1.0.5(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.4)':
|
||||||
|
dependencies:
|
||||||
|
'@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4)
|
||||||
|
dom7: 3.0.0
|
||||||
|
slate: 0.72.8
|
||||||
|
snabbdom: 3.6.4
|
||||||
|
|
||||||
|
'@wangeditor/table-module@1.1.4(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4))(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4)':
|
||||||
|
dependencies:
|
||||||
|
'@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4)
|
||||||
|
dom7: 3.0.0
|
||||||
|
lodash.isequal: 4.5.0
|
||||||
|
lodash.throttle: 4.1.1
|
||||||
|
nanoid: 3.3.12
|
||||||
|
slate: 0.72.8
|
||||||
|
snabbdom: 3.6.4
|
||||||
|
|
||||||
|
'@wangeditor/upload-image-module@1.0.2(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4))(dom7@3.0.0)(lodash.foreach@4.5.0)(slate@0.72.8)(snabbdom@3.6.4)':
|
||||||
|
dependencies:
|
||||||
|
'@uppy/core': 2.3.4
|
||||||
|
'@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4)
|
||||||
|
'@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4)
|
||||||
|
'@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4)
|
||||||
|
dom7: 3.0.0
|
||||||
|
lodash.foreach: 4.5.0
|
||||||
|
slate: 0.72.8
|
||||||
|
snabbdom: 3.6.4
|
||||||
|
|
||||||
|
'@wangeditor/video-module@1.1.4(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4))(dom7@3.0.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4)':
|
||||||
|
dependencies:
|
||||||
|
'@uppy/core': 2.3.4
|
||||||
|
'@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4)
|
||||||
|
'@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.12)(slate@0.72.8)(snabbdom@3.6.4)
|
||||||
|
dom7: 3.0.0
|
||||||
|
nanoid: 3.3.12
|
||||||
|
slate: 0.72.8
|
||||||
|
snabbdom: 3.6.4
|
||||||
|
|
||||||
|
'@xmldom/xmldom@0.8.13': {}
|
||||||
|
|
||||||
'@xmldom/xmldom@0.9.10': {}
|
'@xmldom/xmldom@0.9.10': {}
|
||||||
|
|
||||||
agent-base@6.0.2:
|
agent-base@6.0.2:
|
||||||
@@ -1207,6 +1591,11 @@ snapshots:
|
|||||||
|
|
||||||
csstype@3.2.3: {}
|
csstype@3.2.3: {}
|
||||||
|
|
||||||
|
d@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
es5-ext: 0.10.64
|
||||||
|
type: 2.7.3
|
||||||
|
|
||||||
dayjs@1.11.20: {}
|
dayjs@1.11.20: {}
|
||||||
|
|
||||||
debug@4.4.3:
|
debug@4.4.3:
|
||||||
@@ -1238,6 +1627,10 @@ snapshots:
|
|||||||
|
|
||||||
dom-zindex@1.0.6: {}
|
dom-zindex@1.0.6: {}
|
||||||
|
|
||||||
|
dom7@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
ssr-window: 3.0.0
|
||||||
|
|
||||||
dunder-proto@1.0.1:
|
dunder-proto@1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind-apply-helpers: 1.0.2
|
call-bind-apply-helpers: 1.0.2
|
||||||
@@ -1261,6 +1654,24 @@ snapshots:
|
|||||||
has-tostringtag: 1.0.2
|
has-tostringtag: 1.0.2
|
||||||
hasown: 2.0.3
|
hasown: 2.0.3
|
||||||
|
|
||||||
|
es5-ext@0.10.64:
|
||||||
|
dependencies:
|
||||||
|
es6-iterator: 2.0.3
|
||||||
|
es6-symbol: 3.1.4
|
||||||
|
esniff: 2.0.1
|
||||||
|
next-tick: 1.1.0
|
||||||
|
|
||||||
|
es6-iterator@2.0.3:
|
||||||
|
dependencies:
|
||||||
|
d: 1.0.2
|
||||||
|
es5-ext: 0.10.64
|
||||||
|
es6-symbol: 3.1.4
|
||||||
|
|
||||||
|
es6-symbol@3.1.4:
|
||||||
|
dependencies:
|
||||||
|
d: 1.0.2
|
||||||
|
ext: 1.7.0
|
||||||
|
|
||||||
esbuild@0.25.12:
|
esbuild@0.25.12:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@esbuild/aix-ppc64': 0.25.12
|
'@esbuild/aix-ppc64': 0.25.12
|
||||||
@@ -1290,8 +1701,24 @@ snapshots:
|
|||||||
'@esbuild/win32-ia32': 0.25.12
|
'@esbuild/win32-ia32': 0.25.12
|
||||||
'@esbuild/win32-x64': 0.25.12
|
'@esbuild/win32-x64': 0.25.12
|
||||||
|
|
||||||
|
esniff@2.0.1:
|
||||||
|
dependencies:
|
||||||
|
d: 1.0.2
|
||||||
|
es5-ext: 0.10.64
|
||||||
|
event-emitter: 0.3.5
|
||||||
|
type: 2.7.3
|
||||||
|
|
||||||
estree-walker@2.0.2: {}
|
estree-walker@2.0.2: {}
|
||||||
|
|
||||||
|
event-emitter@0.3.5:
|
||||||
|
dependencies:
|
||||||
|
d: 1.0.2
|
||||||
|
es5-ext: 0.10.64
|
||||||
|
|
||||||
|
ext@1.7.0:
|
||||||
|
dependencies:
|
||||||
|
type: 2.7.3
|
||||||
|
|
||||||
fdir@6.5.0(picomatch@4.0.4):
|
fdir@6.5.0(picomatch@4.0.4):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
picomatch: 4.0.4
|
picomatch: 4.0.4
|
||||||
@@ -1348,6 +1775,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind: 1.1.2
|
function-bind: 1.1.2
|
||||||
|
|
||||||
|
html-void-elements@2.0.1: {}
|
||||||
|
|
||||||
https-proxy-agent@5.0.1:
|
https-proxy-agent@5.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 6.0.2
|
agent-base: 6.0.2
|
||||||
@@ -1355,12 +1784,24 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
i18next@20.6.1:
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.29.2
|
||||||
|
|
||||||
immediate@3.0.6: {}
|
immediate@3.0.6: {}
|
||||||
|
|
||||||
|
immer@9.0.21: {}
|
||||||
|
|
||||||
inherits@2.0.4: {}
|
inherits@2.0.4: {}
|
||||||
|
|
||||||
|
is-hotkey@0.2.0: {}
|
||||||
|
|
||||||
is-plain-object@3.0.1: {}
|
is-plain-object@3.0.1: {}
|
||||||
|
|
||||||
|
is-plain-object@5.0.0: {}
|
||||||
|
|
||||||
|
is-url@1.2.4: {}
|
||||||
|
|
||||||
isarray@1.0.0: {}
|
isarray@1.0.0: {}
|
||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
@@ -1378,6 +1819,20 @@ snapshots:
|
|||||||
|
|
||||||
lodash-es@4.18.1: {}
|
lodash-es@4.18.1: {}
|
||||||
|
|
||||||
|
lodash.camelcase@4.3.0: {}
|
||||||
|
|
||||||
|
lodash.clonedeep@4.5.0: {}
|
||||||
|
|
||||||
|
lodash.debounce@4.0.8: {}
|
||||||
|
|
||||||
|
lodash.foreach@4.5.0: {}
|
||||||
|
|
||||||
|
lodash.isequal@4.5.0: {}
|
||||||
|
|
||||||
|
lodash.throttle@4.1.1: {}
|
||||||
|
|
||||||
|
lodash.toarray@4.4.0: {}
|
||||||
|
|
||||||
lodash@4.18.1: {}
|
lodash@4.18.1: {}
|
||||||
|
|
||||||
loose-envify@1.4.0:
|
loose-envify@1.4.0:
|
||||||
@@ -1392,6 +1847,10 @@ snapshots:
|
|||||||
|
|
||||||
mime-db@1.52.0: {}
|
mime-db@1.52.0: {}
|
||||||
|
|
||||||
|
mime-match@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
wildcard: 1.1.2
|
||||||
|
|
||||||
mime-types@2.1.35:
|
mime-types@2.1.35:
|
||||||
dependencies:
|
dependencies:
|
||||||
mime-db: 1.52.0
|
mime-db: 1.52.0
|
||||||
@@ -1400,12 +1859,16 @@ snapshots:
|
|||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
|
|
||||||
|
namespace-emitter@2.0.1: {}
|
||||||
|
|
||||||
nanoid@3.3.12: {}
|
nanoid@3.3.12: {}
|
||||||
|
|
||||||
nanoid@5.1.11: {}
|
nanoid@5.1.11: {}
|
||||||
|
|
||||||
nanopop@2.4.2: {}
|
nanopop@2.4.2: {}
|
||||||
|
|
||||||
|
next-tick@1.1.0: {}
|
||||||
|
|
||||||
pako@1.0.11: {}
|
pako@1.0.11: {}
|
||||||
|
|
||||||
pako@2.1.0: {}
|
pako@2.1.0: {}
|
||||||
@@ -1432,6 +1895,10 @@ snapshots:
|
|||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
preact@10.29.7: {}
|
||||||
|
|
||||||
|
prismjs@1.30.0: {}
|
||||||
|
|
||||||
process-nextick-args@2.0.1: {}
|
process-nextick-args@2.0.1: {}
|
||||||
|
|
||||||
proxy-from-env@2.1.0: {}
|
proxy-from-env@2.1.0: {}
|
||||||
@@ -1491,8 +1958,23 @@ snapshots:
|
|||||||
|
|
||||||
shallow-equal@1.2.1: {}
|
shallow-equal@1.2.1: {}
|
||||||
|
|
||||||
|
slate-history@0.66.0(slate@0.72.8):
|
||||||
|
dependencies:
|
||||||
|
is-plain-object: 5.0.0
|
||||||
|
slate: 0.72.8
|
||||||
|
|
||||||
|
slate@0.72.8:
|
||||||
|
dependencies:
|
||||||
|
immer: 9.0.21
|
||||||
|
is-plain-object: 5.0.0
|
||||||
|
tiny-warning: 1.0.3
|
||||||
|
|
||||||
|
snabbdom@3.6.4: {}
|
||||||
|
|
||||||
source-map-js@1.2.1: {}
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
|
ssr-window@3.0.0: {}
|
||||||
|
|
||||||
string_decoder@1.1.1:
|
string_decoder@1.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer: 5.1.2
|
safe-buffer: 5.1.2
|
||||||
@@ -1501,11 +1983,15 @@ snapshots:
|
|||||||
|
|
||||||
throttle-debounce@5.0.2: {}
|
throttle-debounce@5.0.2: {}
|
||||||
|
|
||||||
|
tiny-warning@1.0.3: {}
|
||||||
|
|
||||||
tinyglobby@0.2.16:
|
tinyglobby@0.2.16:
|
||||||
dependencies:
|
dependencies:
|
||||||
fdir: 6.5.0(picomatch@4.0.4)
|
fdir: 6.5.0(picomatch@4.0.4)
|
||||||
picomatch: 4.0.4
|
picomatch: 4.0.4
|
||||||
|
|
||||||
|
type@2.7.3: {}
|
||||||
|
|
||||||
undici-types@7.24.6: {}
|
undici-types@7.24.6: {}
|
||||||
|
|
||||||
util-deprecate@1.0.2: {}
|
util-deprecate@1.0.2: {}
|
||||||
@@ -1560,6 +2046,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
|
|
||||||
|
wildcard@1.1.2: {}
|
||||||
|
|
||||||
xe-utils@4.0.8: {}
|
xe-utils@4.0.8: {}
|
||||||
|
|
||||||
xml-js@1.6.11:
|
xml-js@1.6.11:
|
||||||
|
|||||||
@@ -106,6 +106,44 @@ export function finishLaunchSeal(id, data) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function initLaunchSealPosition(id) {
|
||||||
|
return request({
|
||||||
|
url: `/contract/launch/${id}/seal-position/init`,
|
||||||
|
method: "post",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLaunchSealPositionPage(id) {
|
||||||
|
return request({
|
||||||
|
url: `/contract/launch/${id}/seal-position/list`,
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveLaunchSealPositions(id, data = {}) {
|
||||||
|
return request({
|
||||||
|
url: `/contract/launch/${id}/seal-position/save`,
|
||||||
|
method: "post",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function batchUpdateLaunchSealPositionStatus(id, data) {
|
||||||
|
return request({
|
||||||
|
url: `/contract/launch/${id}/seal-position/batch-status`,
|
||||||
|
method: "post",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkLaunchSealPositions(id, data = {}) {
|
||||||
|
return request({
|
||||||
|
url: `/contract/launch/${id}/seal-position/check`,
|
||||||
|
method: "post",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function queryApproveViewUrl(data) {
|
export function queryApproveViewUrl(data) {
|
||||||
return request({
|
return request({
|
||||||
url: "/contract/push/settlement/approve/view-url",
|
url: "/contract/push/settlement/approve/view-url",
|
||||||
@@ -206,6 +244,15 @@ export function downloadLaunchFile(fileName) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function uploadWatermarkFile(id, formData) {
|
||||||
|
return request({
|
||||||
|
url: `/contract/launch/${id}/watermark/upload`,
|
||||||
|
method: "post",
|
||||||
|
data: formData,
|
||||||
|
headers: { "Content-Type": "multipart/form-data" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function queryContractLogList(data) {
|
export function queryContractLogList(data) {
|
||||||
return request({
|
return request({
|
||||||
url: "/contract/log/list",
|
url: "/contract/log/list",
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
import {
|
||||||
|
DEFAULT_MOCK_USER,
|
||||||
|
MOCK_USER_HEADER,
|
||||||
|
MOCK_USER_STORAGE_KEY,
|
||||||
|
getStoredMockUser,
|
||||||
|
setStoredMockUser,
|
||||||
|
} from '@/utils/mockUserStorage'
|
||||||
|
|
||||||
|
function getResult(res) {
|
||||||
|
return res?.result
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSuccessResponse(res) {
|
||||||
|
return String(res?.code || '') === '0000'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listMockUsers() {
|
||||||
|
return request({
|
||||||
|
url: '/mock/security/users',
|
||||||
|
method: 'get',
|
||||||
|
}).then((res) => (isSuccessResponse(res) ? getResult(res) || [] : []))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCurrentMockUser() {
|
||||||
|
return request({
|
||||||
|
url: '/mock/security/current',
|
||||||
|
method: 'get',
|
||||||
|
}).then((res) => (isSuccessResponse(res) ? getResult(res) || null : null))
|
||||||
|
}
|
||||||
+9
-1
@@ -175,7 +175,7 @@ export function downloadContract(contractId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// 回调轮询
|
// 回调轮询 / 测试回调
|
||||||
// ========================================
|
// ========================================
|
||||||
export function pollCallback(requestId) {
|
export function pollCallback(requestId) {
|
||||||
return request({
|
return request({
|
||||||
@@ -183,3 +183,11 @@ export function pollCallback(requestId) {
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mockPreSignSuccessCallback(data) {
|
||||||
|
return request({
|
||||||
|
url: '/seal/callback/presign/mock-success',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
<a-layout-sider width="244" class="contract-sider">
|
<a-layout-sider width="244" class="contract-sider">
|
||||||
<div class="sider-brand">
|
<div class="sider-brand">
|
||||||
<div class="brand-badge">合同管理</div>
|
<div class="brand-badge">合同管理</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a-menu
|
<a-menu
|
||||||
@@ -16,6 +15,10 @@
|
|||||||
<form-outlined />
|
<form-outlined />
|
||||||
<span>发起合同</span>
|
<span>发起合同</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
<a-menu-item key="/contract-console/sign">
|
||||||
|
<file-done-outlined />
|
||||||
|
<span>合同签署</span>
|
||||||
|
</a-menu-item>
|
||||||
<a-menu-item key="/contract-console/manifest">
|
<a-menu-item key="/contract-console/manifest">
|
||||||
<file-text-outlined />
|
<file-text-outlined />
|
||||||
<span>模板管理</span>
|
<span>模板管理</span>
|
||||||
@@ -32,6 +35,21 @@
|
|||||||
|
|
||||||
<div class="sider-footer">
|
<div class="sider-footer">
|
||||||
<a-button block @click="router.push('/')">返回首页</a-button>
|
<a-button block @click="router.push('/')">返回首页</a-button>
|
||||||
|
<div class="mock-user-panel">
|
||||||
|
<div class="mock-user-label">测试用户切换</div>
|
||||||
|
<a-select
|
||||||
|
v-model:value="selectedMockUserKey"
|
||||||
|
class="mock-user-select"
|
||||||
|
:options="mockUserOptions"
|
||||||
|
:loading="mockUserLoading"
|
||||||
|
placeholder="请选择测试用户"
|
||||||
|
@change="handleMockUserChange"
|
||||||
|
/>
|
||||||
|
<div v-if="currentMockUser" class="mock-user-meta">
|
||||||
|
{{ currentMockUser.systemType === 'manage' ? '管理端' : currentMockUser.systemType === 'business' ? '业务端' : '平台' }}
|
||||||
|
· {{ currentMockUser.orgName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
|
|
||||||
@@ -39,9 +57,7 @@
|
|||||||
<a-layout-header class="contract-header">
|
<a-layout-header class="contract-header">
|
||||||
<div>
|
<div>
|
||||||
<div class="header-title">{{ currentTitle }}</div>
|
<div class="header-title">{{ currentTitle }}</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</a-layout-header>
|
</a-layout-header>
|
||||||
|
|
||||||
<a-layout-content class="contract-content">
|
<a-layout-content class="contract-content">
|
||||||
@@ -52,14 +68,34 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { FileTextOutlined, FormOutlined, ProfileOutlined, TeamOutlined } from '@ant-design/icons-vue'
|
import { message } from 'ant-design-vue'
|
||||||
|
import {
|
||||||
|
getCurrentMockUser,
|
||||||
|
listMockUsers,
|
||||||
|
} from '@/api/mockUser'
|
||||||
|
import { getStoredMockUser, setStoredMockUser } from '@/utils/mockUserStorage'
|
||||||
|
import {
|
||||||
|
FileDoneOutlined,
|
||||||
|
FileTextOutlined,
|
||||||
|
FormOutlined,
|
||||||
|
ProfileOutlined,
|
||||||
|
TeamOutlined,
|
||||||
|
} from '@ant-design/icons-vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const mockUserLoading = ref(false)
|
||||||
|
const mockUserList = ref([])
|
||||||
|
const currentMockUser = ref(null)
|
||||||
|
const selectedMockUserKey = ref(getStoredMockUser())
|
||||||
|
|
||||||
const selectedKey = computed(() => {
|
const selectedKey = computed(() => {
|
||||||
|
if (route.path.startsWith('/contract-console/sign')) {
|
||||||
|
return '/contract-console/sign'
|
||||||
|
}
|
||||||
|
|
||||||
if (route.path.startsWith('/contract-console/manifest')) {
|
if (route.path.startsWith('/contract-console/manifest')) {
|
||||||
return '/contract-console/manifest'
|
return '/contract-console/manifest'
|
||||||
}
|
}
|
||||||
@@ -77,9 +113,49 @@ const selectedKey = computed(() => {
|
|||||||
|
|
||||||
const currentTitle = computed(() => route.meta?.title || '合同管理')
|
const currentTitle = computed(() => route.meta?.title || '合同管理')
|
||||||
|
|
||||||
|
const mockUserOptions = computed(() =>
|
||||||
|
mockUserList.value.map((item) => ({
|
||||||
|
label: `${item.displayName} (${item.orgId})`,
|
||||||
|
value: item.key,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|
||||||
function handleMenuClick({ key }) {
|
function handleMenuClick({ key }) {
|
||||||
router.push(key)
|
router.push(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadMockUsers() {
|
||||||
|
mockUserLoading.value = true
|
||||||
|
try {
|
||||||
|
mockUserList.value = await listMockUsers()
|
||||||
|
const availableKeys = new Set(mockUserList.value.map((item) => item.key))
|
||||||
|
if (!availableKeys.has(selectedMockUserKey.value) && mockUserList.value.length > 0) {
|
||||||
|
selectedMockUserKey.value = mockUserList.value[0].key
|
||||||
|
setStoredMockUser(selectedMockUserKey.value)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
mockUserLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCurrentMockUser() {
|
||||||
|
currentMockUser.value = await getCurrentMockUser()
|
||||||
|
if (currentMockUser.value?.key) {
|
||||||
|
selectedMockUserKey.value = currentMockUser.value.key
|
||||||
|
setStoredMockUser(currentMockUser.value.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMockUserChange(value) {
|
||||||
|
setStoredMockUser(value)
|
||||||
|
message.success('测试用户已切换,正在刷新页面数据')
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadMockUsers()
|
||||||
|
await loadCurrentMockUser()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -114,19 +190,6 @@ function handleMenuClick({ key }) {
|
|||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sider-brand h2 {
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #0f172a;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sider-brand p {
|
|
||||||
color: #64748b;
|
|
||||||
line-height: 1.8;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contract-menu {
|
.contract-menu {
|
||||||
border-inline-end: none;
|
border-inline-end: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -151,6 +214,33 @@ function handleMenuClick({ key }) {
|
|||||||
padding: 16px 20px 20px;
|
padding: 16px 20px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mock-user-panel {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: rgba(255, 255, 255, 0.78);
|
||||||
|
border: 1px solid rgba(37, 99, 235, 0.12);
|
||||||
|
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-user-label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #334155;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-user-select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-user-meta {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
.contract-header {
|
.contract-header {
|
||||||
height: auto;
|
height: auto;
|
||||||
background: rgba(255, 255, 255, 0.96);
|
background: rgba(255, 255, 255, 0.96);
|
||||||
@@ -169,12 +259,6 @@ function handleMenuClick({ key }) {
|
|||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-desc {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #64748b;
|
|
||||||
line-height: 1.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contract-content {
|
.contract-content {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|||||||
@@ -19,6 +19,14 @@ const routes = [
|
|||||||
title: '发起合同',
|
title: '发起合同',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'sign',
|
||||||
|
name: 'ContractSignManage',
|
||||||
|
component: () => import('@/views/contract/sign/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '合同签署',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'manifest',
|
path: 'manifest',
|
||||||
name: 'ContractTemplateManage',
|
name: 'ContractTemplateManage',
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export const MOCK_USER_STORAGE_KEY = 'stamp_mock_user'
|
||||||
|
export const DEFAULT_MOCK_USER = 'admin'
|
||||||
|
export const MOCK_USER_HEADER = 'X-Mock-User'
|
||||||
|
|
||||||
|
export function getStoredMockUser() {
|
||||||
|
return localStorage.getItem(MOCK_USER_STORAGE_KEY) || DEFAULT_MOCK_USER
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setStoredMockUser(userKey) {
|
||||||
|
if (!userKey) {
|
||||||
|
localStorage.removeItem(MOCK_USER_STORAGE_KEY)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
localStorage.setItem(MOCK_USER_STORAGE_KEY, userKey)
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
import { DEFAULT_MOCK_USER, getStoredMockUser, MOCK_USER_HEADER } from '@/utils/mockUserStorage'
|
||||||
import { message, Modal, notification } from 'ant-design-vue'
|
import { message, Modal, notification } from 'ant-design-vue'
|
||||||
|
|
||||||
const service = axios.create({
|
const service = axios.create({
|
||||||
@@ -12,6 +13,8 @@ service.interceptors.request.use(
|
|||||||
if (token) {
|
if (token) {
|
||||||
config.headers['Authorization'] = 'Bearer ' + token
|
config.headers['Authorization'] = 'Bearer ' + token
|
||||||
}
|
}
|
||||||
|
const mockUser = getStoredMockUser() || DEFAULT_MOCK_USER
|
||||||
|
config.headers[MOCK_USER_HEADER] = mockUser
|
||||||
return config
|
return config
|
||||||
},
|
},
|
||||||
(error) => Promise.reject(error),
|
(error) => Promise.reject(error),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
ok-text="确认采集"
|
ok-text="确认采集"
|
||||||
cancel-text="取消"
|
cancel-text="取消"
|
||||||
@ok="handleSubmit"
|
@ok="handleSubmit"
|
||||||
|
centered
|
||||||
>
|
>
|
||||||
<div class="collect-body">
|
<div class="collect-body">
|
||||||
<a-descriptions :column="2" size="small" bordered class="contract-brief">
|
<a-descriptions :column="2" size="small" bordered class="contract-brief">
|
||||||
|
|||||||
@@ -707,15 +707,15 @@
|
|||||||
<a-radio-group
|
<a-radio-group
|
||||||
v-model:value="detailState.isInvolveHongKongMacaoTaiwan"
|
v-model:value="detailState.isInvolveHongKongMacaoTaiwan"
|
||||||
>
|
>
|
||||||
<a-radio :value="true">是</a-radio>
|
<a-radio value="1">是</a-radio>
|
||||||
<a-radio :value="false">否</a-radio>
|
<a-radio value="0">否</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<div class="boolean-row">
|
<div class="boolean-row">
|
||||||
<span>是否公开招标</span>
|
<span>是否公开招标</span>
|
||||||
<a-radio-group v-model:value="detailState.isPublicTender">
|
<a-radio-group v-model:value="detailState.isPublicTender">
|
||||||
<a-radio :value="true">是</a-radio>
|
<a-radio value="1">是</a-radio>
|
||||||
<a-radio :value="false">否</a-radio>
|
<a-radio value="0">否</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<div class="boolean-row">
|
<div class="boolean-row">
|
||||||
@@ -723,8 +723,8 @@
|
|||||||
<a-radio-group
|
<a-radio-group
|
||||||
v-model:value="detailState.isForeignRelatedSubject"
|
v-model:value="detailState.isForeignRelatedSubject"
|
||||||
>
|
>
|
||||||
<a-radio :value="true">是</a-radio>
|
<a-radio value="1">是</a-radio>
|
||||||
<a-radio :value="false">否</a-radio>
|
<a-radio value="0">否</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -735,22 +735,22 @@
|
|||||||
<a-radio-group
|
<a-radio-group
|
||||||
v-model:value="detailState.isIncludedInCurrentYearBudget"
|
v-model:value="detailState.isIncludedInCurrentYearBudget"
|
||||||
>
|
>
|
||||||
<a-radio :value="true">是</a-radio>
|
<a-radio value="1">是</a-radio>
|
||||||
<a-radio :value="false">否</a-radio>
|
<a-radio value="0">否</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<div class="boolean-row">
|
<div class="boolean-row">
|
||||||
<span>是否共享</span>
|
<span>是否共享</span>
|
||||||
<a-radio-group v-model:value="detailState.isShared">
|
<a-radio-group v-model:value="detailState.isShared">
|
||||||
<a-radio :value="true">是</a-radio>
|
<a-radio value="1">是</a-radio>
|
||||||
<a-radio :value="false">否</a-radio>
|
<a-radio value="0">否</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<div class="boolean-row">
|
<div class="boolean-row">
|
||||||
<span>是否重大合同</span>
|
<span>是否重大合同</span>
|
||||||
<a-radio-group v-model:value="detailState.isMajorContract">
|
<a-radio-group v-model:value="detailState.isMajorContract">
|
||||||
<a-radio :value="true">是</a-radio>
|
<a-radio value="1">是</a-radio>
|
||||||
<a-radio :value="false">否</a-radio>
|
<a-radio value="0">否</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<div class="boolean-row">
|
<div class="boolean-row">
|
||||||
@@ -1043,12 +1043,9 @@ function normalizeFileItem(item = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function normalizeBooleanValue(value) {
|
function normalizeBooleanValue(value) {
|
||||||
if (value === true || value === false) return value;
|
if (value === 1 || value === "1" || value === true || value === "Y" || value === "true")
|
||||||
if (value === 1 || value === "1" || value === "Y" || value === "true")
|
return "1";
|
||||||
return true;
|
return "0";
|
||||||
if (value === 0 || value === "0" || value === "N" || value === "false")
|
|
||||||
return false;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function pickBooleanSource(source = {}, key) {
|
function pickBooleanSource(source = {}, key) {
|
||||||
@@ -1093,21 +1090,24 @@ function splitFilesByCategory(source = {}) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
contractBodyFileName:
|
contractBodyFileName:
|
||||||
getFileNameFromDetail(source, "body") || bodyFile.fileName || "",
|
source.contractBodyFileName ||
|
||||||
|
bodyFile.fileName ||
|
||||||
|
getFileNameFromDetail(source, "body") ||
|
||||||
|
"",
|
||||||
contractBodyFilePath:
|
contractBodyFilePath:
|
||||||
source.contractBodyFilePath ||
|
source.contractBodyFilePath ||
|
||||||
source.contractTextFilePath ||
|
|
||||||
bodyFile.filePath ||
|
bodyFile.filePath ||
|
||||||
|
source.contractTextFilePath ||
|
||||||
"",
|
"",
|
||||||
contractBodyFileType:
|
contractBodyFileType:
|
||||||
source.contractBodyFileType ||
|
source.contractBodyFileType ||
|
||||||
source.contractTextFileType ||
|
|
||||||
bodyFile.fileType ||
|
bodyFile.fileType ||
|
||||||
|
source.contractTextFileType ||
|
||||||
"",
|
"",
|
||||||
contractBodyFileSize:
|
contractBodyFileSize:
|
||||||
source.contractBodyFileSize ??
|
source.contractBodyFileSize ??
|
||||||
source.contractTextFileSize ??
|
|
||||||
bodyFile.fileSize ??
|
bodyFile.fileSize ??
|
||||||
|
source.contractTextFileSize ??
|
||||||
null,
|
null,
|
||||||
otherAttachmentList: source.otherAttachmentList?.length
|
otherAttachmentList: source.otherAttachmentList?.length
|
||||||
? source.otherAttachmentList.map(normalizeFileItem)
|
? source.otherAttachmentList.map(normalizeFileItem)
|
||||||
@@ -1184,6 +1184,9 @@ function createEmptyParty(roleCode, index = 0) {
|
|||||||
oppositeId: "",
|
oppositeId: "",
|
||||||
companyName: "",
|
companyName: "",
|
||||||
companyType: "",
|
companyType: "",
|
||||||
|
companyNature: "",
|
||||||
|
orgNature: "",
|
||||||
|
orgNatureLevel: "",
|
||||||
oppositeCharacter: "",
|
oppositeCharacter: "",
|
||||||
oppCharacter: "",
|
oppCharacter: "",
|
||||||
contactName: "",
|
contactName: "",
|
||||||
@@ -1193,6 +1196,8 @@ function createEmptyParty(roleCode, index = 0) {
|
|||||||
registeredAddress: "",
|
registeredAddress: "",
|
||||||
legalRepresentative: "",
|
legalRepresentative: "",
|
||||||
creditCode: "",
|
creditCode: "",
|
||||||
|
registeredCapitalText: "",
|
||||||
|
countryName: "",
|
||||||
naturalPersonIdType: "",
|
naturalPersonIdType: "",
|
||||||
naturalPersonIdNumber: "",
|
naturalPersonIdNumber: "",
|
||||||
isGroupInternal: false,
|
isGroupInternal: false,
|
||||||
@@ -1405,12 +1410,12 @@ function createContractFormData() {
|
|||||||
counterpartyList: [],
|
counterpartyList: [],
|
||||||
},
|
},
|
||||||
isSingleAgreement: "0",
|
isSingleAgreement: "0",
|
||||||
isInvolveHongKongMacaoTaiwan: false,
|
isInvolveHongKongMacaoTaiwan: "0",
|
||||||
isPublicTender: false,
|
isPublicTender: "0",
|
||||||
isForeignRelatedSubject: false,
|
isForeignRelatedSubject: "0",
|
||||||
isIncludedInCurrentYearBudget: false,
|
isIncludedInCurrentYearBudget: "0",
|
||||||
isShared: false,
|
isShared: "0",
|
||||||
isMajorContract: false,
|
isMajorContract: "0",
|
||||||
needAmendments: "0",
|
needAmendments: "0",
|
||||||
extraAmounts: {
|
extraAmounts: {
|
||||||
depositEnabled: false,
|
depositEnabled: false,
|
||||||
@@ -1734,6 +1739,9 @@ function mapPartnerToParty(raw = {}, roleKey, companyName) {
|
|||||||
oppositeId: raw.oppositeId || "",
|
oppositeId: raw.oppositeId || "",
|
||||||
companyName: raw.customerNameC || companyName || "",
|
companyName: raw.customerNameC || companyName || "",
|
||||||
companyType: getRelativeTypeLabel(raw.relativeType),
|
companyType: getRelativeTypeLabel(raw.relativeType),
|
||||||
|
companyNature: raw.oppositeCharacter || "",
|
||||||
|
orgNature: raw.natureOfEnterpriseOrg || "",
|
||||||
|
orgNatureLevel: raw.natureOfEnterpriseOrgLev || "",
|
||||||
oppositeCharacter: raw.oppositeCharacter || "",
|
oppositeCharacter: raw.oppositeCharacter || "",
|
||||||
oppCharacter: raw.oppositeCharacter || "",
|
oppCharacter: raw.oppositeCharacter || "",
|
||||||
creditCode: raw.customerTaxno || "",
|
creditCode: raw.customerTaxno || "",
|
||||||
@@ -1743,6 +1751,8 @@ function mapPartnerToParty(raw = {}, roleKey, companyName) {
|
|||||||
contactPhone: firstContact.contactPhone || raw.officephone || "",
|
contactPhone: firstContact.contactPhone || raw.officephone || "",
|
||||||
contactEmail: firstContact.email || raw.emailAddress || "",
|
contactEmail: firstContact.email || raw.emailAddress || "",
|
||||||
contactAddress: firstContact.postalAddress || raw.customerAddress || "",
|
contactAddress: firstContact.postalAddress || raw.customerAddress || "",
|
||||||
|
registeredCapitalText: raw.regCapital || raw.registeredCapital || "",
|
||||||
|
countryName: raw.countryName || raw.country || "",
|
||||||
naturalPersonIdType: raw.certificateType || "",
|
naturalPersonIdType: raw.certificateType || "",
|
||||||
naturalPersonIdNumber: raw.certificateNum || "",
|
naturalPersonIdNumber: raw.certificateNum || "",
|
||||||
isGroupInternal: raw.groupFlag === "Y" || raw.isJtn === "1",
|
isGroupInternal: raw.groupFlag === "Y" || raw.isJtn === "1",
|
||||||
@@ -1757,12 +1767,19 @@ function mapOrgNodeToParty(raw = {}) {
|
|||||||
...createEmptyParty(PARTY_ROLE_OUR),
|
...createEmptyParty(PARTY_ROLE_OUR),
|
||||||
subjectCode: raw.orgId == null ? "" : String(raw.orgId),
|
subjectCode: raw.orgId == null ? "" : String(raw.orgId),
|
||||||
companyName: raw.orgName || "",
|
companyName: raw.orgName || "",
|
||||||
|
companyNature: raw.companyNature || "1",
|
||||||
|
orgNature: raw.orgNature || "QY",
|
||||||
|
orgNatureLevel: raw.orgNatureLevel || "",
|
||||||
contactPhone: raw.contactPhone || "",
|
contactPhone: raw.contactPhone || "",
|
||||||
contactEmail: raw.contactEmail || "",
|
contactEmail: raw.contactEmail || "",
|
||||||
contactAddress: raw.contactAddress || "",
|
contactAddress: raw.contactAddress || "",
|
||||||
registeredAddress: raw.registeredAddress || "",
|
registeredAddress: raw.registeredAddress || "",
|
||||||
legalRepresentative: raw.legalRepresentative || "",
|
legalRepresentative: raw.legalRepresentative || "",
|
||||||
creditCode: raw.creditCode || "",
|
creditCode: raw.creditCode || "",
|
||||||
|
registeredCapitalText: raw.regCapital || raw.registeredCapital || "",
|
||||||
|
countryName: raw.countryName || raw.country || "",
|
||||||
|
isGroupInternal: true,
|
||||||
|
isHongKongMacaoTaiwan: false,
|
||||||
roleCode: PARTY_ROLE_OUR,
|
roleCode: PARTY_ROLE_OUR,
|
||||||
roleName: "我方",
|
roleName: "我方",
|
||||||
};
|
};
|
||||||
@@ -2172,9 +2189,9 @@ function validateSubmit() {
|
|||||||
// 以下为 pushContract 必填项补充校验(接口文档要求)
|
// 以下为 pushContract 必填项补充校验(接口文档要求)
|
||||||
if (detailState.value.isMajorContract === undefined || detailState.value.isMajorContract === null || detailState.value.isMajorContract === "")
|
if (detailState.value.isMajorContract === undefined || detailState.value.isMajorContract === null || detailState.value.isMajorContract === "")
|
||||||
return "请选择是否重大合同";
|
return "请选择是否重大合同";
|
||||||
if (detailState.value.isPublicTender === undefined || detailState.value.isPublicTender === null)
|
if (detailState.value.isPublicTender === undefined || detailState.value.isPublicTender === null || detailState.value.isPublicTender === "")
|
||||||
return "请选择是否公开招标";
|
return "请选择是否公开招标";
|
||||||
if (detailState.value.isInvolveHongKongMacaoTaiwan === undefined || detailState.value.isInvolveHongKongMacaoTaiwan === null)
|
if (detailState.value.isInvolveHongKongMacaoTaiwan === undefined || detailState.value.isInvolveHongKongMacaoTaiwan === null || detailState.value.isInvolveHongKongMacaoTaiwan === "")
|
||||||
return "请选择是否涉及港澳台";
|
return "请选择是否涉及港澳台";
|
||||||
if (!detailState.value.paymentDirection) return "请选择收支方向";
|
if (!detailState.value.paymentDirection) return "请选择收支方向";
|
||||||
if (!detailState.value.contractTypeCode) return "请选择合同类型编码";
|
if (!detailState.value.contractTypeCode) return "请选择合同类型编码";
|
||||||
@@ -2376,12 +2393,12 @@ function buildLaunchRecord() {
|
|||||||
isElectron: detailState.value.isElectron || "1",
|
isElectron: detailState.value.isElectron || "1",
|
||||||
isSingleAgreement: detailState.value.isSingleAgreement || "0",
|
isSingleAgreement: detailState.value.isSingleAgreement || "0",
|
||||||
paymentDirection: detailState.value.paymentDirection || "0",
|
paymentDirection: detailState.value.paymentDirection || "0",
|
||||||
isMajorContract: detailState.value.isMajorContract ? 1 : 0,
|
isMajorContract: detailState.value.isMajorContract,
|
||||||
isOpenBidding: detailState.value.isPublicTender ? 1 : 0,
|
isOpenBidding: detailState.value.isPublicTender,
|
||||||
isYearBudget: detailState.value.isIncludedInCurrentYearBudget ? 1 : 0,
|
isYearBudget: detailState.value.isIncludedInCurrentYearBudget,
|
||||||
isShare: detailState.value.isShared ? "1" : "0",
|
isShare: detailState.value.isShared,
|
||||||
contractNature: detailState.value.isForeignRelatedSubject ? "1" : "0",
|
contractNature: detailState.value.isForeignRelatedSubject,
|
||||||
isSgat: detailState.value.isInvolveHongKongMacaoTaiwan ? "1" : "0",
|
isSgat: detailState.value.isInvolveHongKongMacaoTaiwan,
|
||||||
biddingNo: detailState.value.isPublicTender
|
biddingNo: detailState.value.isPublicTender
|
||||||
? detailState.value.biddingNo || ""
|
? detailState.value.biddingNo || ""
|
||||||
: "",
|
: "",
|
||||||
|
|||||||
@@ -71,12 +71,34 @@
|
|||||||
v-for="field in launchFieldList"
|
v-for="field in launchFieldList"
|
||||||
:key="field.key"
|
:key="field.key"
|
||||||
:label="field.label"
|
:label="field.label"
|
||||||
:required="!field.readonly"
|
:required="field.required"
|
||||||
|
:extra="field.remark || undefined"
|
||||||
|
:class="{ 'grid-span-2': field.fieldType === 'rich_text' }"
|
||||||
>
|
>
|
||||||
<a-input
|
<a-select
|
||||||
|
v-if="field.fieldType === 'select'"
|
||||||
:value="getLaunchFieldValue(field.key)"
|
:value="getLaunchFieldValue(field.key)"
|
||||||
:placeholder="`请输入${field.label}`"
|
:options="getFieldDictOptions(field)"
|
||||||
:readonly="field.readonly"
|
:placeholder="field.placeholder"
|
||||||
|
:loading="isDictLoading(field.dictType)"
|
||||||
|
:disabled="isViewMode"
|
||||||
|
allow-clear
|
||||||
|
show-search
|
||||||
|
option-filter-prop="label"
|
||||||
|
@update:value="setLaunchFieldValue(field.key, $event)"
|
||||||
|
/>
|
||||||
|
<TemplateRichTextEditor
|
||||||
|
v-else-if="field.fieldType === 'rich_text'"
|
||||||
|
:model-value="getLaunchFieldValue(field.key)"
|
||||||
|
:placeholder="field.placeholder"
|
||||||
|
:disabled="isViewMode"
|
||||||
|
@update:model-value="setLaunchFieldValue(field.key, $event)"
|
||||||
|
/>
|
||||||
|
<a-input
|
||||||
|
v-else
|
||||||
|
:value="getLaunchFieldValue(field.key)"
|
||||||
|
:placeholder="field.placeholder"
|
||||||
|
:readonly="isViewMode"
|
||||||
@update:value="setLaunchFieldValue(field.key, $event)"
|
@update:value="setLaunchFieldValue(field.key, $event)"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -177,7 +199,6 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, nextTick, onBeforeUnmount, ref, watch } from "vue";
|
import { computed, nextTick, onBeforeUnmount, ref, watch } from "vue";
|
||||||
import Docxtemplater from "docxtemplater";
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {
|
import {
|
||||||
FileWordOutlined,
|
FileWordOutlined,
|
||||||
@@ -185,9 +206,9 @@ import {
|
|||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
} from "@ant-design/icons-vue";
|
} from "@ant-design/icons-vue";
|
||||||
import { message } from "ant-design-vue";
|
import { message } from "ant-design-vue";
|
||||||
import PizZip from "pizzip";
|
|
||||||
import { renderAsync } from "docx-preview";
|
import { renderAsync } from "docx-preview";
|
||||||
import { saveAs } from "file-saver";
|
import { saveAs } from "file-saver";
|
||||||
|
import { getDictDataByType } from "@/api/dict.js";
|
||||||
import {
|
import {
|
||||||
downloadTemplateFile,
|
downloadTemplateFile,
|
||||||
queryPublishedManifestList,
|
queryPublishedManifestList,
|
||||||
@@ -197,6 +218,10 @@ import {
|
|||||||
buildTemplateFieldLabelMap,
|
buildTemplateFieldLabelMap,
|
||||||
loadTemplateFieldList,
|
loadTemplateFieldList,
|
||||||
} from "../../shared/templateFieldDict.js";
|
} from "../../shared/templateFieldDict.js";
|
||||||
|
import { renderTemplateDocxBlob } from "../../shared/templateDocxRenderer.js";
|
||||||
|
import { buildTemplateLaunchFieldList } from "../../shared/templateVariableConfig.js";
|
||||||
|
import { buildOurPartyAutoFillValues } from "../../shared/templateAutoFillMappings.js";
|
||||||
|
import TemplateRichTextEditor from "./TemplateRichTextEditor.vue";
|
||||||
import {
|
import {
|
||||||
extraAmountFieldOptions,
|
extraAmountFieldOptions,
|
||||||
} from "../contractLaunchOptions.js";
|
} from "../contractLaunchOptions.js";
|
||||||
@@ -293,6 +318,21 @@ function createParty(roleCode = PARTY_ROLE_OPPOSITE, index = 0) {
|
|||||||
: `相对方${index + 1}`,
|
: `相对方${index + 1}`,
|
||||||
subjectCode: "",
|
subjectCode: "",
|
||||||
companyName: "",
|
companyName: "",
|
||||||
|
companyType: "",
|
||||||
|
companyNature: "",
|
||||||
|
orgNature: "",
|
||||||
|
orgNatureLevel: "",
|
||||||
|
isGroupInternal: false,
|
||||||
|
isHongKongMacaoTaiwan: false,
|
||||||
|
creditCode: "",
|
||||||
|
registeredAddress: "",
|
||||||
|
legalRepresentative: "",
|
||||||
|
registeredCapitalText: "",
|
||||||
|
contactName: "",
|
||||||
|
contactPhone: "",
|
||||||
|
contactEmail: "",
|
||||||
|
contactAddress: "",
|
||||||
|
countryName: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,6 +351,9 @@ function mapLaunchPartyToFormParty(item = {}) {
|
|||||||
oppositeId: item.oppositeId || "",
|
oppositeId: item.oppositeId || "",
|
||||||
companyName: item.companyName || "",
|
companyName: item.companyName || "",
|
||||||
companyType: item.companyType || "",
|
companyType: item.companyType || "",
|
||||||
|
companyNature: item.companyNature || item.oppositeCharacter || "",
|
||||||
|
orgNature: item.orgNature || item.natureOfEnterpriseOrg || "",
|
||||||
|
orgNatureLevel: item.orgNatureLevel || item.natureOfEnterpriseOrgLev || "",
|
||||||
oppCharacter: item.oppositeCharacter || "",
|
oppCharacter: item.oppositeCharacter || "",
|
||||||
contactName: item.contactName || "",
|
contactName: item.contactName || "",
|
||||||
contactPhone: item.contactPhone || "",
|
contactPhone: item.contactPhone || "",
|
||||||
@@ -319,6 +362,9 @@ function mapLaunchPartyToFormParty(item = {}) {
|
|||||||
registeredAddress: item.registeredAddress || "",
|
registeredAddress: item.registeredAddress || "",
|
||||||
legalRepresentative: item.legalRepresentative || "",
|
legalRepresentative: item.legalRepresentative || "",
|
||||||
creditCode: item.creditCode || "",
|
creditCode: item.creditCode || "",
|
||||||
|
registeredCapitalText:
|
||||||
|
item.registeredCapitalText || item.regCapital || item.registeredCapital || "",
|
||||||
|
countryName: item.countryName || item.country || "",
|
||||||
bankName: item.bankName || "",
|
bankName: item.bankName || "",
|
||||||
bankAccount: item.bankAccount || "",
|
bankAccount: item.bankAccount || "",
|
||||||
naturalPersonIdType: item.naturalPersonIdType || "",
|
naturalPersonIdType: item.naturalPersonIdType || "",
|
||||||
@@ -455,12 +501,12 @@ function createContractFormData() {
|
|||||||
isSingleAgreement: "0",
|
isSingleAgreement: "0",
|
||||||
|
|
||||||
// 合同属性
|
// 合同属性
|
||||||
isInvolveHongKongMacaoTaiwan: false, // 是否涉及港澳台
|
isInvolveHongKongMacaoTaiwan: "0", // 是否涉及港澳台
|
||||||
isPublicTender: false, // 是否公开招标
|
isPublicTender: "0", // 是否公开招标
|
||||||
isForeignRelatedSubject: false, // 签约主体是否涉外
|
isForeignRelatedSubject: "0", // 签约主体是否涉外
|
||||||
isIncludedInCurrentYearBudget: false, // 是否列入当年预算
|
isIncludedInCurrentYearBudget: "0", // 是否列入当年预算
|
||||||
isShared: false, // 是否共享
|
isShared: "0", // 是否共享
|
||||||
isMajorContract: false, // 是否重大合同
|
isMajorContract: "0", // 是否重大合同
|
||||||
|
|
||||||
// 额外金额
|
// 额外金额
|
||||||
extraAmounts: {
|
extraAmounts: {
|
||||||
@@ -599,16 +645,9 @@ function copyContractFormData(data = {}) {
|
|||||||
return form;
|
return form;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTemplateRenderData(templateFieldValues = {}) {
|
|
||||||
const data = {};
|
|
||||||
(templateFieldList.value || []).forEach((item) => {
|
|
||||||
data[item.value] = templateFieldValues?.[item.value] ?? "";
|
|
||||||
});
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emit = defineEmits(["save"]);
|
const emit = defineEmits(["save"]);
|
||||||
|
|
||||||
|
const formRef = ref(null);
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
const previewRef = ref(null);
|
const previewRef = ref(null);
|
||||||
const modalMode = ref("create");
|
const modalMode = ref("create");
|
||||||
@@ -622,6 +661,8 @@ const previewBlob = ref(null);
|
|||||||
const manualBodyFileList = ref([]);
|
const manualBodyFileList = ref([]);
|
||||||
const publishedTemplates = ref([]);
|
const publishedTemplates = ref([]);
|
||||||
const templateFieldList = ref([]);
|
const templateFieldList = ref([]);
|
||||||
|
const dictOptionMap = ref({});
|
||||||
|
const dictLoadingMap = ref({});
|
||||||
|
|
||||||
const templateBufferCache = new Map();
|
const templateBufferCache = new Map();
|
||||||
let previewTimer = null;
|
let previewTimer = null;
|
||||||
@@ -648,19 +689,10 @@ const templateFieldLabelMap = computed(() =>
|
|||||||
|
|
||||||
const launchFieldList = computed(() => {
|
const launchFieldList = computed(() => {
|
||||||
if (creationMode.value === "manual") return [];
|
if (creationMode.value === "manual") return [];
|
||||||
const mappings = activeTemplate.value?.variableMappings || [];
|
return buildTemplateLaunchFieldList(
|
||||||
const keys = [];
|
activeTemplate.value?.variableMappings || [],
|
||||||
mappings.forEach((item) => {
|
templateFieldLabelMap.value,
|
||||||
const fieldKey = item.bindField || item.variableKey;
|
);
|
||||||
if (fieldKey && !keys.includes(fieldKey)) {
|
|
||||||
keys.push(fieldKey);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return keys.map((key) => ({
|
|
||||||
key,
|
|
||||||
label: templateFieldLabelMap.value[key] || key,
|
|
||||||
readonly: false,
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function getLaunchFieldValue(fieldKey) {
|
function getLaunchFieldValue(fieldKey) {
|
||||||
@@ -671,15 +703,111 @@ function setLaunchFieldValue(fieldKey, value) {
|
|||||||
if (!contractDetailState.value.templateFieldValues) {
|
if (!contractDetailState.value.templateFieldValues) {
|
||||||
contractDetailState.value.templateFieldValues = {};
|
contractDetailState.value.templateFieldValues = {};
|
||||||
}
|
}
|
||||||
contractDetailState.value.templateFieldValues[fieldKey] = value;
|
contractDetailState.value.templateFieldValues[fieldKey] = value ?? "";
|
||||||
if (visible.value) {
|
if (visible.value) {
|
||||||
schedulePreview();
|
schedulePreview();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const templateVariables = computed(() =>
|
function normalizeDictOptions(list = []) {
|
||||||
buildTemplateRenderData(contractDetailState.value.templateFieldValues || {}),
|
return (list || [])
|
||||||
);
|
.map((item) => ({
|
||||||
|
label: item?.k || item?.label || "",
|
||||||
|
value: item?.v || item?.value || "",
|
||||||
|
remark: item?.remark || "",
|
||||||
|
}))
|
||||||
|
.filter((item) => item.label && item.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadDictOptionsByType(dictType) {
|
||||||
|
const key = String(dictType || "").trim();
|
||||||
|
if (!key) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (dictOptionMap.value[key]?.length) {
|
||||||
|
return dictOptionMap.value[key];
|
||||||
|
}
|
||||||
|
if (dictLoadingMap.value[key]) {
|
||||||
|
return dictOptionMap.value[key] || [];
|
||||||
|
}
|
||||||
|
dictLoadingMap.value = { ...dictLoadingMap.value, [key]: true };
|
||||||
|
try {
|
||||||
|
const result = await getDictDataByType(key);
|
||||||
|
const options = normalizeDictOptions(result);
|
||||||
|
dictOptionMap.value = { ...dictOptionMap.value, [key]: options };
|
||||||
|
return options;
|
||||||
|
} finally {
|
||||||
|
dictLoadingMap.value = { ...dictLoadingMap.value, [key]: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCurrentTemplateDictOptions() {
|
||||||
|
if (creationMode.value !== "template") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dictTypes = [...new Set(
|
||||||
|
launchFieldList.value
|
||||||
|
.filter((item) => item.fieldType === "select" && item.dictType)
|
||||||
|
.map((item) => item.dictType),
|
||||||
|
)];
|
||||||
|
await Promise.all(dictTypes.map((item) => loadDictOptionsByType(item)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFieldDictOptions(field = {}) {
|
||||||
|
return dictOptionMap.value[String(field.dictType || "").trim()] || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDictLoading(dictType) {
|
||||||
|
return !!dictLoadingMap.value[String(dictType || "").trim()];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLaunchFieldByKey(fieldKey) {
|
||||||
|
return launchFieldList.value.find((item) => item.key === fieldKey) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLaunchFieldDisplayValue(field = {}) {
|
||||||
|
const rawValue = getLaunchFieldValue(field.key);
|
||||||
|
if (field.fieldType !== "select") {
|
||||||
|
return rawValue;
|
||||||
|
}
|
||||||
|
const option = getFieldDictOptions(field).find(
|
||||||
|
(item) => String(item.value) === String(rawValue ?? ""),
|
||||||
|
);
|
||||||
|
return option?.label ?? rawValue ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripRichTextContent(htmlValue) {
|
||||||
|
return String(htmlValue || "")
|
||||||
|
.replace(/<table[\s\S]*?<\/table>/gi, " [表格] ")
|
||||||
|
.replace(/<style[\s\S]*?<\/style>/gi, " ")
|
||||||
|
.replace(/<script[\s\S]*?<\/script>/gi, " ")
|
||||||
|
.replace(/<[^>]+>/g, " ")
|
||||||
|
.replace(/ /gi, " ")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLaunchFieldEmpty(field = {}, value) {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (field.fieldType === "rich_text") {
|
||||||
|
return !stripRichTextContent(value);
|
||||||
|
}
|
||||||
|
return String(value).trim() === "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTemplateRenderFieldValues() {
|
||||||
|
const values = {
|
||||||
|
...(contractDetailState.value.templateFieldValues || {}),
|
||||||
|
};
|
||||||
|
launchFieldList.value.forEach((field) => {
|
||||||
|
if (field.fieldType === "select") {
|
||||||
|
values[field.key] = getLaunchFieldDisplayValue(field);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
const hasPreviewSource = computed(() => {
|
const hasPreviewSource = computed(() => {
|
||||||
return creationMode.value === "manual"
|
return creationMode.value === "manual"
|
||||||
@@ -710,9 +838,11 @@ watch(
|
|||||||
previewError.value = "";
|
previewError.value = "";
|
||||||
if (visible.value && creationMode.value === "template") {
|
if (visible.value && creationMode.value === "template") {
|
||||||
prefillFromMainFields();
|
prefillFromMainFields();
|
||||||
|
loadCurrentTemplateDictOptions().finally(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
schedulePreview();
|
schedulePreview();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -721,15 +851,17 @@ watch(
|
|||||||
() => visible.value,
|
() => visible.value,
|
||||||
(open) => {
|
(open) => {
|
||||||
if (open) {
|
if (open) {
|
||||||
|
loadCurrentTemplateDictOptions().finally(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
refreshPreview();
|
refreshPreview();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => JSON.stringify(templateVariables.value),
|
() => JSON.stringify(contractDetailState.value.templateFieldValues || {}),
|
||||||
() => {
|
() => {
|
||||||
if (visible.value) {
|
if (visible.value) {
|
||||||
schedulePreview();
|
schedulePreview();
|
||||||
@@ -764,7 +896,9 @@ watch(
|
|||||||
previewBlob.value = null;
|
previewBlob.value = null;
|
||||||
previewError.value = "";
|
previewError.value = "";
|
||||||
if (visible.value) {
|
if (visible.value) {
|
||||||
|
loadCurrentTemplateDictOptions().finally(() => {
|
||||||
schedulePreview();
|
schedulePreview();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -793,6 +927,22 @@ function loadTemplateFields() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => JSON.stringify(
|
||||||
|
launchFieldList.value.map((item) => ({
|
||||||
|
key: item.key,
|
||||||
|
fieldType: item.fieldType,
|
||||||
|
dictType: item.dictType,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
() => {
|
||||||
|
if (visible.value && creationMode.value === "template") {
|
||||||
|
loadCurrentTemplateDictOptions();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: false },
|
||||||
|
);
|
||||||
|
|
||||||
function getManifestDetailLocal(templateId) {
|
function getManifestDetailLocal(templateId) {
|
||||||
return (
|
return (
|
||||||
publishedTemplates.value.find((item) => item.id === templateId) || null
|
publishedTemplates.value.find((item) => item.id === templateId) || null
|
||||||
@@ -869,6 +1019,8 @@ async function buildFilledDocumentBlob() {
|
|||||||
throw new Error("请选择合同模板");
|
throw new Error("请选择合同模板");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await loadCurrentTemplateDictOptions();
|
||||||
|
|
||||||
const buffer = activeTemplate.value.templateArrayBuffer
|
const buffer = activeTemplate.value.templateArrayBuffer
|
||||||
? activeTemplate.value.templateArrayBuffer.slice(0)
|
? activeTemplate.value.templateArrayBuffer.slice(0)
|
||||||
: await fetchTemplateBuffer(
|
: await fetchTemplateBuffer(
|
||||||
@@ -876,36 +1028,10 @@ async function buildFilledDocumentBlob() {
|
|||||||
activeTemplate.value.templateFileUrl ||
|
activeTemplate.value.templateFileUrl ||
|
||||||
activeTemplate.value.fileUrl,
|
activeTemplate.value.fileUrl,
|
||||||
);
|
);
|
||||||
const zip = new PizZip(buffer);
|
return await renderTemplateDocxBlob({
|
||||||
const doc = new Docxtemplater(zip, {
|
templateArrayBuffer: buffer,
|
||||||
paragraphLoop: true,
|
variableMappings: activeTemplate.value.variableMappings || [],
|
||||||
linebreaks: true,
|
templateFieldValues: buildTemplateRenderFieldValues(),
|
||||||
nullGetter(part) {
|
|
||||||
return part?.raw ? `[${part.raw}]` : "";
|
|
||||||
},
|
|
||||||
});
|
|
||||||
//这里是生成的测试数据
|
|
||||||
const sourceValues = buildTemplateRenderData(
|
|
||||||
contractDetailState.value.templateFieldValues || {},
|
|
||||||
);
|
|
||||||
const mappings = activeTemplate.value.variableMappings || [];
|
|
||||||
|
|
||||||
const renderData = {};
|
|
||||||
if (mappings.length) {
|
|
||||||
mappings.forEach((item) => {
|
|
||||||
if (!item.variableKey) return;
|
|
||||||
renderData[item.variableKey] = sourceValues[item.bindField] ?? "";
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Object.assign(renderData, sourceValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
doc.render(renderData);
|
|
||||||
|
|
||||||
return doc.getZip().generate({
|
|
||||||
type: "blob",
|
|
||||||
mimeType:
|
|
||||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1048,12 +1174,11 @@ function validateMainForm() {
|
|||||||
return "请选择合同模板";
|
return "请选择合同模板";
|
||||||
|
|
||||||
for (const field of launchFieldList.value) {
|
for (const field of launchFieldList.value) {
|
||||||
if (field.readonly) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const value = getLaunchFieldValue(field.key);
|
const value = getLaunchFieldValue(field.key);
|
||||||
if (value === null || value === undefined || value === "") {
|
if (field.required && isLaunchFieldEmpty(field, value)) {
|
||||||
return `请输入${field.label}`;
|
return field.fieldType === "select"
|
||||||
|
? `请选择${field.label}`
|
||||||
|
: `请输入${field.label}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1129,6 +1254,7 @@ async function showModal(initialValues = {}, mode = "create") {
|
|||||||
|
|
||||||
// 预填充:从主合同同名字段自动取值填入模板变量
|
// 预填充:从主合同同名字段自动取值填入模板变量
|
||||||
prefillFromMainFields(initialValues.detailState || initialValues);
|
prefillFromMainFields(initialValues.detailState || initialValues);
|
||||||
|
await loadCurrentTemplateDictOptions();
|
||||||
|
|
||||||
syncManualBodyFileList();
|
syncManualBodyFileList();
|
||||||
previewBlob.value = null;
|
previewBlob.value = null;
|
||||||
@@ -1153,6 +1279,16 @@ function prefillFromMainFields(rawSource) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let changed = false;
|
let changed = false;
|
||||||
|
const ourPartyAutoFillValues = buildOurPartyAutoFillValues(contractDetailState.value);
|
||||||
|
Object.entries(ourPartyAutoFillValues).forEach(([key, value]) => {
|
||||||
|
if (!keysToFill.has(key)) return;
|
||||||
|
if (values[key] !== undefined && values[key] !== null && values[key] !== "") return;
|
||||||
|
if (value !== undefined && value !== null && value !== "") {
|
||||||
|
values[key] = String(value);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
keysToFill.forEach(key => {
|
keysToFill.forEach(key => {
|
||||||
if (values[key] !== undefined && values[key] !== null && values[key] !== "") return;
|
if (values[key] !== undefined && values[key] !== null && values[key] !== "") return;
|
||||||
// 1. 先尝试从当前合同详情直接取(同名字段)
|
// 1. 先尝试从当前合同详情直接取(同名字段)
|
||||||
@@ -1218,7 +1354,7 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
.contract-template-layout {
|
.contract-template-layout {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 460px minmax(0, 1fr);
|
grid-template-columns: 560px minmax(0, 1fr);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
@@ -1274,6 +1410,15 @@ onBeforeUnmount(() => {
|
|||||||
gap: 14px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grid-span-2 {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-form :deep(.ant-select),
|
||||||
|
.config-form :deep(.ant-input) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.template-tip {
|
.template-tip {
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
|||||||
@@ -0,0 +1,857 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="visible"
|
||||||
|
:title="modalTitle"
|
||||||
|
width="96%"
|
||||||
|
:footer="null"
|
||||||
|
centered
|
||||||
|
:mask-closable="false"
|
||||||
|
:destroy-on-close="false"
|
||||||
|
wrap-class-name="seal-position-config-wrap"
|
||||||
|
:bodyStyle="{ padding: 0, height: 'calc(100vh - 180px)', overflow: 'hidden' }"
|
||||||
|
>
|
||||||
|
<div class="seal-position-config-layout">
|
||||||
|
<div ref="previewPaneRef" class="preview-pane">
|
||||||
|
<div class="preview-toolbar">
|
||||||
|
<a-space wrap>
|
||||||
|
<a-tag color="blue">当前页:第 {{ currentPageNum }} 页</a-tag>
|
||||||
|
<a-tag>签章位 {{ positions.length }} 个</a-tag>
|
||||||
|
<a-button type="primary" :disabled="!pdfImages.length" @click="handleAddPosition">
|
||||||
|
添加签章位
|
||||||
|
</a-button>
|
||||||
|
<a-button :disabled="!currentPosition" @click="handleDeleteCurrent">
|
||||||
|
删除当前框
|
||||||
|
</a-button>
|
||||||
|
<a-button :loading="loading" @click="loadPageData(true)">刷新预览</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="preview-scroll">
|
||||||
|
<div
|
||||||
|
v-for="(imgUrl, pageIndex) in pdfImages"
|
||||||
|
:key="`page_${pageIndex}`"
|
||||||
|
class="pdf-page-card"
|
||||||
|
:class="{ 'is-active': currentPageNum === pageIndex + 1 }"
|
||||||
|
@click="currentPageNum = pageIndex + 1"
|
||||||
|
>
|
||||||
|
<div class="page-card-header">
|
||||||
|
<span>第 {{ pageIndex + 1 }} 页</span>
|
||||||
|
<a-tag v-if="currentPageNum === pageIndex + 1" color="processing">当前页</a-tag>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="pdf-page-shell"
|
||||||
|
:style="{ width: `${pageDisplayWidth}px`, height: `${pageDisplayHeight}px` }"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="imgUrl"
|
||||||
|
class="pdf-page-image"
|
||||||
|
:style="{ width: `${pageDisplayWidth}px`, height: `${pageDisplayHeight}px` }"
|
||||||
|
draggable="false"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="box in getPagePositions(pageIndex + 1)"
|
||||||
|
:key="box.localId"
|
||||||
|
class="seal-position-box"
|
||||||
|
:class="{ 'is-selected': selectedLocalId === box.localId }"
|
||||||
|
:style="getBoxStyle(box)"
|
||||||
|
@click.stop="handleSelectBox(box.localId, box.pageNum)"
|
||||||
|
@mousedown.stop.prevent="startDragBox($event, box)"
|
||||||
|
>
|
||||||
|
<div class="box-label-row">
|
||||||
|
<span class="box-label">{{ box.label || '未命名签章位' }}</span>
|
||||||
|
<span v-if="box.required === 'Y'" class="box-required">必签</span>
|
||||||
|
</div>
|
||||||
|
<div class="box-party">{{ getPartyLabel(box.partyId) }}</div>
|
||||||
|
<div
|
||||||
|
class="resize-handle"
|
||||||
|
title="拖动调整大小"
|
||||||
|
@mousedown.stop.prevent="startResizeBox($event, box)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-empty
|
||||||
|
v-if="!loading && !pdfImages.length"
|
||||||
|
class="empty-preview"
|
||||||
|
description="暂无签署文件预览,请先确认水印文件是否已上传"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="config-pane">
|
||||||
|
<div class="config-pane-scroll">
|
||||||
|
<div class="side-card">
|
||||||
|
<div class="side-card-title">合同信息</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>合同名称</span>
|
||||||
|
<strong>{{ contractDetail?.contractName || '-' }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>合同状态</span>
|
||||||
|
<strong>{{ contractDetail?.status || '-' }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>签章位配置</span>
|
||||||
|
<strong>{{ positions.length ? '已配置' : '未配置' }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="meta-note">
|
||||||
|
当前方案采用“当前合同单独配框”。框的位置以后会直接用于在线签订时的吸附和未签提醒。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="side-card">
|
||||||
|
<div class="side-card-title">签章位属性</div>
|
||||||
|
<template v-if="currentPosition">
|
||||||
|
<a-form layout="vertical">
|
||||||
|
<a-form-item label="标签">
|
||||||
|
<a-input
|
||||||
|
v-model:value="currentPosition.label"
|
||||||
|
allow-clear
|
||||||
|
placeholder="例如:甲方公章位"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="签署方">
|
||||||
|
<a-select
|
||||||
|
v-model:value="currentPosition.partyId"
|
||||||
|
:options="partyOptions"
|
||||||
|
placeholder="请选择签署方"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="是否必签">
|
||||||
|
<a-switch
|
||||||
|
:checked="currentPosition.required === 'Y'"
|
||||||
|
checked-children="必签"
|
||||||
|
un-checked-children="可选"
|
||||||
|
@change="(checked) => handleRequiredChange(currentPosition, checked)"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="所在页">
|
||||||
|
<a-select
|
||||||
|
v-model:value="currentPosition.pageNum"
|
||||||
|
:options="pageOptions"
|
||||||
|
placeholder="请选择页码"
|
||||||
|
@change="(value) => handleBoxPageChange(currentPosition, value)"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<div class="coordinate-grid">
|
||||||
|
<a-form-item label="X 坐标">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="currentPosition.x"
|
||||||
|
:min="0"
|
||||||
|
:step="1"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="normalizeBox(currentPosition)"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Y 坐标">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="currentPosition.y"
|
||||||
|
:min="0"
|
||||||
|
:step="1"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="normalizeBox(currentPosition)"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="宽度">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="currentPosition.width"
|
||||||
|
:min="60"
|
||||||
|
:step="5"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="normalizeBox(currentPosition)"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="高度">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="currentPosition.height"
|
||||||
|
:min="60"
|
||||||
|
:step="5"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="normalizeBox(currentPosition)"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</div>
|
||||||
|
</a-form>
|
||||||
|
</template>
|
||||||
|
<a-empty v-else description="请先新增并选中一个签章位" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="side-card side-card-grow">
|
||||||
|
<div class="side-card-title">签章位列表({{ sortedPositions.length }})</div>
|
||||||
|
<div class="position-list">
|
||||||
|
<a-empty
|
||||||
|
v-if="!sortedPositions.length"
|
||||||
|
class="position-list-empty"
|
||||||
|
description="暂无签章位,请先点击左侧“添加签章位”"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-for="box in sortedPositions"
|
||||||
|
:key="box.localId"
|
||||||
|
class="position-list-item"
|
||||||
|
:class="{ 'is-selected': selectedLocalId === box.localId }"
|
||||||
|
@click="handleSelectBox(box.localId, box.pageNum)"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div class="position-item-title">{{ box.label || '未命名签章位' }}</div>
|
||||||
|
<div class="position-item-sub">
|
||||||
|
第 {{ box.pageNum }} 页 · {{ getPartyLabel(box.partyId) }} · {{ box.required === 'Y' ? '必签' : '可选' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a-button type="link" danger size="small" @click.stop="handleDeletePosition(box.localId)">
|
||||||
|
删除
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer-actions">
|
||||||
|
<a-space>
|
||||||
|
<a-button @click="visible = false">取消</a-button>
|
||||||
|
<a-button type="primary" :loading="saving" @click="handleSave">保存签章位</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
import { initLaunchSealPosition, saveLaunchSealPositions } from '@/api/launch'
|
||||||
|
|
||||||
|
const emit = defineEmits(['saved'])
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
const saving = ref(false)
|
||||||
|
const previewPaneRef = ref(null)
|
||||||
|
const contractDetail = ref(null)
|
||||||
|
const pageData = ref(null)
|
||||||
|
const positions = ref([])
|
||||||
|
const selectedLocalId = ref('')
|
||||||
|
const currentPageNum = ref(1)
|
||||||
|
const pageDisplayWidth = ref(760)
|
||||||
|
|
||||||
|
const DEFAULT_IMAGE_WIDTH = 793
|
||||||
|
const DEFAULT_IMAGE_HEIGHT = 1122
|
||||||
|
const DEFAULT_BOX_WIDTH = 160
|
||||||
|
const DEFAULT_BOX_HEIGHT = 160
|
||||||
|
const MIN_BOX_SIZE = 60
|
||||||
|
let localSeed = 0
|
||||||
|
|
||||||
|
const modalTitle = computed(() => {
|
||||||
|
const name = contractDetail.value?.contractName || '合同'
|
||||||
|
return `配置签章位 - ${name}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const pdfImages = computed(() => normalizePdfImages(pageData.value?.pdfToImageList))
|
||||||
|
const imageWidth = computed(() => Number(pageData.value?.imageWidth || DEFAULT_IMAGE_WIDTH))
|
||||||
|
const imageHeight = computed(() => Number(pageData.value?.imageHeight || DEFAULT_IMAGE_HEIGHT))
|
||||||
|
const scaleRatio = computed(() => pageDisplayWidth.value / imageWidth.value)
|
||||||
|
const pageDisplayHeight = computed(() => Math.round(imageHeight.value * scaleRatio.value))
|
||||||
|
const currentPosition = computed(() => positions.value.find((item) => item.localId === selectedLocalId.value) || null)
|
||||||
|
|
||||||
|
const sortedPositions = computed(() =>
|
||||||
|
[...positions.value].sort((a, b) => {
|
||||||
|
if (a.pageNum !== b.pageNum) {
|
||||||
|
return Number(a.pageNum || 0) - Number(b.pageNum || 0)
|
||||||
|
}
|
||||||
|
return String(a.localId).localeCompare(String(b.localId))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const partyOptions = computed(() =>
|
||||||
|
(contractDetail.value?.partyList || []).map((item) => ({
|
||||||
|
label: `${item.roleName || item.roleCode || '签署方'} - ${item.companyName || item.subjectCode || item.id}`,
|
||||||
|
value: item.id,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|
||||||
|
const pageOptions = computed(() =>
|
||||||
|
pdfImages.value.map((_, index) => ({
|
||||||
|
label: `第 ${index + 1} 页`,
|
||||||
|
value: index + 1,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|
||||||
|
function isSuccessResponse(res) {
|
||||||
|
return String(res?.code || '') === '0000'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getResult(res) {
|
||||||
|
return res?.result || null
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePdfImages(list = []) {
|
||||||
|
return (Array.isArray(list) ? list : [])
|
||||||
|
.map((item) => {
|
||||||
|
if (typeof item === 'string') return item
|
||||||
|
if (!item || typeof item !== 'object') return ''
|
||||||
|
return item.url || item.imageUrl || item.previewUrl || item.fileUrl || item.path || ''
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createLocalId(id) {
|
||||||
|
if (id != null) {
|
||||||
|
return `db_${id}`
|
||||||
|
}
|
||||||
|
localSeed += 1
|
||||||
|
return `tmp_${localSeed}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetState() {
|
||||||
|
loading.value = false
|
||||||
|
saving.value = false
|
||||||
|
pageData.value = null
|
||||||
|
positions.value = []
|
||||||
|
selectedLocalId.value = ''
|
||||||
|
currentPageNum.value = 1
|
||||||
|
pageDisplayWidth.value = 760
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildLocalPositions(page) {
|
||||||
|
const rawPositions = Array.isArray(page?.positions) ? page.positions : []
|
||||||
|
return rawPositions.map((item) => ({
|
||||||
|
...item,
|
||||||
|
localId: createLocalId(item.id),
|
||||||
|
required: item.required === 'N' ? 'N' : 'Y',
|
||||||
|
pageNum: Number(item.pageNum || 1),
|
||||||
|
x: Number(item.x || 0),
|
||||||
|
y: Number(item.y || 0),
|
||||||
|
width: Number(item.width || DEFAULT_BOX_WIDTH),
|
||||||
|
height: Number(item.height || DEFAULT_BOX_HEIGHT),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshPreviewSize() {
|
||||||
|
nextTick(() => {
|
||||||
|
const paneWidth = previewPaneRef.value?.clientWidth || 980
|
||||||
|
const availableWidth = Math.max(360, paneWidth - 48)
|
||||||
|
pageDisplayWidth.value = Math.min(imageWidth.value, availableWidth)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRequiredChange(box, checked) {
|
||||||
|
box.required = checked ? 'Y' : 'N'
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelectBox(localId, pageNum) {
|
||||||
|
selectedLocalId.value = localId
|
||||||
|
currentPageNum.value = Number(pageNum || 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPagePositions(pageNum) {
|
||||||
|
return positions.value.filter((item) => Number(item.pageNum) === Number(pageNum))
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBoxStyle(box) {
|
||||||
|
return {
|
||||||
|
left: `${Number(box.x || 0) * scaleRatio.value}px`,
|
||||||
|
top: `${Number(box.y || 0) * scaleRatio.value}px`,
|
||||||
|
width: `${Number(box.width || DEFAULT_BOX_WIDTH) * scaleRatio.value}px`,
|
||||||
|
height: `${Number(box.height || DEFAULT_BOX_HEIGHT) * scaleRatio.value}px`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPartyLabel(partyId) {
|
||||||
|
const party = (contractDetail.value?.partyList || []).find((item) => Number(item.id) === Number(partyId))
|
||||||
|
if (!party) {
|
||||||
|
return '未绑定签署方'
|
||||||
|
}
|
||||||
|
return `${party.roleName || party.roleCode || '签署方'} - ${party.companyName || party.subjectCode || party.id}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function clampValue(value, min, max) {
|
||||||
|
const numericValue = Number(value)
|
||||||
|
if (Number.isNaN(numericValue)) return min
|
||||||
|
return Math.min(Math.max(numericValue, min), max)
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeBox(box) {
|
||||||
|
if (!box) return
|
||||||
|
box.pageNum = clampValue(box.pageNum || 1, 1, Math.max(pdfImages.value.length, 1))
|
||||||
|
box.width = clampValue(box.width || DEFAULT_BOX_WIDTH, MIN_BOX_SIZE, imageWidth.value)
|
||||||
|
box.height = clampValue(box.height || DEFAULT_BOX_HEIGHT, MIN_BOX_SIZE, imageHeight.value)
|
||||||
|
box.x = clampValue(box.x || 0, 0, Math.max(0, imageWidth.value - box.width))
|
||||||
|
box.y = clampValue(box.y || 0, 0, Math.max(0, imageHeight.value - box.height))
|
||||||
|
box.x = Math.round(box.x * 100) / 100
|
||||||
|
box.y = Math.round(box.y * 100) / 100
|
||||||
|
box.width = Math.round(box.width * 100) / 100
|
||||||
|
box.height = Math.round(box.height * 100) / 100
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBoxPageChange(box, pageNum) {
|
||||||
|
box.pageNum = Number(pageNum || 1)
|
||||||
|
currentPageNum.value = box.pageNum
|
||||||
|
normalizeBox(box)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAddPosition() {
|
||||||
|
if (!pdfImages.value.length) {
|
||||||
|
message.warning('暂无可配置的签署文件预览')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const partyId = currentPosition.value?.partyId ?? partyOptions.value[0]?.value
|
||||||
|
const box = {
|
||||||
|
id: null,
|
||||||
|
localId: createLocalId(null),
|
||||||
|
partyId,
|
||||||
|
label: `签章位${positions.value.length + 1}`,
|
||||||
|
required: 'Y',
|
||||||
|
pageNum: currentPageNum.value,
|
||||||
|
x: Math.max(0, Math.round((imageWidth.value - DEFAULT_BOX_WIDTH) / 2)),
|
||||||
|
y: Math.max(0, Math.round((imageHeight.value - DEFAULT_BOX_HEIGHT) / 2)),
|
||||||
|
width: DEFAULT_BOX_WIDTH,
|
||||||
|
height: DEFAULT_BOX_HEIGHT,
|
||||||
|
}
|
||||||
|
positions.value.push(box)
|
||||||
|
handleSelectBox(box.localId, box.pageNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDeletePosition(localId) {
|
||||||
|
positions.value = positions.value.filter((item) => item.localId !== localId)
|
||||||
|
if (selectedLocalId.value === localId) {
|
||||||
|
const nextBox = positions.value[0] || null
|
||||||
|
selectedLocalId.value = nextBox?.localId || ''
|
||||||
|
currentPageNum.value = Number(nextBox?.pageNum || currentPageNum.value || 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDeleteCurrent() {
|
||||||
|
if (!currentPosition.value) return
|
||||||
|
handleDeletePosition(currentPosition.value.localId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function startDragBox(event, box) {
|
||||||
|
const startX = event.clientX
|
||||||
|
const startY = event.clientY
|
||||||
|
const originX = Number(box.x || 0)
|
||||||
|
const originY = Number(box.y || 0)
|
||||||
|
|
||||||
|
const onMouseMove = (moveEvent) => {
|
||||||
|
const deltaX = (moveEvent.clientX - startX) / scaleRatio.value
|
||||||
|
const deltaY = (moveEvent.clientY - startY) / scaleRatio.value
|
||||||
|
box.x = originX + deltaX
|
||||||
|
box.y = originY + deltaY
|
||||||
|
normalizeBox(box)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMouseUp = () => {
|
||||||
|
document.removeEventListener('mousemove', onMouseMove)
|
||||||
|
document.removeEventListener('mouseup', onMouseUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', onMouseMove)
|
||||||
|
document.addEventListener('mouseup', onMouseUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
function startResizeBox(event, box) {
|
||||||
|
const startX = event.clientX
|
||||||
|
const startY = event.clientY
|
||||||
|
const originWidth = Number(box.width || DEFAULT_BOX_WIDTH)
|
||||||
|
const originHeight = Number(box.height || DEFAULT_BOX_HEIGHT)
|
||||||
|
|
||||||
|
const onMouseMove = (moveEvent) => {
|
||||||
|
const deltaX = (moveEvent.clientX - startX) / scaleRatio.value
|
||||||
|
const deltaY = (moveEvent.clientY - startY) / scaleRatio.value
|
||||||
|
box.width = originWidth + deltaX
|
||||||
|
box.height = originHeight + deltaY
|
||||||
|
normalizeBox(box)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMouseUp = () => {
|
||||||
|
document.removeEventListener('mousemove', onMouseMove)
|
||||||
|
document.removeEventListener('mouseup', onMouseUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', onMouseMove)
|
||||||
|
document.addEventListener('mouseup', onMouseUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadPageData(showSuccessTip = false) {
|
||||||
|
if (!contractDetail.value?.id) return
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await initLaunchSealPosition(contractDetail.value.id)
|
||||||
|
if (!isSuccessResponse(res) || !getResult(res)) {
|
||||||
|
message.error(res?.message || res?.msg || '初始化签署文件失败')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const page = getResult(res)
|
||||||
|
pageData.value = page
|
||||||
|
positions.value = buildLocalPositions(page)
|
||||||
|
if (positions.value.length > 0) {
|
||||||
|
handleSelectBox(positions.value[0].localId, positions.value[0].pageNum)
|
||||||
|
} else {
|
||||||
|
selectedLocalId.value = ''
|
||||||
|
currentPageNum.value = 1
|
||||||
|
}
|
||||||
|
refreshPreviewSize()
|
||||||
|
if (showSuccessTip) {
|
||||||
|
message.success('签署文件预览已刷新')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error?.message || '初始化签署文件失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validatePositions() {
|
||||||
|
if (!positions.value.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const invalid = positions.value.find(
|
||||||
|
(item) => !item.partyId || !String(item.label || '').trim() || !item.pageNum,
|
||||||
|
)
|
||||||
|
if (invalid) {
|
||||||
|
handleSelectBox(invalid.localId, invalid.pageNum)
|
||||||
|
return '请完整填写签章位标签、签署方和页码'
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSave() {
|
||||||
|
const validationMessage = validatePositions()
|
||||||
|
if (validationMessage) {
|
||||||
|
message.warning(validationMessage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
saving.value = true
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
positions: positions.value.map((item) => {
|
||||||
|
normalizeBox(item)
|
||||||
|
return {
|
||||||
|
partyId: item.partyId,
|
||||||
|
label: String(item.label || '').trim(),
|
||||||
|
required: item.required === 'N' ? 'N' : 'Y',
|
||||||
|
pageNum: Number(item.pageNum || 1),
|
||||||
|
x: Number(item.x || 0),
|
||||||
|
y: Number(item.y || 0),
|
||||||
|
width: Number(item.width || DEFAULT_BOX_WIDTH),
|
||||||
|
height: Number(item.height || DEFAULT_BOX_HEIGHT),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
const res = await saveLaunchSealPositions(contractDetail.value.id, payload)
|
||||||
|
if (!isSuccessResponse(res) || !getResult(res)) {
|
||||||
|
message.error(res?.message || res?.msg || '保存签章位失败')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pageData.value = getResult(res)
|
||||||
|
positions.value = buildLocalPositions(pageData.value)
|
||||||
|
if (positions.value.length > 0) {
|
||||||
|
handleSelectBox(positions.value[0].localId, positions.value[0].pageNum)
|
||||||
|
}
|
||||||
|
emit('saved', pageData.value)
|
||||||
|
message.success('签章位已保存')
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error?.message || '保存签章位失败')
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showModal(detail) {
|
||||||
|
resetState()
|
||||||
|
contractDetail.value = detail ? { ...detail } : null
|
||||||
|
visible.value = true
|
||||||
|
await nextTick()
|
||||||
|
refreshPreviewSize()
|
||||||
|
await loadPageData(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWindowResize() {
|
||||||
|
if (visible.value) {
|
||||||
|
refreshPreviewSize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('resize', handleWindowResize)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('resize', handleWindowResize)
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({ showModal })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.seal-position-config-layout {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
background: #f4f7fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-pane {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-right: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-toolbar {
|
||||||
|
padding: 14px 18px;
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-scroll {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-page-card {
|
||||||
|
margin: 0 auto 18px;
|
||||||
|
padding: 14px;
|
||||||
|
border-radius: 18px;
|
||||||
|
border: 1px solid #dbe4f0;
|
||||||
|
background: #ffffff;
|
||||||
|
width: fit-content;
|
||||||
|
box-shadow: 0 12px 28px rgba(15, 23, 42, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-page-card.is-active {
|
||||||
|
border-color: #60a5fa;
|
||||||
|
box-shadow: 0 16px 30px rgba(37, 99, 235, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #475569;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-page-shell {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-page-image {
|
||||||
|
display: block;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seal-position-box {
|
||||||
|
position: absolute;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 2px dashed #2563eb;
|
||||||
|
background: rgba(37, 99, 235, 0.12);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 8px;
|
||||||
|
color: #1d4ed8;
|
||||||
|
cursor: move;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seal-position-box.is-selected {
|
||||||
|
border-color: #ef4444;
|
||||||
|
background: rgba(239, 68, 68, 0.14);
|
||||||
|
color: #b91c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-label-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-label {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-required {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-party {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: inherit;
|
||||||
|
opacity: 0.92;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle {
|
||||||
|
position: absolute;
|
||||||
|
right: 4px;
|
||||||
|
bottom: 4px;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: currentColor;
|
||||||
|
cursor: nwse-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-pane {
|
||||||
|
width: 380px;
|
||||||
|
flex: 0 0 380px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 16px;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-x: hidden;
|
||||||
|
background: linear-gradient(180deg, #fbfdff 0%, #f5f8fc 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-pane-scroll {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
padding-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-card {
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
background: rgba(255, 255, 255, 0.96);
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-card-grow {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 220px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-card-title {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-item strong {
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
color: #0f172a;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-note {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #eff6ff;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coordinate-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.position-list {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 160px;
|
||||||
|
overflow: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.position-list-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid #dbe4f0;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.position-list-item:hover,
|
||||||
|
.position-list-item.is-selected {
|
||||||
|
border-color: #60a5fa;
|
||||||
|
background: #eff6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.position-item-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.position-item-sub {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.position-list-empty {
|
||||||
|
margin: auto 0;
|
||||||
|
padding: 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-top: 12px;
|
||||||
|
padding-top: 12px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
background: linear-gradient(180deg, rgba(245, 248, 252, 0) 0%, rgba(245, 248, 252, 0.96) 24%, rgba(245, 248, 252, 1) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-preview {
|
||||||
|
margin-top: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
.seal-position-config-layout {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-pane {
|
||||||
|
width: 100%;
|
||||||
|
flex-basis: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,991 @@
|
|||||||
|
<template>
|
||||||
|
<div class="template-rich-editor" :class="{ 'is-disabled': disabled }">
|
||||||
|
<div class="editor-toolbar">
|
||||||
|
<div class="toolbar-groups">
|
||||||
|
<div v-for="(group, groupIndex) in toolbarGroups" :key="groupIndex" class="toolbar-group">
|
||||||
|
<button
|
||||||
|
v-for="action in group"
|
||||||
|
:key="action.key"
|
||||||
|
type="button"
|
||||||
|
class="toolbar-button"
|
||||||
|
:disabled="disabled"
|
||||||
|
@mousedown.prevent
|
||||||
|
@click="handleToolbarAction(action)"
|
||||||
|
>
|
||||||
|
{{ action.label }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="toolbar-button toolbar-button-primary"
|
||||||
|
@mousedown.prevent
|
||||||
|
@click="openFullscreen"
|
||||||
|
>
|
||||||
|
{{ disabled ? '宽屏查看' : '宽屏编辑' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="editor-content-shell">
|
||||||
|
<div class="editor-scroll">
|
||||||
|
<div
|
||||||
|
ref="inlineEditorRef"
|
||||||
|
class="editor-surface"
|
||||||
|
:class="{ 'has-table': containsTable }"
|
||||||
|
:contenteditable="String(!disabled)"
|
||||||
|
:data-placeholder="placeholder"
|
||||||
|
@focus="setActiveSurface('inline')"
|
||||||
|
@mouseup="captureSelection('inline')"
|
||||||
|
@keyup="captureSelection('inline')"
|
||||||
|
@input="handleEditorInput('inline')"
|
||||||
|
@paste="handlePaste($event, 'inline')"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="editor-tip">
|
||||||
|
复制 Word 表格时会优先保留原始合并结构。表格较宽或包含合并单元格时,使用“宽屏编辑”可避免内容被压缩成竖排。
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-modal
|
||||||
|
:open="isFullscreen"
|
||||||
|
:title="disabled ? '富文本宽屏查看' : '富文本宽屏编辑'"
|
||||||
|
width="96vw"
|
||||||
|
:footer="null"
|
||||||
|
centered
|
||||||
|
:mask-closable="false"
|
||||||
|
:body-style="{ padding: '0', height: '82vh', overflow: 'hidden' }"
|
||||||
|
@update:open="handleFullscreenVisibleChange"
|
||||||
|
>
|
||||||
|
<div class="fullscreen-layout">
|
||||||
|
<div class="editor-toolbar fullscreen-toolbar">
|
||||||
|
<div class="toolbar-groups">
|
||||||
|
<div v-for="(group, groupIndex) in toolbarGroups" :key="`fullscreen-${groupIndex}`" class="toolbar-group">
|
||||||
|
<button
|
||||||
|
v-for="action in group"
|
||||||
|
:key="`fullscreen-${action.key}`"
|
||||||
|
type="button"
|
||||||
|
class="toolbar-button"
|
||||||
|
:disabled="disabled"
|
||||||
|
@mousedown.prevent
|
||||||
|
@click="handleToolbarAction(action, 'fullscreen')"
|
||||||
|
>
|
||||||
|
{{ action.label }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="toolbar-button toolbar-button-primary"
|
||||||
|
@mousedown.prevent
|
||||||
|
@click="isFullscreen = false"
|
||||||
|
>
|
||||||
|
完成
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="fullscreen-body">
|
||||||
|
<div class="editor-scroll fullscreen-scroll">
|
||||||
|
<div
|
||||||
|
ref="fullscreenEditorRef"
|
||||||
|
class="editor-surface fullscreen-surface"
|
||||||
|
:class="{ 'has-table': containsTable }"
|
||||||
|
:contenteditable="String(!disabled)"
|
||||||
|
:data-placeholder="placeholder"
|
||||||
|
@focus="setActiveSurface('fullscreen')"
|
||||||
|
@mouseup="captureSelection('fullscreen')"
|
||||||
|
@keyup="captureSelection('fullscreen')"
|
||||||
|
@input="handleEditorInput('fullscreen')"
|
||||||
|
@paste="handlePaste($event, 'fullscreen')"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, nextTick, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '请输入内容,支持插入表格',
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const inlineEditorRef = ref(null)
|
||||||
|
const fullscreenEditorRef = ref(null)
|
||||||
|
const isFullscreen = ref(false)
|
||||||
|
const activeSurface = ref('inline')
|
||||||
|
const currentHtml = ref('')
|
||||||
|
|
||||||
|
const TABLE_STYLE_PROPS = [
|
||||||
|
'width',
|
||||||
|
'min-width',
|
||||||
|
'max-width',
|
||||||
|
'text-align',
|
||||||
|
'vertical-align',
|
||||||
|
'background-color',
|
||||||
|
'color',
|
||||||
|
'font-weight',
|
||||||
|
'font-style',
|
||||||
|
'text-decoration',
|
||||||
|
'white-space',
|
||||||
|
'border',
|
||||||
|
'border-top',
|
||||||
|
'border-right',
|
||||||
|
'border-bottom',
|
||||||
|
'border-left',
|
||||||
|
'border-collapse',
|
||||||
|
'table-layout',
|
||||||
|
]
|
||||||
|
|
||||||
|
const INLINE_STYLE_PROPS = [
|
||||||
|
'text-align',
|
||||||
|
'background-color',
|
||||||
|
'color',
|
||||||
|
'font-weight',
|
||||||
|
'font-style',
|
||||||
|
'text-decoration',
|
||||||
|
'white-space',
|
||||||
|
]
|
||||||
|
|
||||||
|
const toolbarGroups = [
|
||||||
|
[
|
||||||
|
{ key: 'bold', label: '加粗', command: 'bold' },
|
||||||
|
{ key: 'italic', label: '斜体', command: 'italic' },
|
||||||
|
{ key: 'underline', label: '下划线', command: 'underline' },
|
||||||
|
{ key: 'removeFormat', label: '清除格式', command: 'removeFormat' },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ key: 'justifyLeft', label: '左对齐', command: 'justifyLeft' },
|
||||||
|
{ key: 'justifyCenter', label: '居中', command: 'justifyCenter' },
|
||||||
|
{ key: 'justifyRight', label: '右对齐', command: 'justifyRight' },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ key: 'insertUnorderedList', label: '无序列表', command: 'insertUnorderedList' },
|
||||||
|
{ key: 'insertOrderedList', label: '有序列表', command: 'insertOrderedList' },
|
||||||
|
],
|
||||||
|
// [
|
||||||
|
// { key: 'insertTable', label: '插入表格', type: 'insertTable' },
|
||||||
|
// { key: 'undo', label: '撤销', command: 'undo' },
|
||||||
|
// { key: 'redo', label: '重做', command: 'redo' },
|
||||||
|
// ],
|
||||||
|
]
|
||||||
|
|
||||||
|
const savedRanges = {
|
||||||
|
inline: null,
|
||||||
|
fullscreen: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
const containsTable = computed(() => /<table[\s>]/i.test(currentHtml.value))
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(value) => {
|
||||||
|
const nextHtml = String(value || '')
|
||||||
|
if (nextHtml === currentHtml.value) {
|
||||||
|
syncEditorHtml()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
currentHtml.value = nextHtml
|
||||||
|
syncEditorHtml()
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(isFullscreen, async (value) => {
|
||||||
|
if (!value) {
|
||||||
|
activeSurface.value = 'inline'
|
||||||
|
await nextTick()
|
||||||
|
syncEditorHtml()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await nextTick()
|
||||||
|
syncEditorHtml()
|
||||||
|
if (!props.disabled) {
|
||||||
|
focusSurface('fullscreen', true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function getEditorElement(surface = activeSurface.value) {
|
||||||
|
return surface === 'fullscreen' ? fullscreenEditorRef.value : inlineEditorRef.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncEditorHtml(skipSurface = '') {
|
||||||
|
syncSurfaceHtml('inline', skipSurface)
|
||||||
|
syncSurfaceHtml('fullscreen', skipSurface)
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncSurfaceHtml(surface, skipSurface = '') {
|
||||||
|
if (surface === skipSurface) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const editor = getEditorElement(surface)
|
||||||
|
if (!editor) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const html = currentHtml.value || ''
|
||||||
|
if (editor.innerHTML !== html) {
|
||||||
|
editor.innerHTML = html
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setActiveSurface(surface) {
|
||||||
|
activeSurface.value = surface
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusSurface(surface, placeAtEnd = false) {
|
||||||
|
const editor = getEditorElement(surface)
|
||||||
|
if (!editor) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
editor.focus()
|
||||||
|
if (placeAtEnd) {
|
||||||
|
placeCaretAtEnd(editor)
|
||||||
|
captureSelection(surface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function placeCaretAtEnd(editor) {
|
||||||
|
if (!editor) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (!selection) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const range = document.createRange()
|
||||||
|
range.selectNodeContents(editor)
|
||||||
|
range.collapse(false)
|
||||||
|
selection.removeAllRanges()
|
||||||
|
selection.addRange(range)
|
||||||
|
}
|
||||||
|
|
||||||
|
function captureSelection(surface = activeSurface.value) {
|
||||||
|
const editor = getEditorElement(surface)
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (!editor || !selection || !selection.rangeCount) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const range = selection.getRangeAt(0)
|
||||||
|
if (!editor.contains(range.commonAncestorContainer)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
savedRanges[surface] = range.cloneRange()
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreSelection(surface = activeSurface.value) {
|
||||||
|
const editor = getEditorElement(surface)
|
||||||
|
if (!editor) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
editor.focus()
|
||||||
|
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (!selection) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const range = savedRanges[surface]
|
||||||
|
selection.removeAllRanges()
|
||||||
|
if (range && editor.contains(range.commonAncestorContainer)) {
|
||||||
|
selection.addRange(range)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
placeCaretAtEnd(editor)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEditorInput(surface) {
|
||||||
|
const editor = getEditorElement(surface)
|
||||||
|
if (!editor) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
activeSurface.value = surface
|
||||||
|
currentHtml.value = normalizeEditableHtml(editor.innerHTML)
|
||||||
|
syncEditorHtml(surface)
|
||||||
|
emit('update:modelValue', currentHtml.value)
|
||||||
|
captureSelection(surface)
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeEditableHtml(html) {
|
||||||
|
const normalized = String(html || '')
|
||||||
|
.replace(/<div><br\s*\/?><\/div>/gi, '')
|
||||||
|
.replace(/<p><br\s*\/?><\/p>/gi, '')
|
||||||
|
.trim()
|
||||||
|
return hasMeaningfulHtml(normalized) ? normalized : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasMeaningfulHtml(html) {
|
||||||
|
if (!html) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (/<(table|img|hr|video|audio)[\s>]/i.test(html)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return normalizeTextContent(html) !== ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeTextContent(html) {
|
||||||
|
return String(html || '')
|
||||||
|
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
|
||||||
|
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
|
||||||
|
.replace(/<[^>]+>/g, ' ')
|
||||||
|
.replace(/ /gi, ' ')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToolbarAction(action, targetSurface = '') {
|
||||||
|
if (!action) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (action.type === 'insertTable') {
|
||||||
|
insertDefaultTable(targetSurface)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (['justifyLeft', 'justifyCenter', 'justifyRight'].includes(action.command)) {
|
||||||
|
applyTextAlignment(action.command, targetSurface)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
runDocumentCommand(action.command, action.value, targetSurface)
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveTargetSurface(targetSurface = '') {
|
||||||
|
if (targetSurface) {
|
||||||
|
return targetSurface
|
||||||
|
}
|
||||||
|
return isFullscreen.value ? 'fullscreen' : activeSurface.value || 'inline'
|
||||||
|
}
|
||||||
|
|
||||||
|
function runDocumentCommand(command, value = null, targetSurface = '') {
|
||||||
|
if (props.disabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const surface = resolveTargetSurface(targetSurface)
|
||||||
|
activeSurface.value = surface
|
||||||
|
restoreSelection(surface)
|
||||||
|
document.execCommand(command, false, value)
|
||||||
|
captureSelection(surface)
|
||||||
|
handleEditorInput(surface)
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertDefaultTable(targetSurface = '') {
|
||||||
|
if (props.disabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const html = buildDefaultTableHtml(4, 4)
|
||||||
|
insertHtmlAtCursor(html, targetSurface)
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertHtmlAtCursor(html, targetSurface = '') {
|
||||||
|
const surface = resolveTargetSurface(targetSurface)
|
||||||
|
activeSurface.value = surface
|
||||||
|
restoreSelection(surface)
|
||||||
|
const inserted = document.execCommand('insertHTML', false, html)
|
||||||
|
if (!inserted) {
|
||||||
|
const editor = getEditorElement(surface)
|
||||||
|
insertHtmlByRange(editor, html)
|
||||||
|
}
|
||||||
|
if (/<table[\s>]/i.test(html)) {
|
||||||
|
focusFirstTableCell(surface)
|
||||||
|
}
|
||||||
|
captureSelection(surface)
|
||||||
|
handleEditorInput(surface)
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertHtmlByRange(editor, html) {
|
||||||
|
if (!editor) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (!selection || !selection.rangeCount) {
|
||||||
|
editor.insertAdjacentHTML('beforeend', html)
|
||||||
|
placeCaretAtEnd(editor)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const range = selection.getRangeAt(0)
|
||||||
|
if (!editor.contains(range.commonAncestorContainer)) {
|
||||||
|
editor.insertAdjacentHTML('beforeend', html)
|
||||||
|
placeCaretAtEnd(editor)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
range.deleteContents()
|
||||||
|
const fragment = range.createContextualFragment(html)
|
||||||
|
const lastNode = fragment.lastChild
|
||||||
|
range.insertNode(fragment)
|
||||||
|
|
||||||
|
if (lastNode) {
|
||||||
|
const nextRange = document.createRange()
|
||||||
|
nextRange.setStartAfter(lastNode)
|
||||||
|
nextRange.collapse(true)
|
||||||
|
selection.removeAllRanges()
|
||||||
|
selection.addRange(nextRange)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDefaultTableHtml(rows = 4, cols = 4) {
|
||||||
|
const minWidth = Math.max(640, cols * 140)
|
||||||
|
const rowHtml = Array.from({ length: rows }, () => {
|
||||||
|
const cellHtml = Array.from({ length: cols }, () => '<td> </td>').join('')
|
||||||
|
return `<tr>${cellHtml}</tr>`
|
||||||
|
}).join('')
|
||||||
|
return `<table style="width:100%;min-width:${minWidth}px;border-collapse:collapse;table-layout:auto;margin:8px 0;border:1px solid #1f2937;">${rowHtml}</table><p><br></p>`
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePaste(event, surface) {
|
||||||
|
if (props.disabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = event?.clipboardData?.getData('text/html') || ''
|
||||||
|
const plainText = event?.clipboardData?.getData('text/plain') || ''
|
||||||
|
const normalizedHtml = html ? normalizePastedHtml(html) : ''
|
||||||
|
const fallbackTableHtml = !normalizedHtml ? buildPlainTextTableHtml(plainText) : ''
|
||||||
|
|
||||||
|
if (!normalizedHtml && !fallbackTableHtml) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
activeSurface.value = surface
|
||||||
|
insertHtmlAtCursor(normalizedHtml || fallbackTableHtml, surface)
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePastedHtml(html) {
|
||||||
|
const parser = new window.DOMParser()
|
||||||
|
const doc = parser.parseFromString(`<html><body>${html}</body></html>`, 'text/html')
|
||||||
|
const body = doc.body
|
||||||
|
if (!body) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
body.querySelectorAll('script, style, meta, link, title').forEach((node) => node.remove())
|
||||||
|
body.querySelectorAll('table').forEach((table) => normalizePastedTable(table, doc))
|
||||||
|
body.querySelectorAll('*').forEach((node) => cleanupPastedNode(node))
|
||||||
|
|
||||||
|
return body.innerHTML.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePastedTable(table, doc) {
|
||||||
|
const normalizedTable = doc.createElement('table')
|
||||||
|
const columnCount = estimateTableColumnCount(table)
|
||||||
|
const tableWidth = normalizeTableWidth(table) || '100%'
|
||||||
|
normalizedTable.style.width = tableWidth
|
||||||
|
normalizedTable.style.minWidth = `${Math.max(640, columnCount * 140)}px`
|
||||||
|
normalizedTable.style.borderCollapse = 'collapse'
|
||||||
|
normalizedTable.style.tableLayout = 'auto'
|
||||||
|
normalizedTable.style.margin = '8px 0'
|
||||||
|
|
||||||
|
cloneNormalizedColGroups(table, normalizedTable, doc)
|
||||||
|
|
||||||
|
extractPastedTableRows(table).forEach((row) => {
|
||||||
|
const normalizedRow = doc.createElement('tr')
|
||||||
|
Array.from(row.children || []).forEach((cell) => {
|
||||||
|
const tag = String(cell.tagName || '').toLowerCase() === 'th' ? 'th' : 'td'
|
||||||
|
const normalizedCell = doc.createElement(tag)
|
||||||
|
copyTableSpanAttribute(cell, normalizedCell, 'rowspan')
|
||||||
|
copyTableSpanAttribute(cell, normalizedCell, 'colspan')
|
||||||
|
applyNormalizedCellStyle(cell, normalizedCell)
|
||||||
|
normalizedCell.innerHTML = sanitizeCellInnerHtml(cell.innerHTML)
|
||||||
|
normalizedRow.appendChild(normalizedCell)
|
||||||
|
})
|
||||||
|
normalizedTable.appendChild(normalizedRow)
|
||||||
|
})
|
||||||
|
|
||||||
|
table.replaceWith(normalizedTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneNormalizedColGroups(sourceTable, targetTable, doc) {
|
||||||
|
Array.from(sourceTable.querySelectorAll(':scope > colgroup')).forEach((group) => {
|
||||||
|
const normalizedGroup = doc.createElement('colgroup')
|
||||||
|
Array.from(group.children || []).forEach((child) => {
|
||||||
|
if (String(child.tagName || '').toLowerCase() !== 'col') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const normalizedCol = doc.createElement('col')
|
||||||
|
const width = normalizeSizeValue(child.style?.width || child.getAttribute('width') || '')
|
||||||
|
if (width) {
|
||||||
|
normalizedCol.style.width = width
|
||||||
|
}
|
||||||
|
normalizedGroup.appendChild(normalizedCol)
|
||||||
|
})
|
||||||
|
if (normalizedGroup.children.length) {
|
||||||
|
targetTable.appendChild(normalizedGroup)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function estimateTableColumnCount(table) {
|
||||||
|
return extractPastedTableRows(table).reduce((maxCount, row) => {
|
||||||
|
const columnCount = Array.from(row.children || []).reduce((sum, cell) => {
|
||||||
|
const colspan = Number.parseInt(String(cell.getAttribute('colspan') || '').trim(), 10)
|
||||||
|
return sum + (Number.isFinite(colspan) && colspan > 1 ? colspan : 1)
|
||||||
|
}, 0)
|
||||||
|
return Math.max(maxCount, columnCount)
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractPastedTableRows(table) {
|
||||||
|
const rows = Array.from(table.querySelectorAll(':scope > tr'))
|
||||||
|
if (rows.length) {
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
const sectionRows = ['thead', 'tbody', 'tfoot'].flatMap((tagName) =>
|
||||||
|
Array.from(table.querySelectorAll(`:scope > ${tagName} > tr`)),
|
||||||
|
)
|
||||||
|
if (sectionRows.length) {
|
||||||
|
return sectionRows
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(table.querySelectorAll('tr'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeTableWidth(table) {
|
||||||
|
const inlineWidth = table.style?.width || table.getAttribute('width') || ''
|
||||||
|
return normalizeSizeValue(inlineWidth) || '100%'
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSizeValue(value) {
|
||||||
|
const normalized = String(value || '').trim()
|
||||||
|
if (!normalized) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (/^\d+(\.\d+)?$/.test(normalized)) {
|
||||||
|
return `${normalized}px`
|
||||||
|
}
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyTableSpanAttribute(sourceCell, targetCell, attrName) {
|
||||||
|
const rawValue = sourceCell.getAttribute(attrName)
|
||||||
|
const spanValue = Number.parseInt(String(rawValue || '').trim(), 10)
|
||||||
|
if (Number.isFinite(spanValue) && spanValue > 1) {
|
||||||
|
targetCell.setAttribute(attrName, String(spanValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyNormalizedCellStyle(sourceCell, targetCell) {
|
||||||
|
const textAlign = sourceCell.style?.textAlign || sourceCell.getAttribute('align') || ''
|
||||||
|
const verticalAlign = sourceCell.style?.verticalAlign || sourceCell.getAttribute('valign') || ''
|
||||||
|
const backgroundColor = sourceCell.style?.backgroundColor || sourceCell.getAttribute('bgcolor') || ''
|
||||||
|
const width = normalizeSizeValue(sourceCell.style?.width || sourceCell.getAttribute('width') || '')
|
||||||
|
|
||||||
|
targetCell.style.border = '1px solid #1f2937'
|
||||||
|
targetCell.style.padding = '6px 10px'
|
||||||
|
targetCell.style.verticalAlign = normalizeVerticalAlign(verticalAlign) || 'middle'
|
||||||
|
targetCell.style.wordBreak = 'keep-all'
|
||||||
|
targetCell.style.overflowWrap = 'break-word'
|
||||||
|
targetCell.style.whiteSpace = 'pre-wrap'
|
||||||
|
targetCell.style.lineHeight = '1.7'
|
||||||
|
targetCell.style.minWidth = '120px'
|
||||||
|
|
||||||
|
if (textAlign) {
|
||||||
|
targetCell.style.textAlign = textAlign
|
||||||
|
}
|
||||||
|
if (backgroundColor) {
|
||||||
|
targetCell.style.backgroundColor = backgroundColor
|
||||||
|
}
|
||||||
|
if (width) {
|
||||||
|
targetCell.style.width = width
|
||||||
|
}
|
||||||
|
|
||||||
|
if (String(sourceCell.tagName || '').toLowerCase() === 'th') {
|
||||||
|
targetCell.style.fontWeight = '700'
|
||||||
|
targetCell.style.textAlign = targetCell.style.textAlign || 'center'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeCellInnerHtml(html) {
|
||||||
|
const parser = new window.DOMParser()
|
||||||
|
const doc = parser.parseFromString(`<div>${html || ''}</div>`, 'text/html')
|
||||||
|
const wrapper = doc.body.firstElementChild
|
||||||
|
if (!wrapper) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper.querySelectorAll('script, style, meta, link, title').forEach((node) => node.remove())
|
||||||
|
Array.from(wrapper.querySelectorAll('*')).forEach((node) => {
|
||||||
|
cleanupPastedNode(node)
|
||||||
|
if (String(node.tagName || '').toLowerCase() === 'table') {
|
||||||
|
normalizePastedTable(node, doc)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return wrapper.innerHTML
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanupPastedNode(node) {
|
||||||
|
if (!(node instanceof window.HTMLElement)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
node.removeAttribute('class')
|
||||||
|
node.removeAttribute('lang')
|
||||||
|
node.removeAttribute('dir')
|
||||||
|
node.removeAttribute('data-mce-style')
|
||||||
|
node.removeAttribute('data-mce-selected')
|
||||||
|
|
||||||
|
const tag = String(node.tagName || '').toLowerCase()
|
||||||
|
if (tag === 'table' || tag === 'td' || tag === 'th' || tag === 'tr' || tag === 'col' || tag === 'colgroup') {
|
||||||
|
retainInlineStyle(node, TABLE_STYLE_PROPS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
retainInlineStyle(node, INLINE_STYLE_PROPS)
|
||||||
|
}
|
||||||
|
|
||||||
|
function retainInlineStyle(node, allowedProperties = []) {
|
||||||
|
const preservedEntries = allowedProperties
|
||||||
|
.map((propertyName) => {
|
||||||
|
const value = node.style?.getPropertyValue(propertyName) || ''
|
||||||
|
return value ? `${propertyName}:${value}` : ''
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
|
||||||
|
if (preservedEntries.length) {
|
||||||
|
node.setAttribute('style', preservedEntries.join(';'))
|
||||||
|
} else {
|
||||||
|
node.removeAttribute('style')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPlainTextTableHtml(text) {
|
||||||
|
const normalizedText = String(text || '').replace(/\r/g, '')
|
||||||
|
if (!normalizedText.includes('\t')) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = normalizedText
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => line.split('\t'))
|
||||||
|
.filter((cells) => cells.some((cell) => String(cell || '').trim() !== ''))
|
||||||
|
|
||||||
|
const columnCount = rows.reduce((maxCount, row) => Math.max(maxCount, row.length), 0)
|
||||||
|
if (rows.length < 2 || columnCount < 2) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const minWidth = Math.max(640, columnCount * 140)
|
||||||
|
const rowHtml = rows
|
||||||
|
.map((cells) => {
|
||||||
|
const cellHtml = Array.from({ length: columnCount }, (_, index) => {
|
||||||
|
const value = escapeHtml(String(cells[index] || '').trim())
|
||||||
|
return `<td>${value || ' '}</td>`
|
||||||
|
}).join('')
|
||||||
|
return `<tr>${cellHtml}</tr>`
|
||||||
|
})
|
||||||
|
.join('')
|
||||||
|
|
||||||
|
return `<table style="width:100%;min-width:${minWidth}px;border-collapse:collapse;table-layout:auto;margin:8px 0;border:1px solid #1f2937;">${rowHtml}</table><p><br></p>`
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTextAlignment(command, targetSurface = '') {
|
||||||
|
if (props.disabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const surface = resolveTargetSurface(targetSurface)
|
||||||
|
activeSurface.value = surface
|
||||||
|
restoreSelection(surface)
|
||||||
|
const editor = getEditorElement(surface)
|
||||||
|
if (!editor) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const alignValue = resolveAlignValue(command)
|
||||||
|
if (!alignValue) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (!selection || !selection.rangeCount) {
|
||||||
|
editor.style.textAlign = alignValue
|
||||||
|
handleEditorInput(surface)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const range = selection.getRangeAt(0)
|
||||||
|
const block = findAlignmentTarget(range.startContainer, editor)
|
||||||
|
;(block || editor).style.textAlign = alignValue
|
||||||
|
captureSelection(surface)
|
||||||
|
handleEditorInput(surface)
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveAlignValue(command) {
|
||||||
|
if (command === 'justifyLeft') return 'left'
|
||||||
|
if (command === 'justifyCenter') return 'center'
|
||||||
|
if (command === 'justifyRight') return 'right'
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function findAlignmentTarget(node, editor) {
|
||||||
|
let current = node?.nodeType === Node.ELEMENT_NODE ? node : node?.parentElement || null
|
||||||
|
while (current && current !== editor) {
|
||||||
|
const tag = String(current.tagName || '').toLowerCase()
|
||||||
|
if (['p', 'div', 'li', 'td', 'th'].includes(tag)) {
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
current = current.parentElement
|
||||||
|
}
|
||||||
|
return editor
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusFirstTableCell(surface) {
|
||||||
|
const editor = getEditorElement(surface)
|
||||||
|
if (!editor) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const firstCell = editor.querySelector('td, th')
|
||||||
|
if (!firstCell) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (!selection) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const range = document.createRange()
|
||||||
|
range.selectNodeContents(firstCell)
|
||||||
|
range.collapse(true)
|
||||||
|
selection.removeAllRanges()
|
||||||
|
selection.addRange(range)
|
||||||
|
savedRanges[surface] = range.cloneRange()
|
||||||
|
}
|
||||||
|
|
||||||
|
function openFullscreen() {
|
||||||
|
isFullscreen.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFullscreenVisibleChange(value) {
|
||||||
|
isFullscreen.value = !!value
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeVerticalAlign(value) {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase()
|
||||||
|
if (['top', 'middle', 'center', 'bottom'].includes(normalized)) {
|
||||||
|
return normalized === 'center' ? 'middle' : normalized
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(text) {
|
||||||
|
return String(text || '')
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.template-rich-editor {
|
||||||
|
border: 1px solid #d9e1ec;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-rich-editor.is-disabled {
|
||||||
|
opacity: 0.92;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-bottom: 1px solid #edf2f7;
|
||||||
|
background: linear-gradient(180deg, #fcfdff 0%, #f8fbff 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-groups {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding-right: 10px;
|
||||||
|
border-right: 1px solid #e5edf7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-group:last-child {
|
||||||
|
padding-right: 0;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-button {
|
||||||
|
appearance: none;
|
||||||
|
border: 1px solid #d5deea;
|
||||||
|
background: #fff;
|
||||||
|
color: #334155;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-button:hover:not(:disabled) {
|
||||||
|
border-color: #93c5fd;
|
||||||
|
color: #1d4ed8;
|
||||||
|
background: #eff6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-button:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.48;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-button-primary {
|
||||||
|
border-color: #3b82f6;
|
||||||
|
color: #1d4ed8;
|
||||||
|
background: #eff6ff;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-content-shell {
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-scroll {
|
||||||
|
overflow: auto;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-surface {
|
||||||
|
min-height: 260px;
|
||||||
|
min-width: 100%;
|
||||||
|
width: max-content;
|
||||||
|
outline: none;
|
||||||
|
padding: 14px 16px 18px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #fff;
|
||||||
|
line-height: 1.7;
|
||||||
|
color: #0f172a;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-surface.has-table {
|
||||||
|
min-width: 760px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-surface[contenteditable='false'] {
|
||||||
|
background: #fcfdff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-surface:empty::before {
|
||||||
|
content: attr(data-placeholder);
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-surface p,
|
||||||
|
.editor-surface div,
|
||||||
|
.editor-surface li {
|
||||||
|
margin: 0 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-surface table {
|
||||||
|
width: 100%;
|
||||||
|
max-width: none;
|
||||||
|
margin: 8px 0;
|
||||||
|
border-collapse: collapse;
|
||||||
|
table-layout: auto;
|
||||||
|
border: 1px solid #1f2937;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-surface td,
|
||||||
|
.editor-surface th {
|
||||||
|
min-width: 120px;
|
||||||
|
border: 1px solid #1f2937 !important;
|
||||||
|
padding: 6px 10px;
|
||||||
|
vertical-align: middle;
|
||||||
|
word-break: keep-all;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-surface th {
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-tip {
|
||||||
|
padding: 8px 12px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #64748b;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-top: 1px solid #edf2f7;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-layout {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
background: #f4f7fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-toolbar {
|
||||||
|
border-bottom-color: rgba(148, 163, 184, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-body {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-scroll {
|
||||||
|
height: 100%;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-surface {
|
||||||
|
min-height: 100%;
|
||||||
|
min-width: 960px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.editor-toolbar,
|
||||||
|
.fullscreen-toolbar {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-groups {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-button-primary {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-surface.has-table,
|
||||||
|
.fullscreen-surface {
|
||||||
|
min-width: 720px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -228,7 +228,7 @@
|
|||||||
<a v-if="canMockReject(row)" @click="handleMockReject(row)">{{
|
<a v-if="canMockReject(row)" @click="handleMockReject(row)">{{
|
||||||
isSingleAgreementRecord(row) ? "审批驳回" : "测试驳回"
|
isSingleAgreementRecord(row) ? "审批驳回" : "测试驳回"
|
||||||
}}</a>
|
}}</a>
|
||||||
<a v-if="canOnlineSign(row)" @click="handleOnlineSign(row)">{{
|
<a v-if="SHOW_SIGN_ACTIONS_IN_LAUNCH && canOnlineSign(row)" @click="handleOnlineSign(row)">{{
|
||||||
isSingleAgreementRecord(row) ? "发起盖章" : "在线签订"
|
isSingleAgreementRecord(row) ? "发起盖章" : "在线签订"
|
||||||
}}</a>
|
}}</a>
|
||||||
<a
|
<a
|
||||||
@@ -236,11 +236,11 @@
|
|||||||
@click="handleDownloadSigned(row)"
|
@click="handleDownloadSigned(row)"
|
||||||
>下载签署文件</a
|
>下载签署文件</a
|
||||||
>
|
>
|
||||||
<a v-if="canUploadSigned(row)" @click="handleUploadSigned(row)">{{
|
<a v-if="SHOW_SIGN_ACTIONS_IN_LAUNCH && canUploadSigned(row)" @click="handleUploadSigned(row)">{{
|
||||||
isSingleAgreementRecord(row) ? "上传盖章版" : "上传已签署版本"
|
isSingleAgreementRecord(row) ? "上传盖章版" : "上传已签署版本"
|
||||||
}}</a>
|
}}</a>
|
||||||
<a
|
<a
|
||||||
v-if="canMockFinishSign(row)"
|
v-if="SHOW_SIGN_ACTIONS_IN_LAUNCH && canMockFinishSign(row)"
|
||||||
@click="handleMockFinishSign(row)"
|
@click="handleMockFinishSign(row)"
|
||||||
>{{
|
>{{
|
||||||
isSingleAgreementRecord(row) ? "测试盖章完成" : "测试签署完成"
|
isSingleAgreementRecord(row) ? "测试盖章完成" : "测试签署完成"
|
||||||
@@ -323,6 +323,7 @@ import {
|
|||||||
getLaunchDetail,
|
getLaunchDetail,
|
||||||
getLawApproveCallbackSample,
|
getLawApproveCallbackSample,
|
||||||
mockApproveLaunch,
|
mockApproveLaunch,
|
||||||
|
uploadWatermarkFile,
|
||||||
mockRejectLaunch,
|
mockRejectLaunch,
|
||||||
finishLaunchSeal,
|
finishLaunchSeal,
|
||||||
queryApproveViewUrl,
|
queryApproveViewUrl,
|
||||||
@@ -345,6 +346,9 @@ const contractRecordStatusOptions = [
|
|||||||
{ label: "已终止", value: "terminated" },
|
{ label: "已终止", value: "terminated" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 合同签署已拆到独立菜单,这里只隐藏入口,保留原有逻辑方便后续恢复。
|
||||||
|
const SHOW_SIGN_ACTIONS_IN_LAUNCH = false;
|
||||||
|
|
||||||
const formRef = ref(null);
|
const formRef = ref(null);
|
||||||
const queryContainer = ref(null);
|
const queryContainer = ref(null);
|
||||||
const pageWrapperRef = ref(null);
|
const pageWrapperRef = ref(null);
|
||||||
@@ -1039,12 +1043,16 @@ function handleUploadSigned(row) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleOnlineSign(row) {
|
function handleOnlineSign(row) {
|
||||||
getLaunchDetail(row.id).then((res) => {
|
getLaunchDetail(row.id).then(async (res) => {
|
||||||
if (!isSuccessResponse(res) || !getDataResult(res)) {
|
if (!isSuccessResponse(res) || !getDataResult(res)) {
|
||||||
message.error(getResponseMessage(res, "获取合同详情失败"));
|
message.error(getResponseMessage(res, "获取合同详情失败"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const detail = getDataResult(res);
|
const detail = getDataResult(res);
|
||||||
|
if (!detail.contractTextFilePath) {
|
||||||
|
await handleWatermarkUpload(row);
|
||||||
|
return;
|
||||||
|
}
|
||||||
mixedSignModalRef.value?.showModal({
|
mixedSignModalRef.value?.showModal({
|
||||||
...detail,
|
...detail,
|
||||||
contractId: detail.sealContractId,
|
contractId: detail.sealContractId,
|
||||||
@@ -1052,6 +1060,60 @@ function handleOnlineSign(row) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleWatermarkUpload(row) {
|
||||||
|
let file
|
||||||
|
const ok = await new Promise((resolve) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: "法务水印文件缺失",
|
||||||
|
content: h("div", [
|
||||||
|
h("p", { style: "margin-bottom: 12px" }, "法务审批回调未带回带水印的合同文件,请手动上传带水印的合同文件后继续在线签订。"),
|
||||||
|
h("input", {
|
||||||
|
type: "file",
|
||||||
|
accept: ".pdf,.doc,.docx",
|
||||||
|
style: "margin-top: 8px; width: 100%",
|
||||||
|
onChange(e) {
|
||||||
|
file = e.target.files?.[0]
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
okText: "上传并继续",
|
||||||
|
cancelText: "取消",
|
||||||
|
async onOk() {
|
||||||
|
if (!file) {
|
||||||
|
message.warning("请先选择文件")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append("file", file)
|
||||||
|
try {
|
||||||
|
const res = await uploadWatermarkFile(row.id, formData)
|
||||||
|
if (!isSuccessResponse(res)) {
|
||||||
|
message.error(getResponseMessage(res, "上传水印文件失败"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
message.success("水印文件上传成功")
|
||||||
|
resolve(true)
|
||||||
|
} catch {
|
||||||
|
message.error("上传水印文件失败")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
resolve(false)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if (!ok) return
|
||||||
|
getLaunchDetail(row.id).then((res) => {
|
||||||
|
if (!isSuccessResponse(res) || !getDataResult(res)) return
|
||||||
|
const detail = getDataResult(res)
|
||||||
|
mixedSignModalRef.value?.showModal({
|
||||||
|
...detail,
|
||||||
|
contractId: detail.sealContractId,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function handleSignBusinessUpdated() {
|
function handleSignBusinessUpdated() {
|
||||||
getList();
|
getList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,6 +180,15 @@ const actionTypeOptions = [
|
|||||||
{ label: "合同归档", value: "archive" },
|
{ label: "合同归档", value: "archive" },
|
||||||
{ label: "终止合同", value: "terminate" },
|
{ label: "终止合同", value: "terminate" },
|
||||||
{ label: "删除合同", value: "delete" },
|
{ label: "删除合同", value: "delete" },
|
||||||
|
// ===== 签章平台操作日志 =====
|
||||||
|
{ label: "创建签署流程", value: "seal_create" },
|
||||||
|
{ label: "签署确认", value: "seal_sign" },
|
||||||
|
{ label: "签署流程完结", value: "seal_complete" },
|
||||||
|
{ label: "取消签署", value: "seal_cancel" },
|
||||||
|
{ label: "时间戳签章", value: "seal_timestamp" },
|
||||||
|
{ label: "批注签章", value: "seal_comment" },
|
||||||
|
{ label: "签章回调通知", value: "seal_callback" },
|
||||||
|
{ label: "上传线下签署文件", value: "seal_upload" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const actionStatusOptions = [
|
const actionStatusOptions = [
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<div>
|
<div>
|
||||||
<h3>合同模板配置中心</h3>
|
<h3>合同模板配置中心</h3>
|
||||||
<p>在线维护模板配置与变量映射,推荐在本地 Word 编辑正文后回传系统。</p>
|
<p>上传模板后自动识别 `{中文变量}` 占位符,并为每个变量配置录入类型、展示名称和数据源。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -33,18 +33,11 @@
|
|||||||
<a-card size="small" title="基本信息" class="panel-card">
|
<a-card size="small" title="基本信息" class="panel-card">
|
||||||
<div class="field-grid field-grid-2">
|
<div class="field-grid field-grid-2">
|
||||||
<a-form-item label="范本名称" name="templateName">
|
<a-form-item label="范本名称" name="templateName">
|
||||||
<a-input
|
<a-input v-model:value="formState.templateName" placeholder="请输入范本名称" />
|
||||||
v-model:value="formState.templateName"
|
|
||||||
placeholder="请输入范本名称"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="范本编号" name="templateCode">
|
<a-form-item label="范本编号" name="templateCode">
|
||||||
<a-input
|
<a-input v-model:value="formState.templateCode" placeholder="保存后自动生成" readonly />
|
||||||
v-model:value="formState.templateCode"
|
|
||||||
placeholder="保存后自动生成"
|
|
||||||
readonly
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="范本类型" name="source">
|
<a-form-item label="范本类型" name="source">
|
||||||
@@ -62,11 +55,9 @@
|
|||||||
placeholder="请选择合同类型"
|
placeholder="请选择合同类型"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="版本号" name="version">
|
<a-form-item label="版本号" name="version">
|
||||||
<a-input
|
<a-input v-model:value="formState.version" placeholder="例如 V1.0" />
|
||||||
v-model:value="formState.version"
|
|
||||||
placeholder="例如 V1.0"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="发布范围" name="publishOrgIds" class="field-span-2">
|
<a-form-item label="发布范围" name="publishOrgIds" class="field-span-2">
|
||||||
@@ -102,20 +93,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
|
||||||
<a-card size="small" title="模板文件与配置预览" class="panel-card">
|
<a-card size="small" title="模板文件与识别结果" class="panel-card">
|
||||||
<a-alert
|
<a-alert
|
||||||
type="info"
|
type="info"
|
||||||
show-icon
|
show-icon
|
||||||
class="workflow-alert"
|
class="workflow-alert"
|
||||||
message="推荐维护方式"
|
message="维护建议"
|
||||||
description="下载当前模板到本地 Word 编辑正文与占位符,保存后重新上传回传;平台负责变量识别、映射、预览与发布。"
|
description="富文本变量建议单独占一行或单独放在表格单元格内,这样后续渲染表格和多段文字会更稳。"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="upload-meta">
|
<div class="upload-meta">
|
||||||
<div>
|
<div>
|
||||||
<div class="meta-label">当前模板文件</div>
|
<div class="meta-label">当前模板文件</div>
|
||||||
<div class="meta-value">
|
<div class="meta-value">{{ formState.templateFileName || '尚未上传 .docx 模板文件' }}</div>
|
||||||
{{ formState.templateFileName || '尚未上传 .docx 模板文件' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a-space v-if="!isViewMode">
|
<a-space v-if="!isViewMode">
|
||||||
@@ -123,7 +113,7 @@
|
|||||||
<DownloadOutlined />
|
<DownloadOutlined />
|
||||||
下载当前模板
|
下载当前模板
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button @click="handleReDetectVariables">
|
<a-button @click="handleReDetectVariables" :disabled="!templateArrayBuffer">
|
||||||
<ReloadOutlined />
|
<ReloadOutlined />
|
||||||
重新识别变量
|
重新识别变量
|
||||||
</a-button>
|
</a-button>
|
||||||
@@ -148,25 +138,29 @@
|
|||||||
<FileWordOutlined />
|
<FileWordOutlined />
|
||||||
</p>
|
</p>
|
||||||
<p class="ant-upload-text">上传 Word 模板文件(支持 `.docx`)</p>
|
<p class="ant-upload-text">上传 Word 模板文件(支持 `.docx`)</p>
|
||||||
<p class="ant-upload-hint">
|
<p class="ant-upload-hint">上传后自动识别 `{合同名称}`、`{附件一内容}` 这类花括号变量。</p>
|
||||||
上传后会自动识别花括号变量,如 `{contractName}`、`{partyAName}`,并生成变量匹配清单。
|
<p class="ant-upload-hint">推荐先在本地 Word 调整正文,再上传到平台做变量配置。</p>
|
||||||
</p>
|
|
||||||
<p class="ant-upload-hint">
|
|
||||||
如需修改正文内容,建议先下载模板到本地 Word 编辑后再重新上传回传。
|
|
||||||
</p>
|
|
||||||
</a-upload-dragger>
|
</a-upload-dragger>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
|
||||||
<a-card size="small" title="占位符工具栏与变量映射" class="panel-card">
|
<a-card size="small" title="变量映射与录入类型" class="panel-card">
|
||||||
<div class="variable-header">
|
<div class="variable-header">
|
||||||
<div>
|
<div>
|
||||||
<div class="meta-label">已识别变量</div>
|
<div class="meta-label">已识别变量</div>
|
||||||
<div class="meta-value">{{ detectedVariableKeys.length ? detectedVariableKeys.join('、') : '暂未识别到变量' }}</div>
|
<div class="meta-value">
|
||||||
|
{{ detectedVariableKeys.length ? detectedVariableKeys.join('、') : '暂未识别到变量' }}
|
||||||
|
</div>
|
||||||
<div class="catalog-help">
|
<div class="catalog-help">
|
||||||
点击下方字段标签可直接复制占位符,点击“加入映射”可快速补充变量绑定。
|
每个变量都要配置展示名称和录入类型。下拉变量需要填写字典类型,富文本变量支持文字和表格。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a-space>
|
<a-space>
|
||||||
|
<a-tooltip title="查看甲方信息自动带出规则">
|
||||||
|
<a-button size="small" @click="autoFillGuideVisible = true">
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
甲方带出说明
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
<a-button @click="handleDownloadVariableGuide">
|
<a-button @click="handleDownloadVariableGuide">
|
||||||
<DownloadOutlined />
|
<DownloadOutlined />
|
||||||
下载变量说明
|
下载变量说明
|
||||||
@@ -178,33 +172,13 @@
|
|||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div class="catalog-wrap">
|
<a-alert
|
||||||
<div class="catalog-title">占位符工具栏</div>
|
type="warning"
|
||||||
<div class="catalog-list">
|
show-icon
|
||||||
<div
|
class="workflow-alert"
|
||||||
v-for="item in templateFieldList"
|
message="富文本变量说明"
|
||||||
:key="item.value"
|
description="富文本变量最终会替换为 Word 正文内容,支持多段文本和表格。请尽量把富文本占位符单独放在一行。"
|
||||||
class="catalog-item"
|
/>
|
||||||
>
|
|
||||||
<a-tag
|
|
||||||
color="blue"
|
|
||||||
class="catalog-tag"
|
|
||||||
@click="handleCopyPlaceholder(item.value)"
|
|
||||||
>
|
|
||||||
{{ item.label }}
|
|
||||||
</a-tag>
|
|
||||||
<a-button
|
|
||||||
v-if="!isViewMode"
|
|
||||||
type="link"
|
|
||||||
size="small"
|
|
||||||
class="catalog-action"
|
|
||||||
@click.stop="handleQuickAddCatalogField(item.value)"
|
|
||||||
>
|
|
||||||
加入映射
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<div v-if="variableMappings.length" class="mapping-list">
|
<div v-if="variableMappings.length" class="mapping-list">
|
||||||
<div
|
<div
|
||||||
@@ -230,31 +204,81 @@
|
|||||||
<a-input
|
<a-input
|
||||||
v-model:value="item.variableKey"
|
v-model:value="item.variableKey"
|
||||||
:disabled="isViewMode"
|
:disabled="isViewMode"
|
||||||
placeholder="例如 contractName"
|
placeholder="例如 合同名称、附件一内容"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="匹配业务字段">
|
<a-form-item label="匹配业务字段">
|
||||||
<a-select
|
<a-auto-complete
|
||||||
v-model:value="item.bindField"
|
v-model:value="item.bindField"
|
||||||
:options="templateFieldOptions"
|
:options="templateFieldAutoOptions"
|
||||||
:disabled="isViewMode"
|
:disabled="isViewMode"
|
||||||
placeholder="请选择业务字段"
|
placeholder="请选择或直接输入业务字段编码"
|
||||||
|
allow-clear
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="展示名称">
|
||||||
|
<a-input
|
||||||
|
v-model:value="item.fieldLabel"
|
||||||
|
:disabled="isViewMode"
|
||||||
|
placeholder="合同发起页表单展示名称"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="录入类型">
|
||||||
|
<a-select
|
||||||
|
v-model:value="item.fieldType"
|
||||||
|
:options="fieldTypeOptions"
|
||||||
|
:disabled="isViewMode"
|
||||||
|
placeholder="请选择录入类型"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="是否必填">
|
||||||
|
<a-switch
|
||||||
|
v-model:checked="item.required"
|
||||||
|
:disabled="isViewMode"
|
||||||
|
checked-children="必填"
|
||||||
|
un-checked-children="选填"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="输入提示">
|
||||||
|
<a-input
|
||||||
|
v-model:value="item.placeholder"
|
||||||
|
:disabled="isViewMode"
|
||||||
|
placeholder="可选,例如 请输入附件一内容"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item v-if="item.fieldType === 'select'" label="字典类型" class="field-span-2">
|
||||||
|
<a-auto-complete
|
||||||
|
v-model:value="item.dictType"
|
||||||
|
:options="dictTypePresetOptions"
|
||||||
|
:disabled="isViewMode"
|
||||||
|
placeholder="例如 orgBusType、contractPayMethod"
|
||||||
|
allow-clear
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="备注" class="field-span-2">
|
||||||
|
<a-input
|
||||||
|
v-model:value="item.remark"
|
||||||
|
:disabled="isViewMode"
|
||||||
|
placeholder="可选,说明该变量的填写要求"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a-empty
|
<a-empty v-else description="暂无变量匹配项" />
|
||||||
v-else
|
|
||||||
description="暂无变量匹配项"
|
|
||||||
/>
|
|
||||||
</a-card>
|
</a-card>
|
||||||
|
|
||||||
<a-card size="small" title="维护信息" class="panel-card">
|
<a-card size="small" title="维护信息" class="panel-card">
|
||||||
<div class="field-grid field-grid-2">
|
<div class="field-grid field-grid-2">
|
||||||
<a-form-item label="创建人">
|
<a-form-item label="维护人">
|
||||||
<a-input v-model:value="formState.updatedBy" disabled />
|
<a-input v-model:value="formState.updatedBy" disabled />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="创建时间">
|
<a-form-item label="创建时间">
|
||||||
@@ -269,12 +293,8 @@
|
|||||||
<a-space>
|
<a-space>
|
||||||
<a-button @click="visible = false">关闭</a-button>
|
<a-button @click="visible = false">关闭</a-button>
|
||||||
<template v-if="!isViewMode">
|
<template v-if="!isViewMode">
|
||||||
<a-button @click="handleSubmit('draft')">
|
<a-button @click="handleSubmit('draft')">保存</a-button>
|
||||||
保存
|
<a-button type="primary" @click="handleSubmit('published')">发布</a-button>
|
||||||
</a-button>
|
|
||||||
<a-button type="primary" @click="handleSubmit('published')">
|
|
||||||
发布
|
|
||||||
</a-button>
|
|
||||||
</template>
|
</template>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
@@ -284,7 +304,7 @@
|
|||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<div>
|
<div>
|
||||||
<h3>模板预览</h3>
|
<h3>模板预览</h3>
|
||||||
<p>右侧基于当前变量映射渲染模板效果,用于检查占位符位置与展示结果,本页不直接在线修改 Word 正文。</p>
|
<p>右侧用变量展示名模拟填充效果,帮助检查占位符是否识别正确。</p>
|
||||||
</div>
|
</div>
|
||||||
<a-space size="small">
|
<a-space size="small">
|
||||||
<a-tag color="geekblue">{{ formState.source || '未分类' }}</a-tag>
|
<a-tag color="geekblue">{{ formState.source || '未分类' }}</a-tag>
|
||||||
@@ -295,7 +315,7 @@
|
|||||||
<div class="preview-shell">
|
<div class="preview-shell">
|
||||||
<div v-if="!templateArrayBuffer" class="preview-state">
|
<div v-if="!templateArrayBuffer" class="preview-state">
|
||||||
<FileWordOutlined style="font-size: 42px; color: #94a3b8" />
|
<FileWordOutlined style="font-size: 42px; color: #94a3b8" />
|
||||||
<p>上传 `.docx` 模板文件后,这里会显示填充后的预览;正文修改建议在本地 Word 完成后回传。</p>
|
<p>上传 `.docx` 模板文件后,这里会显示填充预览。</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else ref="previewRef" class="preview-stage"></div>
|
<div v-else ref="previewRef" class="preview-stage"></div>
|
||||||
<div v-if="rendering" class="preview-state preview-mask">
|
<div v-if="rendering" class="preview-state preview-mask">
|
||||||
@@ -310,6 +330,43 @@
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
|
<a-modal
|
||||||
|
v-model:open="autoFillGuideVisible"
|
||||||
|
title="甲方自动带出变量"
|
||||||
|
width="760px"
|
||||||
|
:footer="null"
|
||||||
|
>
|
||||||
|
<div class="auto-fill-guide">
|
||||||
|
<a-alert
|
||||||
|
type="info"
|
||||||
|
show-icon
|
||||||
|
message="使用规则"
|
||||||
|
description="模板中写入下列中文变量后,合同发起选择甲方并进入合同正文配置时,系统会从甲方信息自动填入。只有一个甲方时会填“甲方名称”这类变量;多个甲方时使用“甲方1名称”“甲方名称列表”。"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="guide-section">
|
||||||
|
<h4>单甲方变量</h4>
|
||||||
|
<div class="guide-tag-list">
|
||||||
|
<a-tag v-for="name in ourPartySingleVariables" :key="name">{{ name }}</a-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="guide-section">
|
||||||
|
<h4>多甲方变量(支持甲方1到甲方{{ OUR_PARTY_AUTO_FILL_MAX_COUNT }})</h4>
|
||||||
|
<div class="guide-tag-list">
|
||||||
|
<a-tag v-for="name in ourPartyIndexedVariables" :key="name">{{ name }}</a-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="guide-section">
|
||||||
|
<h4>汇总变量</h4>
|
||||||
|
<div class="guide-tag-list">
|
||||||
|
<a-tag v-for="name in ourPartyListVariables" :key="name">{{ name }}</a-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -320,6 +377,7 @@ import {
|
|||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
DownloadOutlined,
|
DownloadOutlined,
|
||||||
FileWordOutlined,
|
FileWordOutlined,
|
||||||
|
InfoCircleOutlined,
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
} from '@ant-design/icons-vue'
|
} from '@ant-design/icons-vue'
|
||||||
@@ -334,16 +392,30 @@ import {
|
|||||||
import { contractTypeTreeOptions } from '../contractTypeOptions.js'
|
import { contractTypeTreeOptions } from '../contractTypeOptions.js'
|
||||||
import {
|
import {
|
||||||
buildTemplateFieldLabelMap,
|
buildTemplateFieldLabelMap,
|
||||||
buildTemplateFieldOptions,
|
|
||||||
loadTemplateFieldList,
|
loadTemplateFieldList,
|
||||||
} from '../../shared/templateFieldDict.js'
|
} from '../../shared/templateFieldDict.js'
|
||||||
|
import {
|
||||||
|
buildTemplatePreviewData,
|
||||||
|
} from '../../shared/templateDocxRenderer.js'
|
||||||
|
import {
|
||||||
|
createEmptyTemplateVariableMapping,
|
||||||
|
normalizeTemplateVariableMapping,
|
||||||
|
TEMPLATE_FIELD_TYPE_OPTIONS,
|
||||||
|
} from '../../shared/templateVariableConfig.js'
|
||||||
|
import {
|
||||||
|
getOurPartyAutoFillFieldOptions,
|
||||||
|
getOurPartyAutoFillIndexedVariableNames,
|
||||||
|
getOurPartyAutoFillListVariableNames,
|
||||||
|
getOurPartyAutoFillSingleVariableNames,
|
||||||
|
OUR_PARTY_AUTO_FILL_MAX_COUNT,
|
||||||
|
} from '../../shared/templateAutoFillMappings.js'
|
||||||
|
import {
|
||||||
|
loadDictTypeOptions,
|
||||||
|
buildDictTypeSelectOptions,
|
||||||
|
} from '../../shared/dictTypeOptions.js'
|
||||||
|
|
||||||
// ========== 模板管理字典(后续可由后端接口替换) ==========
|
|
||||||
|
|
||||||
// 合同类型选项
|
|
||||||
const contractTypeOptions = contractTypeTreeOptions
|
const contractTypeOptions = contractTypeTreeOptions
|
||||||
|
|
||||||
// 范本类型选项
|
|
||||||
const templateSourceOptions = [
|
const templateSourceOptions = [
|
||||||
{ label: '标准模板', value: '标准模板' },
|
{ label: '标准模板', value: '标准模板' },
|
||||||
{ label: '推荐文本', value: '推荐文本' },
|
{ label: '推荐文本', value: '推荐文本' },
|
||||||
@@ -351,13 +423,8 @@ const templateSourceOptions = [
|
|||||||
{ label: '对方文本', value: '对方文本' },
|
{ label: '对方文本', value: '对方文本' },
|
||||||
]
|
]
|
||||||
|
|
||||||
// 批量创建变量映射
|
|
||||||
function createSimpleVariableList(list = []) {
|
function createSimpleVariableList(list = []) {
|
||||||
return list.map((item, index) => ({
|
return list.map((item, index) => normalizeTemplateVariableMapping(item, index))
|
||||||
id: item.id || `var_${Date.now()}_${index}`,
|
|
||||||
variableKey: item.variableKey || '',
|
|
||||||
bindField: item.bindField || '',
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit = defineEmits(['submit'])
|
const emit = defineEmits(['submit'])
|
||||||
@@ -372,6 +439,7 @@ const templateArrayBuffer = ref(null)
|
|||||||
const templateUploadList = ref([])
|
const templateUploadList = ref([])
|
||||||
const variableMappings = ref([])
|
const variableMappings = ref([])
|
||||||
const detectedVariableKeys = ref([])
|
const detectedVariableKeys = ref([])
|
||||||
|
const autoFillGuideVisible = ref(false)
|
||||||
const orgTreeData = ref([])
|
const orgTreeData = ref([])
|
||||||
const SHOW_PARENT = 'SHOW_PARENT'
|
const SHOW_PARENT = 'SHOW_PARENT'
|
||||||
const templateFieldList = ref([])
|
const templateFieldList = ref([])
|
||||||
@@ -379,14 +447,28 @@ const templateFieldList = ref([])
|
|||||||
let previewTimer = null
|
let previewTimer = null
|
||||||
let renderTaskId = 0
|
let renderTaskId = 0
|
||||||
|
|
||||||
const templateFieldOptions = computed(() =>
|
|
||||||
buildTemplateFieldOptions(templateFieldList.value),
|
|
||||||
)
|
|
||||||
|
|
||||||
const templateFieldLabelMap = computed(() =>
|
const templateFieldLabelMap = computed(() =>
|
||||||
buildTemplateFieldLabelMap(templateFieldList.value),
|
buildTemplateFieldLabelMap(templateFieldList.value),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const templateFieldAutoOptions = computed(() =>
|
||||||
|
[
|
||||||
|
...(templateFieldList.value || []).map((item) => ({
|
||||||
|
value: item.value,
|
||||||
|
label: `${item.label}(${item.value})`,
|
||||||
|
})),
|
||||||
|
...getOurPartyAutoFillFieldOptions(),
|
||||||
|
].filter((item, index, list) =>
|
||||||
|
item.value && list.findIndex((option) => option.value === item.value) === index,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
const fieldTypeOptions = TEMPLATE_FIELD_TYPE_OPTIONS
|
||||||
|
const dictTypePresetOptions = ref([])
|
||||||
|
const ourPartySingleVariables = getOurPartyAutoFillSingleVariableNames()
|
||||||
|
const ourPartyIndexedVariables = getOurPartyAutoFillIndexedVariableNames()
|
||||||
|
const ourPartyListVariables = getOurPartyAutoFillListVariableNames()
|
||||||
|
|
||||||
const formRules = {
|
const formRules = {
|
||||||
templateName: [{ required: true, message: '请输入范本名称', trigger: 'blur' }],
|
templateName: [{ required: true, message: '请输入范本名称', trigger: 'blur' }],
|
||||||
source: [{ required: true, message: '请选择范本类型', trigger: 'change' }],
|
source: [{ required: true, message: '请选择范本类型', trigger: 'change' }],
|
||||||
@@ -446,6 +528,11 @@ watch(
|
|||||||
variableMappings.value.map((item) => ({
|
variableMappings.value.map((item) => ({
|
||||||
variableKey: item.variableKey,
|
variableKey: item.variableKey,
|
||||||
bindField: item.bindField,
|
bindField: item.bindField,
|
||||||
|
fieldLabel: item.fieldLabel,
|
||||||
|
fieldType: item.fieldType,
|
||||||
|
required: item.required,
|
||||||
|
dictType: item.dictType,
|
||||||
|
placeholder: item.placeholder,
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
() => {
|
() => {
|
||||||
@@ -520,7 +607,6 @@ function resolveUploadedFilePath(res) {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function extractVariablesFromText(text) {
|
function extractVariablesFromText(text) {
|
||||||
const result = new Set()
|
const result = new Set()
|
||||||
const regex = /\{+\s*([^{}\s]+)\s*\}+/g
|
const regex = /\{+\s*([^{}\s]+)\s*\}+/g
|
||||||
@@ -543,11 +629,17 @@ function mergeDetectedVariables(variableKeys) {
|
|||||||
|
|
||||||
const detectedRows = nextKeys.map((variableKey) => {
|
const detectedRows = nextKeys.map((variableKey) => {
|
||||||
const existing = existingMap.get(variableKey)
|
const existing = existingMap.get(variableKey)
|
||||||
return {
|
return normalizeTemplateVariableMapping({
|
||||||
id: existing?.id || `var_${Date.now()}_${variableKey}`,
|
id: existing?.id || `var_${Date.now()}_${variableKey}`,
|
||||||
variableKey,
|
variableKey,
|
||||||
bindField: existing?.bindField || variableKey,
|
bindField: existing?.bindField || variableKey,
|
||||||
}
|
fieldLabel: existing?.fieldLabel || variableKey,
|
||||||
|
fieldType: existing?.fieldType || 'input',
|
||||||
|
required: existing?.required !== false,
|
||||||
|
placeholder: existing?.placeholder || '',
|
||||||
|
dictType: existing?.dictType || '',
|
||||||
|
remark: existing?.remark || '',
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const manualRows = variableMappings.value
|
const manualRows = variableMappings.value
|
||||||
@@ -575,7 +667,6 @@ async function detectVariablesFromBuffer(buffer) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
mergeDetectedVariables([...allVariables])
|
mergeDetectedVariables([...allVariables])
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.warning('模板文件已上传,但变量自动识别失败,请手动维护变量')
|
message.warning('模板文件已上传,但变量自动识别失败,请手动维护变量')
|
||||||
}
|
}
|
||||||
@@ -649,22 +740,8 @@ async function handleReDetectVariables() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleAddManualVariable() {
|
function handleAddManualVariable() {
|
||||||
variableMappings.value.push({
|
|
||||||
id: `var_${Date.now()}_${variableMappings.value.length + 1}`,
|
|
||||||
variableKey: '',
|
|
||||||
bindField: '',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleQuickAddCatalogField(fieldKey) {
|
|
||||||
if (isViewMode.value) return
|
|
||||||
|
|
||||||
variableMappings.value.push(
|
variableMappings.value.push(
|
||||||
{
|
createEmptyTemplateVariableMapping(variableMappings.value.length + 1),
|
||||||
id: `var_${Date.now()}_${fieldKey}`,
|
|
||||||
variableKey: fieldKey,
|
|
||||||
bindField: fieldKey,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -672,47 +749,12 @@ function buildPlaceholderText(fieldKey) {
|
|||||||
return `{${fieldKey}}`
|
return `{${fieldKey}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyTextToClipboard(text) {
|
|
||||||
if (navigator?.clipboard?.writeText) {
|
|
||||||
await navigator.clipboard.writeText(text)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const textarea = document.createElement('textarea')
|
|
||||||
textarea.value = text
|
|
||||||
textarea.setAttribute('readonly', 'readonly')
|
|
||||||
textarea.style.position = 'fixed'
|
|
||||||
textarea.style.left = '-9999px'
|
|
||||||
document.body.appendChild(textarea)
|
|
||||||
textarea.select()
|
|
||||||
document.execCommand('copy')
|
|
||||||
document.body.removeChild(textarea)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleCopyPlaceholder(fieldKey) {
|
|
||||||
const placeholder = buildPlaceholderText(fieldKey)
|
|
||||||
try {
|
|
||||||
await copyTextToClipboard(placeholder)
|
|
||||||
message.success(`已复制占位符:${placeholder}`)
|
|
||||||
} catch (error) {
|
|
||||||
message.warning(`复制失败,请手动使用占位符:${placeholder}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeVariable(id) {
|
function removeVariable(id) {
|
||||||
variableMappings.value = variableMappings.value.filter((item) => item.id !== id)
|
variableMappings.value = variableMappings.value.filter((item) => item.id !== id)
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildPreviewData() {
|
function buildPreviewData() {
|
||||||
const data = {}
|
return buildTemplatePreviewData(variableMappings.value, templateFieldLabelMap.value)
|
||||||
variableMappings.value.forEach((item) => {
|
|
||||||
if (!item.variableKey) return
|
|
||||||
data[item.variableKey] =
|
|
||||||
templateFieldLabelMap.value[item.bindField] ||
|
|
||||||
item.bindField ||
|
|
||||||
`[${item.variableKey}]`
|
|
||||||
})
|
|
||||||
return data
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderPreview() {
|
async function renderPreview() {
|
||||||
@@ -796,9 +838,9 @@ function buildRecord(status) {
|
|||||||
variables: variableMappings.value
|
variables: variableMappings.value
|
||||||
.filter((item) => item.variableKey)
|
.filter((item) => item.variableKey)
|
||||||
.map((item) => item.variableKey),
|
.map((item) => item.variableKey),
|
||||||
variableMappings: variableMappings.value.map((item) => ({
|
variableMappings: variableMappings.value.map((item, index) =>
|
||||||
...item,
|
normalizeTemplateVariableMapping(item, index),
|
||||||
})),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -824,6 +866,18 @@ function validateVariableMappings() {
|
|||||||
if (!item.bindField) {
|
if (!item.bindField) {
|
||||||
return `请为变量【${item.variableKey}】选择匹配业务字段`
|
return `请为变量【${item.variableKey}】选择匹配业务字段`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!item.fieldLabel) {
|
||||||
|
return `请为变量【${item.variableKey}】填写展示名称`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item.fieldType) {
|
||||||
|
return `请为变量【${item.variableKey}】选择录入类型`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.fieldType === 'select' && !item.dictType) {
|
||||||
|
return `请为下拉变量【${item.variableKey}】配置字典类型`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ''
|
return ''
|
||||||
@@ -888,14 +942,23 @@ function buildVariableGuideText() {
|
|||||||
'',
|
'',
|
||||||
'推荐维护方式:',
|
'推荐维护方式:',
|
||||||
'1. 下载当前模板到本地 Word 编辑正文内容。',
|
'1. 下载当前模板到本地 Word 编辑正文内容。',
|
||||||
'2. 在正文中插入占位符,例如 {contractName}、{partyAName}。',
|
'2. 在正文中插入占位符,例如 {合同名称}、{附件一内容}。',
|
||||||
'3. 保存后重新上传 .docx 文件回传系统。',
|
'3. 保存后重新上传 .docx 文件回传系统。',
|
||||||
'4. 在平台内完成变量识别、字段映射、预览和发布。',
|
'4. 在平台内为每个变量配置展示名称、录入类型和数据源。',
|
||||||
'',
|
'',
|
||||||
'可用占位符清单:',
|
'录入类型说明:',
|
||||||
|
'- 输入框:普通文本输入。',
|
||||||
|
'- 下拉框:需配置 dictType,例如 orgBusType。',
|
||||||
|
'- 富文本:支持多段文本与表格,建议占位符单独成段。',
|
||||||
|
'',
|
||||||
|
'可用业务字段参考:',
|
||||||
...templateFieldList.value.map(
|
...templateFieldList.value.map(
|
||||||
(item) => `- ${item.label}: ${buildPlaceholderText(item.value)}`,
|
(item) => `- ${item.label}: ${item.value}`,
|
||||||
),
|
),
|
||||||
|
'',
|
||||||
|
'甲方自动带出变量:',
|
||||||
|
'说明:单甲方变量仅在只有一个甲方时自动填;多甲方请使用甲方1/甲方2变量或列表变量。',
|
||||||
|
...getOurPartyAutoFillFieldOptions().map((item) => `- ${item.value}`),
|
||||||
]
|
]
|
||||||
|
|
||||||
return lines.join('\r\n')
|
return lines.join('\r\n')
|
||||||
@@ -942,9 +1005,6 @@ async function handleDownloadCurrentTemplate() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 将后端组织树节点转为 a-tree-select 需要的格式
|
|
||||||
*/
|
|
||||||
function convertOrgTreeToTreeSelect(node) {
|
function convertOrgTreeToTreeSelect(node) {
|
||||||
const treeNode = {
|
const treeNode = {
|
||||||
value: String(node.id),
|
value: String(node.id),
|
||||||
@@ -956,9 +1016,6 @@ function convertOrgTreeToTreeSelect(node) {
|
|||||||
return treeNode
|
return treeNode
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 从树数据构建 orgId → orgName 的映射(递归遍历)
|
|
||||||
*/
|
|
||||||
function buildOrgNameMap(treeData) {
|
function buildOrgNameMap(treeData) {
|
||||||
const map = new Map()
|
const map = new Map()
|
||||||
function walk(list) {
|
function walk(list) {
|
||||||
@@ -976,6 +1033,10 @@ async function showModal(record = {}, mode = 'create') {
|
|||||||
resetState()
|
resetState()
|
||||||
modalMode.value = mode || 'create'
|
modalMode.value = mode || 'create'
|
||||||
await ensureTemplateFieldListLoaded()
|
await ensureTemplateFieldListLoaded()
|
||||||
|
if (!dictTypePresetOptions.value.length) {
|
||||||
|
const list = await loadDictTypeOptions()
|
||||||
|
dictTypePresetOptions.value = buildDictTypeSelectOptions(list)
|
||||||
|
}
|
||||||
if (!orgTreeData.value.length) {
|
if (!orgTreeData.value.length) {
|
||||||
const tree = await getOrgTree()
|
const tree = await getOrgTree()
|
||||||
orgTreeData.value = (tree || []).map(convertOrgTreeToTreeSelect)
|
orgTreeData.value = (tree || []).map(convertOrgTreeToTreeSelect)
|
||||||
@@ -1017,7 +1078,6 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 详情模式下禁用表单文字颜色加深 */
|
|
||||||
.view-form:deep(.ant-select-disabled .ant-select-selection-item) {
|
.view-form:deep(.ant-select-disabled .ant-select-selection-item) {
|
||||||
color: rgba(0, 0, 0, 0.85) !important;
|
color: rgba(0, 0, 0, 0.85) !important;
|
||||||
}
|
}
|
||||||
@@ -1028,20 +1088,6 @@ defineExpose({
|
|||||||
color: rgba(0, 0, 0, 0.85) !important;
|
color: rgba(0, 0, 0, 0.85) !important;
|
||||||
-webkit-text-fill-color: rgba(0, 0, 0, 0.85) !important;
|
-webkit-text-fill-color: rgba(0, 0, 0, 0.85) !important;
|
||||||
}
|
}
|
||||||
.view-form:deep(.ant-input-number-disabled .ant-input-number-input) {
|
|
||||||
color: rgba(0, 0, 0, 0.85) !important;
|
|
||||||
-webkit-text-fill-color: rgba(0, 0, 0, 0.85) !important;
|
|
||||||
}
|
|
||||||
.view-form:deep(.ant-picker.ant-picker-disabled .ant-picker-input input) {
|
|
||||||
color: rgba(0, 0, 0, 0.85) !important;
|
|
||||||
-webkit-text-fill-color: rgba(0, 0, 0, 0.85) !important;
|
|
||||||
}
|
|
||||||
.view-form:deep(.ant-radio-wrapper.ant-radio-wrapper-disabled .ant-radio + span) {
|
|
||||||
color: rgba(0, 0, 0, 0.85) !important;
|
|
||||||
}
|
|
||||||
.view-form:deep(.ant-checkbox-wrapper.ant-checkbox-wrapper-disabled .ant-checkbox + span) {
|
|
||||||
color: rgba(0, 0, 0, 0.85) !important;
|
|
||||||
}
|
|
||||||
.view-form:deep(.ant-input-disabled) {
|
.view-form:deep(.ant-input-disabled) {
|
||||||
color: rgba(0, 0, 0, 0.85) !important;
|
color: rgba(0, 0, 0, 0.85) !important;
|
||||||
-webkit-text-fill-color: rgba(0, 0, 0, 0.85) !important;
|
-webkit-text-fill-color: rgba(0, 0, 0, 0.85) !important;
|
||||||
@@ -1049,7 +1095,7 @@ defineExpose({
|
|||||||
|
|
||||||
.template-manage-modal {
|
.template-manage-modal {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 560px minmax(0, 1fr);
|
grid-template-columns: 580px minmax(0, 1fr);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
@@ -1086,6 +1132,27 @@ defineExpose({
|
|||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.auto-fill-guide {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
max-height: 68vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-section h4 {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-tag-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.panel-header p {
|
.panel-header p {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #64748b;
|
color: #64748b;
|
||||||
@@ -1157,21 +1224,6 @@ defineExpose({
|
|||||||
line-height: 1.8;
|
line-height: 1.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.catalog-wrap {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
padding: 12px 14px;
|
|
||||||
border-radius: 12px;
|
|
||||||
background: #f8fbff;
|
|
||||||
border: 1px solid #e0eaf8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.catalog-title {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #334155;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.catalog-help {
|
.catalog-help {
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -1179,30 +1231,6 @@ defineExpose({
|
|||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.catalog-list {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.catalog-item {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 2px;
|
|
||||||
padding: 2px 4px;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: rgba(255, 255, 255, 0.72);
|
|
||||||
}
|
|
||||||
|
|
||||||
.catalog-tag {
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.catalog-action {
|
|
||||||
padding-inline: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mapping-list {
|
.mapping-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -1290,7 +1318,7 @@ defineExpose({
|
|||||||
|
|
||||||
@media (max-width: 1280px) {
|
@media (max-width: 1280px) {
|
||||||
.template-manage-modal {
|
.template-manage-modal {
|
||||||
grid-template-columns: 520px minmax(0, 1fr);
|
grid-template-columns: 540px minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { getDictDataByType } from "@/api/dict.js";
|
||||||
|
|
||||||
|
export const TEMPLATE_DICT_TYPES_KEY = "TEMPLATE_DICT_TYPES";
|
||||||
|
|
||||||
|
let cachedDictTypeOptions = null;
|
||||||
|
|
||||||
|
function normalizeDictTypeList(list = []) {
|
||||||
|
return list
|
||||||
|
.map((item) => ({
|
||||||
|
label: item?.k || item?.label || "",
|
||||||
|
value: item?.v || item?.value || "",
|
||||||
|
remark: item?.remark || "",
|
||||||
|
}))
|
||||||
|
.filter((item) => item.label && item.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadDictTypeOptions() {
|
||||||
|
if (cachedDictTypeOptions?.length) {
|
||||||
|
return [...cachedDictTypeOptions];
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await getDictDataByType(TEMPLATE_DICT_TYPES_KEY);
|
||||||
|
cachedDictTypeOptions = normalizeDictTypeList(result);
|
||||||
|
return [...cachedDictTypeOptions];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildDictTypeSelectOptions(list = []) {
|
||||||
|
return list.map((item) => ({
|
||||||
|
label: item.label,
|
||||||
|
value: item.value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
const LIST_SEPARATOR = ";";
|
||||||
|
|
||||||
|
export const OUR_PARTY_AUTO_FILL_MAX_COUNT = 5;
|
||||||
|
|
||||||
|
const RELATIVE_TYPE_LABELS = {
|
||||||
|
client: "客户",
|
||||||
|
supplier: "供应商",
|
||||||
|
Partner: "合作伙伴",
|
||||||
|
clientAndSup: "客户及供应商",
|
||||||
|
other: "其他",
|
||||||
|
};
|
||||||
|
|
||||||
|
const OPPOSITE_CHARACTER_LABELS = {
|
||||||
|
1: "境内组织",
|
||||||
|
2: "境内个人",
|
||||||
|
3: "境外组织",
|
||||||
|
4: "境外个人",
|
||||||
|
};
|
||||||
|
|
||||||
|
const ORG_NATURE_LABELS = {
|
||||||
|
ZF: "政府",
|
||||||
|
ZY: "政府",
|
||||||
|
QY: "企业",
|
||||||
|
QT: "其他组织",
|
||||||
|
};
|
||||||
|
|
||||||
|
const ORG_NATURE_LEVEL_LABELS = {
|
||||||
|
GNQY: "国有企业",
|
||||||
|
MYQY: "民营企业",
|
||||||
|
WZQY: "外资企业",
|
||||||
|
HZQY: "合资企业",
|
||||||
|
GYQZHKGQY: "国有全资和控股企业",
|
||||||
|
QT: "其他",
|
||||||
|
};
|
||||||
|
|
||||||
|
const ID_TYPE_LABELS = {
|
||||||
|
idCard: "身份证",
|
||||||
|
passport: "护照",
|
||||||
|
other: "其他",
|
||||||
|
};
|
||||||
|
|
||||||
|
function normalizeText(value) {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (typeof value === "boolean") {
|
||||||
|
return value ? "是" : "否";
|
||||||
|
}
|
||||||
|
return String(value).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickLabel(value, labelMap) {
|
||||||
|
const text = normalizeText(value);
|
||||||
|
if (!text) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return labelMap[text] || text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatBoolean(value) {
|
||||||
|
if (value === true || value === "true" || value === "1" || value === "Y" || value === "是") {
|
||||||
|
return "是";
|
||||||
|
}
|
||||||
|
if (value === false || value === "false" || value === "0" || value === "N" || value === "否") {
|
||||||
|
return "否";
|
||||||
|
}
|
||||||
|
return normalizeText(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMoneyLike(value) {
|
||||||
|
const text = normalizeText(value);
|
||||||
|
if (!text) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OUR_PARTY_AUTO_FILL_FIELD_MAPPINGS = [
|
||||||
|
{
|
||||||
|
label: "甲方名称",
|
||||||
|
suffixes: ["名称", "公司名称"],
|
||||||
|
sourceKeys: ["companyName"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "甲方主体编码",
|
||||||
|
suffixes: ["主体编码", "组织编码"],
|
||||||
|
sourceKeys: ["subjectCode"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "甲方公司类型",
|
||||||
|
suffixes: ["公司类型"],
|
||||||
|
sourceKeys: ["companyType", "relativeType"],
|
||||||
|
formatter: (value) => pickLabel(value, RELATIVE_TYPE_LABELS),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "甲方公司性质",
|
||||||
|
suffixes: ["公司性质"],
|
||||||
|
sourceKeys: ["companyNature", "oppositeCharacter", "oppCharacter"],
|
||||||
|
formatter: (value) => pickLabel(value, OPPOSITE_CHARACTER_LABELS),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "甲方组织性质",
|
||||||
|
suffixes: ["组织性质"],
|
||||||
|
sourceKeys: ["orgNature", "natureOfEnterpriseOrg"],
|
||||||
|
formatter: (value) => pickLabel(value, ORG_NATURE_LABELS),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "甲方组织性质企业二级",
|
||||||
|
suffixes: ["组织性质企业二级"],
|
||||||
|
sourceKeys: ["orgNatureLevel", "natureOfEnterpriseOrgLev"],
|
||||||
|
formatter: (value) => pickLabel(value, ORG_NATURE_LEVEL_LABELS),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "甲方是否涉港澳台",
|
||||||
|
suffixes: ["是否涉港澳台"],
|
||||||
|
sourceKeys: ["isHongKongMacaoTaiwan", "isSgat", "gatFlag"],
|
||||||
|
formatter: formatBoolean,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "甲方是否集团内",
|
||||||
|
suffixes: ["是否集团内"],
|
||||||
|
sourceKeys: ["isGroupInternal", "isJtn", "groupFlag"],
|
||||||
|
formatter: formatBoolean,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "甲方统一社会信用代码",
|
||||||
|
suffixes: ["统一社会信用代码", "信用代码"],
|
||||||
|
sourceKeys: ["creditCode", "customerTaxno"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "甲方法定代表人或负责人",
|
||||||
|
suffixes: ["法定代表人或负责人", "法定代表人", "负责人"],
|
||||||
|
sourceKeys: ["legalRepresentative", "legalPersonName"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "甲方注册地址",
|
||||||
|
suffixes: ["注册地址", "地址"],
|
||||||
|
sourceKeys: ["registeredAddress", "customerRegaddr"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "甲方注册资本",
|
||||||
|
suffixes: ["注册资本"],
|
||||||
|
sourceKeys: ["registeredCapitalText", "regCapital", "registeredCapital"],
|
||||||
|
formatter: formatMoneyLike,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "甲方联系人",
|
||||||
|
suffixes: ["联系人"],
|
||||||
|
sourceKeys: ["contactName"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "甲方联系电话",
|
||||||
|
suffixes: ["联系电话", "电话"],
|
||||||
|
sourceKeys: ["contactPhone", "officephone"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "甲方通讯地址",
|
||||||
|
suffixes: ["通讯地址", "联系地址"],
|
||||||
|
sourceKeys: ["contactAddress", "customerAddress"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "甲方电子邮箱",
|
||||||
|
suffixes: ["电子邮箱", "邮箱"],
|
||||||
|
sourceKeys: ["contactEmail", "emailAddress"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "甲方国家",
|
||||||
|
suffixes: ["国家"],
|
||||||
|
sourceKeys: ["countryName", "country"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "甲方自然人证件类型",
|
||||||
|
suffixes: ["自然人证件类型"],
|
||||||
|
sourceKeys: ["naturalPersonIdType", "certificateType"],
|
||||||
|
formatter: (value) => pickLabel(value, ID_TYPE_LABELS),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "甲方自然人证件号码",
|
||||||
|
suffixes: ["自然人证件号码"],
|
||||||
|
sourceKeys: ["naturalPersonIdNumber", "certificateNum"],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function getPrimarySuffix(mapping) {
|
||||||
|
return mapping.suffixes?.[0] || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildVariableName(prefix, suffix) {
|
||||||
|
return `${prefix}${suffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPartyList(source = {}) {
|
||||||
|
const partyState = source.partyState || source;
|
||||||
|
if (Array.isArray(partyState.ourPartyList)) {
|
||||||
|
return partyState.ourPartyList.filter((party) => normalizeText(party?.companyName || party?.subjectCode));
|
||||||
|
}
|
||||||
|
if (Array.isArray(source.partyList)) {
|
||||||
|
return source.partyList.filter((party) => String(party?.roleCode || "").toUpperCase() === "OUR");
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPartyValue(party = {}, mapping = {}) {
|
||||||
|
for (const key of mapping.sourceKeys || []) {
|
||||||
|
const rawValue = party[key];
|
||||||
|
const value = mapping.formatter ? mapping.formatter(rawValue, party) : normalizeText(rawValue);
|
||||||
|
if (value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOurPartyAutoFillSingleVariableNames() {
|
||||||
|
return OUR_PARTY_AUTO_FILL_FIELD_MAPPINGS.flatMap((mapping) =>
|
||||||
|
(mapping.suffixes || []).map((suffix) => buildVariableName("甲方", suffix)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOurPartyAutoFillIndexedVariableNames(maxCount = OUR_PARTY_AUTO_FILL_MAX_COUNT) {
|
||||||
|
const names = [];
|
||||||
|
for (let index = 1; index <= maxCount; index += 1) {
|
||||||
|
OUR_PARTY_AUTO_FILL_FIELD_MAPPINGS.forEach((mapping) => {
|
||||||
|
(mapping.suffixes || []).forEach((suffix) => {
|
||||||
|
names.push(buildVariableName(`甲方${index}`, suffix));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOurPartyAutoFillListVariableNames() {
|
||||||
|
return OUR_PARTY_AUTO_FILL_FIELD_MAPPINGS.map((mapping) =>
|
||||||
|
buildVariableName("甲方", `${getPrimarySuffix(mapping)}列表`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOurPartyAutoFillVariableNames() {
|
||||||
|
return [
|
||||||
|
...getOurPartyAutoFillSingleVariableNames(),
|
||||||
|
...getOurPartyAutoFillIndexedVariableNames(),
|
||||||
|
...getOurPartyAutoFillListVariableNames(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOurPartyAutoFillFieldOptions() {
|
||||||
|
return getOurPartyAutoFillVariableNames().map((name) => ({
|
||||||
|
label: `${name}(甲方自动带出)`,
|
||||||
|
value: name,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildOurPartyAutoFillValues(source = {}) {
|
||||||
|
const parties = getPartyList(source);
|
||||||
|
const values = {};
|
||||||
|
|
||||||
|
parties.slice(0, OUR_PARTY_AUTO_FILL_MAX_COUNT).forEach((party, index) => {
|
||||||
|
const prefix = `甲方${index + 1}`;
|
||||||
|
OUR_PARTY_AUTO_FILL_FIELD_MAPPINGS.forEach((mapping) => {
|
||||||
|
const value = getPartyValue(party, mapping);
|
||||||
|
if (!value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(mapping.suffixes || []).forEach((suffix) => {
|
||||||
|
values[buildVariableName(prefix, suffix)] = value;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
OUR_PARTY_AUTO_FILL_FIELD_MAPPINGS.forEach((mapping) => {
|
||||||
|
const listValue = parties
|
||||||
|
.map((party) => getPartyValue(party, mapping))
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(LIST_SEPARATOR);
|
||||||
|
if (listValue) {
|
||||||
|
values[buildVariableName("甲方", `${getPrimarySuffix(mapping)}列表`)] = listValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (parties.length === 1) {
|
||||||
|
OUR_PARTY_AUTO_FILL_FIELD_MAPPINGS.forEach((mapping) => {
|
||||||
|
const value = getPartyValue(parties[0], mapping);
|
||||||
|
if (!value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(mapping.suffixes || []).forEach((suffix) => {
|
||||||
|
values[buildVariableName("甲方", suffix)] = value;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
|||||||
|
export const TEMPLATE_FIELD_TYPE_OPTIONS = [
|
||||||
|
{ label: "输入框", value: "input" },
|
||||||
|
{ label: "下拉框", value: "select" },
|
||||||
|
{ label: "富文本", value: "rich_text" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function normalizeTemplateVariableMapping(item = {}, index = 0) {
|
||||||
|
const variableKey = String(item.variableKey || "").trim();
|
||||||
|
const bindField = String(item.bindField || variableKey || "").trim();
|
||||||
|
const fieldType = normalizeFieldType(item.fieldType);
|
||||||
|
return {
|
||||||
|
id: item.id || `var_${Date.now()}_${index}`,
|
||||||
|
variableKey,
|
||||||
|
bindField,
|
||||||
|
fieldLabel: String(item.fieldLabel || variableKey || bindField || "").trim(),
|
||||||
|
fieldType,
|
||||||
|
required: item.required !== false,
|
||||||
|
placeholder: String(item.placeholder || "").trim(),
|
||||||
|
dictType: String(item.dictType || "").trim(),
|
||||||
|
remark: String(item.remark || "").trim(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createEmptyTemplateVariableMapping(index = 0) {
|
||||||
|
return normalizeTemplateVariableMapping({}, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildTemplateLaunchFieldList(variableMappings = [], templateFieldLabelMap = {}) {
|
||||||
|
const fieldMap = new Map();
|
||||||
|
variableMappings.forEach((item, index) => {
|
||||||
|
const mapping = normalizeTemplateVariableMapping(item, index);
|
||||||
|
const fieldKey = mapping.bindField || mapping.variableKey;
|
||||||
|
if (!fieldKey || fieldMap.has(fieldKey)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fieldMap.set(fieldKey, {
|
||||||
|
key: fieldKey,
|
||||||
|
label:
|
||||||
|
mapping.fieldLabel ||
|
||||||
|
templateFieldLabelMap[fieldKey] ||
|
||||||
|
mapping.variableKey ||
|
||||||
|
fieldKey,
|
||||||
|
fieldType: mapping.fieldType,
|
||||||
|
required: mapping.required !== false,
|
||||||
|
placeholder: mapping.placeholder || buildDefaultPlaceholder(mapping),
|
||||||
|
dictType: mapping.dictType || "",
|
||||||
|
remark: mapping.remark || "",
|
||||||
|
variableKey: mapping.variableKey || fieldKey,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return [...fieldMap.values()];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeFieldType(fieldType) {
|
||||||
|
const value = String(fieldType || "input").trim();
|
||||||
|
if (value === "select") {
|
||||||
|
return "select";
|
||||||
|
}
|
||||||
|
if (value === "rich_text") {
|
||||||
|
return "rich_text";
|
||||||
|
}
|
||||||
|
return "input";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildDefaultPlaceholder(mapping = {}) {
|
||||||
|
const label = mapping.fieldLabel || mapping.variableKey || mapping.bindField || "内容";
|
||||||
|
if (mapping.fieldType === "select") {
|
||||||
|
return `请选择${label}`;
|
||||||
|
}
|
||||||
|
if (mapping.fieldType === "rich_text") {
|
||||||
|
return `请输入${label},支持表格`;
|
||||||
|
}
|
||||||
|
return `请输入${label}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stringifyTemplateFieldValue(value, fieldType = "input") {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (fieldType === "rich_text") {
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
return String(value).trim();
|
||||||
|
}
|
||||||
@@ -0,0 +1,772 @@
|
|||||||
|
<template>
|
||||||
|
<section class="section-container">
|
||||||
|
<a-card :bordered="false" class="query-card" :body-style="queryCardBodyStyle">
|
||||||
|
<div ref="queryContainer" class="section-query-container">
|
||||||
|
<a-form :model="queryParam" layout="inline">
|
||||||
|
<a-row :gutter="16" style="width: 100%">
|
||||||
|
<a-col :md="6" :sm="24">
|
||||||
|
<a-form-item label="合同名称">
|
||||||
|
<a-input v-model:value="queryParam.contractName" allow-clear placeholder="请输入合同名称" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :md="6" :sm="24">
|
||||||
|
<a-form-item label="合同编码">
|
||||||
|
<a-input v-model:value="queryParam.contractCode" allow-clear placeholder="请输入合同编码" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :md="6" :sm="24">
|
||||||
|
<a-form-item label="合同类型">
|
||||||
|
<a-select
|
||||||
|
v-model:value="queryParam.contractType"
|
||||||
|
allow-clear
|
||||||
|
placeholder="请选择合同类型"
|
||||||
|
:options="contractTypeOptions"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :md="6" :sm="24">
|
||||||
|
<a-form-item label="签署状态">
|
||||||
|
<a-select
|
||||||
|
v-model:value="queryParam.status"
|
||||||
|
allow-clear
|
||||||
|
placeholder="全部签署阶段"
|
||||||
|
:options="signStatusOptions"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :md="12" :sm="24">
|
||||||
|
<a-form-item label="发起日期">
|
||||||
|
<a-range-picker
|
||||||
|
v-model:value="queryParam.dateRange"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<a-card :bordered="false" class="table-card" :body-style="tableCardBodyStyle">
|
||||||
|
<vxe-toolbar ref="toolbarRef" custom>
|
||||||
|
<template #buttons>
|
||||||
|
<a-space wrap>
|
||||||
|
<a-button type="primary" :icon="h(SearchOutlined)" @click="handleQuery">查询</a-button>
|
||||||
|
<a-button :icon="h(RedoOutlined)" @click="handleReset">重置</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</vxe-toolbar>
|
||||||
|
|
||||||
|
<vxe-table
|
||||||
|
ref="tableRef"
|
||||||
|
border
|
||||||
|
:height="tableHeight"
|
||||||
|
:row-config="{ isHover: true, height: 44 }"
|
||||||
|
:column-config="{ resizable: true, minWidth: 120 }"
|
||||||
|
:custom-config="{ storage: true }"
|
||||||
|
:scroll-y="{ enabled: true, gt: 0, mode: 'wheel' }"
|
||||||
|
:data="tableData"
|
||||||
|
:loading="loading"
|
||||||
|
:loading-config="{ icon: 'vxe-icon-indicator roll', text: '正在拼命加载中...' }"
|
||||||
|
>
|
||||||
|
<vxe-column type="seq" title="序号" width="68" />
|
||||||
|
<vxe-column field="contractCode" title="合同编码" width="160" show-overflow="title" />
|
||||||
|
<vxe-column field="lawContractCode" title="法务合同编号" width="160" show-overflow="title" />
|
||||||
|
<vxe-column field="contractName" title="合同名称" width="220" show-overflow="title" />
|
||||||
|
<vxe-column field="contractType" title="合同类型" width="160" show-overflow="title">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ getContractTypeSimpleLabel(row.contractTypeCode || row.contractType) }}
|
||||||
|
</template>
|
||||||
|
</vxe-column>
|
||||||
|
<vxe-column field="isElectron" title="签署方式" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<a-tag :color="String(row.isElectron || '1') === '1' ? 'processing' : 'default'">
|
||||||
|
{{ String(row.isElectron || '1') === '1' ? '电子合同' : '纸质合同' }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
</vxe-column>
|
||||||
|
<vxe-column field="counterpartySummary" title="合同相对方" width="260" show-overflow="title" />
|
||||||
|
<vxe-column field="status" title="状态" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<a-tag :color="getStatusColor(row.status)">{{ getStatusLabelByRow(row) }}</a-tag>
|
||||||
|
</template>
|
||||||
|
</vxe-column>
|
||||||
|
<vxe-column field="approveMessage" title="审批说明" width="220" show-overflow="title" />
|
||||||
|
<vxe-column field="initiator" title="发起人" width="100" show-overflow="title" />
|
||||||
|
<vxe-column field="initiatorDept" title="所属部门" width="140" show-overflow="title" />
|
||||||
|
<vxe-column field="createTime" title="发起时间" width="170" show-overflow="title" />
|
||||||
|
<vxe-column field="updateTime" title="最后更新时间" width="170" show-overflow="title" />
|
||||||
|
<vxe-column title="操作" width="760" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="table-actions">
|
||||||
|
<a @click="handleView(row)">查看</a>
|
||||||
|
<a v-if="canViewApprove(row)" @click="handleViewApprove(row)">审批记录</a>
|
||||||
|
<a v-if="canViewLawCallback(row)" @click="handleViewLawCallback(row)">回调data</a>
|
||||||
|
<a v-if="canConfigSealPosition(row)" @click="handleConfigSealPosition(row)">配置签章位</a>
|
||||||
|
<a v-if="canOnlineSign(row)" @click="handleOnlineSign(row)">在线签订</a>
|
||||||
|
<a v-if="canUploadSigned(row)" @click="handleUploadSigned(row)">上传已签署版本</a>
|
||||||
|
<a v-if="canMockFinishSign(row)" @click="handleMockFinishSign(row)">测试签署完成</a>
|
||||||
|
<a v-if="canDownloadSigned(row)" @click="handleDownloadSigned(row)">下载签署文件</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</vxe-column>
|
||||||
|
</vxe-table>
|
||||||
|
|
||||||
|
<a-pagination
|
||||||
|
id="pageWrapper"
|
||||||
|
ref="pageWrapperRef"
|
||||||
|
v-model:pageSize="queryParam.pageSize"
|
||||||
|
v-model:current="queryParam.pageNo"
|
||||||
|
class="table-pagination"
|
||||||
|
show-size-changer
|
||||||
|
:page-size-options="['10', '20', '50', '100']"
|
||||||
|
:total="total"
|
||||||
|
:show-total="(value) => `共 ${value} 条`"
|
||||||
|
@change="onShowSizeChange"
|
||||||
|
@show-size-change="onShowSizeChange"
|
||||||
|
/>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<ContractPartyPlaceholderModal ref="contractModalRef" />
|
||||||
|
<SealPositionConfigModal ref="sealPositionConfigModalRef" @saved="handleSealPositionSaved" />
|
||||||
|
<MixedSignModal ref="mixedSignModalRef" @business-updated="handleSignBusinessUpdated" />
|
||||||
|
<UploadSignedModal ref="uploadSignedModalRef" @done="handleSignBusinessUpdated" />
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { h, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
|
import { RedoOutlined, SearchOutlined } from '@ant-design/icons-vue'
|
||||||
|
import { message, Modal } from 'ant-design-vue'
|
||||||
|
import { getCurrentMockUser } from '@/api/mockUser'
|
||||||
|
import ContractPartyPlaceholderModal from '../launch/components/ContractPartyPlaceholderModal.vue'
|
||||||
|
import SealPositionConfigModal from '../launch/components/SealPositionConfigModal.vue'
|
||||||
|
import MixedSignModal from '../launch/components/mixedSignModal.vue'
|
||||||
|
import UploadSignedModal from '../launch/components/UploadSignedModal.vue'
|
||||||
|
import {
|
||||||
|
contractTypeOptions,
|
||||||
|
getContractTypeSimpleLabel,
|
||||||
|
} from '../launch/contractLaunchOptions.js'
|
||||||
|
import {
|
||||||
|
downloadLaunchFile,
|
||||||
|
finishLaunchSeal,
|
||||||
|
getLaunchDetail,
|
||||||
|
getLaunchSealPositionPage,
|
||||||
|
getLawApproveCallbackSample,
|
||||||
|
queryApproveViewUrl,
|
||||||
|
queryLaunchList,
|
||||||
|
uploadWatermarkFile,
|
||||||
|
} from '@/api/launch.js'
|
||||||
|
|
||||||
|
const SIGN_STAGE_STATUSES = ['pending_sign', 'signing']
|
||||||
|
const SIGN_LIST_QUERY_PAGE_SIZE = 200
|
||||||
|
const signStatusOptions = [
|
||||||
|
{ label: '待签署', value: 'pending_sign' },
|
||||||
|
{ label: '签署中', value: 'signing' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const contractModalRef = ref(null)
|
||||||
|
const sealPositionConfigModalRef = ref(null)
|
||||||
|
const mixedSignModalRef = ref(null)
|
||||||
|
const uploadSignedModalRef = ref(null)
|
||||||
|
const queryContainer = ref(null)
|
||||||
|
const pageWrapperRef = ref(null)
|
||||||
|
const tableRef = ref(null)
|
||||||
|
const toolbarRef = ref(null)
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const currentMockUser = ref(null)
|
||||||
|
const total = ref(0)
|
||||||
|
const tableHeight = ref(420)
|
||||||
|
const tableData = ref([])
|
||||||
|
const allRecords = ref([])
|
||||||
|
|
||||||
|
const queryParam = ref({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
contractName: undefined,
|
||||||
|
contractCode: undefined,
|
||||||
|
contractType: undefined,
|
||||||
|
status: undefined,
|
||||||
|
dateRange: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
const queryCardBodyStyle = {
|
||||||
|
padding: '20px 24px 6px',
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableCardBodyStyle = {
|
||||||
|
display: 'flex',
|
||||||
|
flexFlow: 'column nowrap',
|
||||||
|
minHeight: '0',
|
||||||
|
padding: '8px 12px 6px',
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusLabelMap = {
|
||||||
|
pending_sign: '待签署',
|
||||||
|
signing: '签署中',
|
||||||
|
pending_collect: '待采集',
|
||||||
|
pending_archive: '待归档',
|
||||||
|
completed: '已完成',
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusColorMap = {
|
||||||
|
pending_sign: 'warning',
|
||||||
|
signing: 'processing',
|
||||||
|
pending_collect: 'cyan',
|
||||||
|
pending_archive: 'orange',
|
||||||
|
completed: 'success',
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSuccessResponse(res) {
|
||||||
|
return String(res?.code || '') === '0000'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPageResult(res) {
|
||||||
|
return res?.result || {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDataResult(res) {
|
||||||
|
return res?.result || null
|
||||||
|
}
|
||||||
|
|
||||||
|
function getResponseMessage(res, fallback = '操作失败') {
|
||||||
|
return res?.message || res?.msg || fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
function sliceDateValue(value) {
|
||||||
|
return value ? String(value).slice(0, 10) : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function todayString() {
|
||||||
|
const now = new Date()
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(now.getDate()).padStart(2, '0')
|
||||||
|
return `${now.getFullYear()}-${month}-${day}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDateTimeValue(value) {
|
||||||
|
return value ? `${value} 00:00:00` : null
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectToolbar() {
|
||||||
|
const table = tableRef.value
|
||||||
|
const toolbar = toolbarRef.value
|
||||||
|
if (table && toolbar && table.connect) {
|
||||||
|
table.connect(toolbar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusLabel(status) {
|
||||||
|
return statusLabelMap[status] || status
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusColor(status) {
|
||||||
|
return statusColorMap[status] || 'default'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusLabelByRow(row) {
|
||||||
|
return getStatusLabel(row?.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSingleAgreementRecord(row) {
|
||||||
|
return String(row?.isSingleAgreement || '0') === '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
function isManageOperator() {
|
||||||
|
if (currentMockUser.value?.superAdmin) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return String(currentMockUser.value?.systemType || '').toLowerCase() === 'manage'
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSignOperator() {
|
||||||
|
if (currentMockUser.value?.superAdmin) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const systemType = String(currentMockUser.value?.systemType || '').toLowerCase()
|
||||||
|
return systemType === 'manage' || systemType === 'business'
|
||||||
|
}
|
||||||
|
|
||||||
|
function canConfigSealPosition(row) {
|
||||||
|
return isManageOperator()
|
||||||
|
&& !isSingleAgreementRecord(row)
|
||||||
|
&& String(row.isElectron || '1') === '1'
|
||||||
|
&& row.status === 'pending_sign'
|
||||||
|
}
|
||||||
|
|
||||||
|
function canOnlineSign(row) {
|
||||||
|
return isSignOperator()
|
||||||
|
&& !isSingleAgreementRecord(row)
|
||||||
|
&& String(row.isElectron || '1') === '1'
|
||||||
|
&& SIGN_STAGE_STATUSES.includes(row.status)
|
||||||
|
&& String(row.sealPosConfigured || 'N') === 'Y'
|
||||||
|
}
|
||||||
|
|
||||||
|
function canUploadSigned(row) {
|
||||||
|
return String(row.isElectron || '1') === '0' && SIGN_STAGE_STATUSES.includes(row.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
function canMockFinishSign(row) {
|
||||||
|
return String(row.isElectron || '1') === '1' && SIGN_STAGE_STATUSES.includes(row.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
function canDownloadSigned(row) {
|
||||||
|
return Boolean(row.signedFilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
function canViewApprove(row) {
|
||||||
|
return !isSingleAgreementRecord(row) && Boolean(row.lawContractId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function canViewLawCallback(row) {
|
||||||
|
return !isSingleAgreementRecord(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildQueryPayload(status, pageNo = 1, pageSize = SIGN_LIST_QUERY_PAGE_SIZE) {
|
||||||
|
return {
|
||||||
|
pageNo,
|
||||||
|
pageSize,
|
||||||
|
contractName: queryParam.value.contractName,
|
||||||
|
contractCode: queryParam.value.contractCode,
|
||||||
|
contractType: queryParam.value.contractType,
|
||||||
|
status,
|
||||||
|
dateStart: queryParam.value.dateRange?.[0],
|
||||||
|
dateEnd: queryParam.value.dateRange?.[1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchRecordsByStatus(status) {
|
||||||
|
let pageNo = 1
|
||||||
|
let totalCount = 0
|
||||||
|
const records = []
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const res = await queryLaunchList(buildQueryPayload(status, pageNo))
|
||||||
|
if (!isSuccessResponse(res)) {
|
||||||
|
throw new Error(getResponseMessage(res, '合同签署列表查询失败'))
|
||||||
|
}
|
||||||
|
const pageResult = getPageResult(res)
|
||||||
|
const data = Array.isArray(pageResult?.data) ? pageResult.data : []
|
||||||
|
totalCount = Number(pageResult?.totalSize ?? pageResult?.total ?? 0)
|
||||||
|
records.push(...data)
|
||||||
|
if (!data.length) break
|
||||||
|
if (totalCount > 0 && records.length >= totalCount) break
|
||||||
|
if (data.length < SIGN_LIST_QUERY_PAGE_SIZE) break
|
||||||
|
pageNo += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareRecord(a, b) {
|
||||||
|
const aTime = new Date(a?.createTime || a?.updateTime || 0).getTime()
|
||||||
|
const bTime = new Date(b?.createTime || b?.updateTime || 0).getTime()
|
||||||
|
if (aTime !== bTime) {
|
||||||
|
return bTime - aTime
|
||||||
|
}
|
||||||
|
return Number(b?.id || 0) - Number(a?.id || 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshTableData() {
|
||||||
|
total.value = allRecords.value.length
|
||||||
|
const pageNo = Number(queryParam.value.pageNo || 1)
|
||||||
|
const pageSize = Number(queryParam.value.pageSize || 10)
|
||||||
|
const startIndex = (pageNo - 1) * pageSize
|
||||||
|
tableData.value = allRecords.value.slice(startIndex, startIndex + pageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getList() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const statuses = queryParam.value.status ? [queryParam.value.status] : SIGN_STAGE_STATUSES
|
||||||
|
const mergedMap = new Map()
|
||||||
|
for (const status of statuses) {
|
||||||
|
const records = await fetchRecordsByStatus(status)
|
||||||
|
records.forEach((item) => {
|
||||||
|
if (!mergedMap.has(item.id)) {
|
||||||
|
mergedMap.set(item.id, item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
allRecords.value = Array.from(mergedMap.values()).sort(compareRecord)
|
||||||
|
refreshTableData()
|
||||||
|
} catch (error) {
|
||||||
|
tableData.value = []
|
||||||
|
allRecords.value = []
|
||||||
|
total.value = 0
|
||||||
|
message.error(error?.message || '合同签署列表查询失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleQuery() {
|
||||||
|
queryParam.value.pageNo = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleReset() {
|
||||||
|
queryParam.value = {
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
contractName: undefined,
|
||||||
|
contractCode: undefined,
|
||||||
|
contractType: undefined,
|
||||||
|
status: undefined,
|
||||||
|
dateRange: [],
|
||||||
|
}
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onShowSizeChange(current, pageSize) {
|
||||||
|
queryParam.value.pageNo = current
|
||||||
|
queryParam.value.pageSize = pageSize
|
||||||
|
refreshTableData()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleView(row) {
|
||||||
|
getLaunchDetail(row.id).then((res) => {
|
||||||
|
if (!isSuccessResponse(res) || !getDataResult(res)) {
|
||||||
|
message.error(getResponseMessage(res, '获取合同详情失败'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
contractModalRef.value?.showModal(getDataResult(res), 'view')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleViewApprove(row) {
|
||||||
|
if (!row.lawContractId) {
|
||||||
|
message.warning('当前暂无法务合同ID,审批记录地址尚未生成')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
queryApproveViewUrl({
|
||||||
|
contractId: String(row.lawContractId),
|
||||||
|
ivUser: row.initiator || 'admin',
|
||||||
|
}).then((res) => {
|
||||||
|
if (!isSuccessResponse(res)) {
|
||||||
|
message.error(getResponseMessage(res, '获取审批记录查看地址失败'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const approveViewUrl = res?.result?.approveViewUrl
|
||||||
|
if (!approveViewUrl) {
|
||||||
|
message.warning('审批记录地址为空')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
window.open(approveViewUrl, '_blank')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildLawCallbackModalContent(sample) {
|
||||||
|
const approvedJson = JSON.stringify({ data: sample?.approvedData || {} }, null, 2)
|
||||||
|
const rejectedJson = JSON.stringify({ data: sample?.rejectedData || {} }, null, 2)
|
||||||
|
return h('div', { style: { display: 'flex', flexDirection: 'column', gap: '12px' } }, [
|
||||||
|
h('div', [
|
||||||
|
h('div', { style: { marginBottom: '6px', fontWeight: 600 } }, '回调地址'),
|
||||||
|
h('div', { style: { color: '#666', wordBreak: 'break-all' } }, `${sample?.callbackMethod || 'POST'} ${sample?.callbackUrl || '/contract/launch/callback/law/approve'}`),
|
||||||
|
]),
|
||||||
|
h('div', [
|
||||||
|
h('div', { style: { marginBottom: '6px', fontWeight: 600 } }, '审批通过 data'),
|
||||||
|
h('pre', {
|
||||||
|
style: {
|
||||||
|
margin: 0,
|
||||||
|
maxHeight: '260px',
|
||||||
|
overflow: 'auto',
|
||||||
|
padding: '12px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
background: '#f6f8fa',
|
||||||
|
fontSize: '12px',
|
||||||
|
lineHeight: '1.6',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
},
|
||||||
|
}, approvedJson),
|
||||||
|
]),
|
||||||
|
h('div', [
|
||||||
|
h('div', { style: { marginBottom: '6px', fontWeight: 600 } }, '审批驳回 data'),
|
||||||
|
h('pre', {
|
||||||
|
style: {
|
||||||
|
margin: 0,
|
||||||
|
maxHeight: '220px',
|
||||||
|
overflow: 'auto',
|
||||||
|
padding: '12px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
background: '#f6f8fa',
|
||||||
|
fontSize: '12px',
|
||||||
|
lineHeight: '1.6',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
},
|
||||||
|
}, rejectedJson),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleViewLawCallback(row) {
|
||||||
|
getLawApproveCallbackSample(row.id).then((res) => {
|
||||||
|
if (!isSuccessResponse(res) || !getDataResult(res)) {
|
||||||
|
message.error(getResponseMessage(res, '获取法务回调样例失败'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const sample = getDataResult(res)
|
||||||
|
Modal.info({
|
||||||
|
title: `法务回调 data 结构:${row.contractName}`,
|
||||||
|
width: 900,
|
||||||
|
okText: '知道了',
|
||||||
|
content: buildLawCallbackModalContent(sample),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMockFinishSign(row) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '测试操作:模拟签署完成',
|
||||||
|
content: `模拟合同【${row.contractName}】签署完成,状态将变为「待采集」。`,
|
||||||
|
okText: '确认模拟',
|
||||||
|
cancelText: '取消',
|
||||||
|
onOk: () => {
|
||||||
|
const signDate = sliceDateValue(row.signDate) || todayString()
|
||||||
|
const sealDate = sliceDateValue(row.sealDate) || signDate
|
||||||
|
const activeDate = sliceDateValue(row.activeDate) || sliceDateValue(row.effectiveStart) || signDate
|
||||||
|
return finishLaunchSeal(row.id, {
|
||||||
|
sealContractId: row.sealContractId || `TEST-SEAL-${row.id}`,
|
||||||
|
sealContractStatus: 2000,
|
||||||
|
signedFileName: `${row.contractName || '合同'}-测试已签署.pdf`,
|
||||||
|
signedFilePath: row.contractTextFilePath || `mock-signed/${row.id}.pdf`,
|
||||||
|
signedFileType: 'pdf',
|
||||||
|
signedFileSize: row.contractTextFileSize || null,
|
||||||
|
signedOfflineReason: '测试按钮模拟签署完成,真实签章接入后可删除此按钮',
|
||||||
|
signDate: formatDateTimeValue(signDate),
|
||||||
|
sealDate: formatDateTimeValue(sealDate),
|
||||||
|
activeDate: formatDateTimeValue(activeDate),
|
||||||
|
}).then((res) => {
|
||||||
|
if (!isSuccessResponse(res)) {
|
||||||
|
message.error(getResponseMessage(res, '测试签署完成失败'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
message.success('测试签署完成,合同已流转到待采集')
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
icon: null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDownloadSigned(row) {
|
||||||
|
const filePath = row.signedFilePath
|
||||||
|
const fileName = row.signedFileName || `${row.contractName || '合同'}-已签署版.pdf`
|
||||||
|
if (!filePath) {
|
||||||
|
message.warning('暂无签署文件可下载')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
downloadLaunchFile(filePath)
|
||||||
|
.then((res) => {
|
||||||
|
const arrayBuffer = res?.data ?? res
|
||||||
|
const blob = new Blob([arrayBuffer])
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = fileName
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
message.error('签署文件下载失败')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUploadSigned(row) {
|
||||||
|
uploadSignedModalRef.value?.showModal(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureDetailReady(row, actionLabel) {
|
||||||
|
const detailRes = await getLaunchDetail(row.id)
|
||||||
|
if (!isSuccessResponse(detailRes) || !getDataResult(detailRes)) {
|
||||||
|
message.error(getResponseMessage(detailRes, '获取合同详情失败'))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
let detail = getDataResult(detailRes)
|
||||||
|
if (detail.contractTextFilePath) {
|
||||||
|
return detail
|
||||||
|
}
|
||||||
|
const uploaded = await handleWatermarkUpload(row, actionLabel)
|
||||||
|
if (!uploaded) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const reloadRes = await getLaunchDetail(row.id)
|
||||||
|
if (!isSuccessResponse(reloadRes) || !getDataResult(reloadRes)) {
|
||||||
|
message.error(getResponseMessage(reloadRes, '获取合同详情失败'))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
detail = getDataResult(reloadRes)
|
||||||
|
return detail
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleConfigSealPosition(row) {
|
||||||
|
const detail = await ensureDetailReady(row, '配置签章位')
|
||||||
|
if (!detail) return
|
||||||
|
sealPositionConfigModalRef.value?.showModal(detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleOnlineSign(row) {
|
||||||
|
const detail = await ensureDetailReady(row, '在线签订')
|
||||||
|
if (!detail) return
|
||||||
|
try {
|
||||||
|
const pageRes = await getLaunchSealPositionPage(row.id)
|
||||||
|
if (!isSuccessResponse(pageRes) || !getDataResult(pageRes)) {
|
||||||
|
message.error(getResponseMessage(pageRes, '获取签章位失败'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const page = getDataResult(pageRes)
|
||||||
|
if (!Array.isArray(page.positions) || !page.positions.length) {
|
||||||
|
message.warning('当前合同还未配置签章位,请先点击“配置签章位”')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!Array.isArray(page.availablePartyIds) || !page.availablePartyIds.length) {
|
||||||
|
message.warning('当前用户未匹配到可签署方,暂时不能在线签订')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mixedSignModalRef.value?.showModal({
|
||||||
|
...detail,
|
||||||
|
contractId: detail.sealContractId,
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
message.error('获取签章位失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleWatermarkUpload(row, actionLabel = '在线签订') {
|
||||||
|
let file
|
||||||
|
const ok = await new Promise((resolve) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '法务水印文件缺失',
|
||||||
|
content: h('div', [
|
||||||
|
h('p', { style: 'margin-bottom: 12px' }, `法务审批回调未带回带水印的合同文件,请先上传后再${actionLabel}。`),
|
||||||
|
h('input', {
|
||||||
|
type: 'file',
|
||||||
|
accept: '.pdf,.doc,.docx',
|
||||||
|
style: 'margin-top: 8px; width: 100%',
|
||||||
|
onChange(e) {
|
||||||
|
file = e.target.files?.[0]
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
okText: '上传并继续',
|
||||||
|
cancelText: '取消',
|
||||||
|
async onOk() {
|
||||||
|
if (!file) {
|
||||||
|
message.warning('请先选择文件')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
try {
|
||||||
|
const res = await uploadWatermarkFile(row.id, formData)
|
||||||
|
if (!isSuccessResponse(res)) {
|
||||||
|
message.error(getResponseMessage(res, '上传水印文件失败'))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
message.success('水印文件上传成功')
|
||||||
|
resolve(true)
|
||||||
|
} catch {
|
||||||
|
message.error('上传水印文件失败')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
resolve(false)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSealPositionSaved() {
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSignBusinessUpdated() {
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCurrentMockUser() {
|
||||||
|
try {
|
||||||
|
currentMockUser.value = await getCurrentMockUser()
|
||||||
|
} catch {
|
||||||
|
currentMockUser.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncTableHeight() {
|
||||||
|
nextTick(() => {
|
||||||
|
const viewportHeight = window.innerHeight
|
||||||
|
const queryHeight = queryContainer.value?.offsetHeight || 0
|
||||||
|
const pageHeight = pageWrapperRef.value?.$el?.offsetHeight || 56
|
||||||
|
tableHeight.value = Math.max(320, viewportHeight - queryHeight - pageHeight - 320)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadCurrentMockUser()
|
||||||
|
handleQuery()
|
||||||
|
nextTick(() => {
|
||||||
|
connectToolbar()
|
||||||
|
syncTableHeight()
|
||||||
|
})
|
||||||
|
window.addEventListener('resize', syncTableHeight)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('resize', syncTableHeight)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.section-container {
|
||||||
|
height: calc(100vh - 116px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-card {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-card {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-query-container :deep(.ant-form-item) {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-query-container :deep(.ant-form-item .ant-form-item-control) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 12px 4px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
.section-container {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -35,6 +35,10 @@ export default defineConfig({
|
|||||||
target: 'http://127.0.0.1:8899',
|
target: 'http://127.0.0.1:8899',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
|
'/mock': {
|
||||||
|
target: 'http://127.0.0.1:8899',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
'/trans': {
|
'/trans': {
|
||||||
target: 'http://127.0.0.1:8899',
|
target: 'http://127.0.0.1:8899',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user