Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f571704614 | ||
|
|
c3da8055ec | ||
|
|
852b446901 | ||
|
|
225a7a1932 | ||
|
|
466ac71f80 | ||
|
|
e7ac5f3bbf | ||
|
|
a173cf7044 | ||
|
|
809c07264f | ||
|
|
a01a36e1b7 | ||
|
|
79f9124c9a | ||
|
|
848cabd0fa | ||
|
|
86a75d5c28 | ||
|
|
660a95c9ed | ||
|
|
ebe08f0c4f | ||
|
|
e5d9cea7b6 | ||
|
|
4d52bceea5 | ||
|
|
3c03907e69 | ||
|
|
fce1099b38 | ||
|
|
9927382054 | ||
|
|
69c1430836 | ||
|
|
d1aca2113d | ||
|
|
a062c996c7 | ||
|
|
d34a501df9 | ||
|
|
947a8a27d3 | ||
|
|
cf269a2f10 | ||
|
|
da317b9460 | ||
|
|
c360d00cac | ||
|
|
60f01ac6d4 | ||
|
|
e9ed63646a | ||
|
|
5d5996b915 | ||
|
|
a54a56865a | ||
|
|
45bd3bc8f5 | ||
|
|
6f3613133b | ||
|
|
7238aabd0a | ||
|
|
3eb98be172 | ||
|
|
63013bbd67 | ||
|
|
ad1cd77a48 | ||
|
|
57ba3a72b8 | ||
|
|
443ee0038b | ||
|
|
33db8595ec | ||
|
|
0dee240c6e | ||
|
|
69f97a740d | ||
|
|
c4303b4d5f | ||
|
|
7b57bb9b05 | ||
|
|
feab964397 | ||
|
|
0320c8d8bc | ||
|
|
2dc2d4887d | ||
|
|
05c6cf6b1c | ||
|
|
722fbd988c | ||
|
|
5554cf1548 | ||
|
|
37369a8e9a | ||
|
|
22747f7c87 | ||
|
|
5aec2d7d1f | ||
|
|
1a1be2f9d3 | ||
| df67f63137 | |||
|
|
0f7efec0e1 | ||
|
|
2171c83d55 | ||
|
|
db40b1733b | ||
|
|
fe56422336 | ||
|
|
dc395406c3 | ||
|
|
41db731e40 | ||
|
|
35fcd40bd3 | ||
|
|
e27e44a3c3 | ||
|
|
47463c9b14 | ||
|
|
3f8b6e4695 | ||
| 2bf7b6872a | |||
|
|
a3cb2263fd | ||
|
|
d8273464c5 |
@@ -1,15 +1,19 @@
|
||||
# 环境变量 (命名必须以 VITE_ 开头)
|
||||
|
||||
# 是否在打包时启用 Mock
|
||||
VITE_BUILD_MOCK = false
|
||||
# 接口前缀
|
||||
VITE_API_PREFIX = '/dev-api'
|
||||
|
||||
# 接口地址
|
||||
VITE_API_BASE_URL = 'https://api.continew.top'
|
||||
VITE_API_WS_URL = 'wss://api.continew.top'
|
||||
VITE_API_BASE_URL = 'http://localhost:6609'
|
||||
|
||||
# 接口地址 (WebSocket)
|
||||
VITE_API_WS_URL = 'ws://localhost:6609'
|
||||
|
||||
# 地址前缀
|
||||
VITE_BASE = '/'
|
||||
|
||||
# 是否开启开发者工具
|
||||
VITE_OPEN_DEVTOOLS = false
|
||||
|
||||
# 应用配置面板
|
||||
VITE_APP_SETTING = true
|
||||
|
||||
|
||||
17
.env.test
17
.env.test
@@ -1,22 +1,21 @@
|
||||
# 环境变量 (命名必须以 VITE_ 开头)
|
||||
|
||||
# 是否在打包时启用 Mock
|
||||
VITE_BUILD_MOCK = true
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_PREFIX = '/test-api'
|
||||
VITE_API_PREFIX = '/dev-api'
|
||||
|
||||
# 接口地址
|
||||
VITE_API_BASE_URL = 'http://localhost:6609'
|
||||
|
||||
# 接口地址 (WebSocket)
|
||||
VITE_API_WS_URL = 'ws://localhost:6609'
|
||||
|
||||
# 地址前缀
|
||||
VITE_BASE = '/test'
|
||||
VITE_BASE = '/'
|
||||
|
||||
# 是否开启开发者工具
|
||||
VITE_OPEN_DEVTOOLS = true
|
||||
VITE_OPEN_DEVTOOLS = false
|
||||
|
||||
# 应用配置面板
|
||||
VITE_APP_SETTING = false
|
||||
VITE_APP_SETTING = true
|
||||
|
||||
# 终端ID
|
||||
VITE_CLIENT_ID = 'ef51c9a3e9046c4f2ea45142c8a8344a'
|
||||
VITE_CLIENT_ID = 'ef51c9a3e9046c4f2ea45142c8a8344a'
|
||||
285
package-lock.json
generated
285
package-lock.json
generated
@@ -37,6 +37,7 @@
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.0.16",
|
||||
"pinia-plugin-persistedstate": "^3.1.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"qs": "^6.11.2",
|
||||
"query-string": "^9.0.0",
|
||||
"v-viewer": "^3.0.10",
|
||||
@@ -61,6 +62,7 @@
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.2.5",
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"@types/query-string": "^6.3.0",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
@@ -3272,6 +3274,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/qrcode": {
|
||||
"version": "1.5.6",
|
||||
"resolved": "https://registry.npmmirror.com/@types/qrcode/-/qrcode-1.5.6.tgz",
|
||||
"integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/query-string": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/@types/query-string/-/query-string-6.3.0.tgz",
|
||||
@@ -6005,6 +6017,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/decode-uri-component": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.4.1.tgz",
|
||||
@@ -6154,6 +6175,12 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/dijkstrajs": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
|
||||
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/doctrine": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz",
|
||||
@@ -8447,7 +8474,6 @@
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
@@ -11236,7 +11262,6 @@
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -11413,7 +11438,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -11555,6 +11579,15 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/pngjs/-/pngjs-5.0.0.tgz",
|
||||
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/posix-character-classes": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
|
||||
@@ -11941,6 +11974,233 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmmirror.com/qrcode/-/qrcode-1.5.4.tgz",
|
||||
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dijkstrajs": "^1.0.1",
|
||||
"pngjs": "^5.0.0",
|
||||
"yargs": "^15.3.1"
|
||||
},
|
||||
"bin": {
|
||||
"qrcode": "bin/qrcode"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz",
|
||||
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/qrcode/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/qrcode/node_modules/find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz",
|
||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"locate-path": "^5.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz",
|
||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-locate": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-try": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/p-locate": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz",
|
||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-limit": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/y18n": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz",
|
||||
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/qrcode/node_modules/yargs": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz",
|
||||
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/yargs-parser": {
|
||||
"version": "18.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.0.tgz",
|
||||
@@ -12335,7 +12595,6 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -12352,6 +12611,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-main-filename": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/resize-detector": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/resize-detector/-/resize-detector-0.3.0.tgz",
|
||||
@@ -12875,6 +13140,12 @@
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
@@ -16154,6 +16425,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/which-module": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz",
|
||||
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/which-typed-array": {
|
||||
"version": "1.1.19",
|
||||
"resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.19.tgz",
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.0.16",
|
||||
"pinia-plugin-persistedstate": "^3.1.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"qs": "^6.11.2",
|
||||
"query-string": "^9.0.0",
|
||||
"v-viewer": "^3.0.10",
|
||||
@@ -67,6 +68,7 @@
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.2.5",
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"@types/query-string": "^6.3.0",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
|
||||
@@ -83,11 +83,6 @@ export interface CardLoginReq extends AuthReq {
|
||||
cardNumber: string
|
||||
}
|
||||
|
||||
/** 邮箱登录请求参数 */
|
||||
export interface CardLoginReq extends AuthReq {
|
||||
card: string
|
||||
}
|
||||
|
||||
/** 登录响应类型 */
|
||||
export interface LoginResp {
|
||||
token: string
|
||||
|
||||
62
src/apis/fullWorkOrder/fullWorkOrder.ts
Normal file
62
src/apis/fullWorkOrder/fullWorkOrder.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import http from '@/utils/http'
|
||||
|
||||
const BASE_URL = '/fullWorkOrder/fullWorkOrder'
|
||||
|
||||
export interface FullWorkOrderResp {
|
||||
id: string
|
||||
title: string
|
||||
orderNo: string
|
||||
materialCode: string
|
||||
encodingPrint: string
|
||||
materialName: string
|
||||
batch: string
|
||||
mark: string
|
||||
count: number
|
||||
imgUrl: string
|
||||
createUser: string
|
||||
createTime: string
|
||||
updateUser: string
|
||||
updateTime: string
|
||||
createUserString: string
|
||||
updateUserString: string
|
||||
disabled: boolean
|
||||
}
|
||||
export interface FullWorkOrderQuery {
|
||||
orderNo: string | undefined
|
||||
materialCode: string | undefined
|
||||
materialName: string | undefined
|
||||
batch: string | undefined
|
||||
createTime: Array<string> | undefined
|
||||
sort: Array<string>
|
||||
}
|
||||
export interface FullWorkOrderPageQuery extends FullWorkOrderQuery, PageQuery {}
|
||||
|
||||
/** @desc 查询整箱领取记录列表 */
|
||||
export function listFullWorkOrder(query: FullWorkOrderPageQuery) {
|
||||
return http.get<PageRes<FullWorkOrderResp[]>>(`${BASE_URL}`, query)
|
||||
}
|
||||
|
||||
/** @desc 新增整箱领取记录 */
|
||||
export function addFullWorkOrder(data: any) {
|
||||
return http.post(`${BASE_URL}`, data)
|
||||
}
|
||||
|
||||
/** @desc 删除整箱领取记录 */
|
||||
export function deleteFullWorkOrder(id: string) {
|
||||
return http.del(`${BASE_URL}/${id}`)
|
||||
}
|
||||
|
||||
/** @desc 导出整箱领取记录 */
|
||||
export function exportFullWorkOrder(query: FullWorkOrderQuery) {
|
||||
return http.download(`${BASE_URL}/export`, query)
|
||||
}
|
||||
|
||||
/** @desc 保存原材料详情 */
|
||||
export function saveFullWorkOrderDetail(data: any) {
|
||||
return http.post(`${BASE_URL}/saveInfo`, data)
|
||||
}
|
||||
|
||||
/** @desc 获取原材料详情列表 */
|
||||
export function getFullWorkOrderDetailList(id: string) {
|
||||
return http.get(`${BASE_URL}/infos/${id}`)
|
||||
}
|
||||
@@ -3,8 +3,10 @@ import http from '@/utils/http'
|
||||
const BASE_URL = '/admin/materialInfo'
|
||||
|
||||
export interface MaterialInfoResp {
|
||||
id: string
|
||||
materialName: string
|
||||
encoding: string
|
||||
encodingPrint: string
|
||||
unitWeight: string
|
||||
materialSpec: string
|
||||
photoUrl: string
|
||||
@@ -12,11 +14,18 @@ export interface MaterialInfoResp {
|
||||
createTime: string
|
||||
createUserString: string
|
||||
updateUserString: string
|
||||
lightLevel: number
|
||||
batch: string
|
||||
mark: string
|
||||
disabled: boolean
|
||||
photoLoadError: boolean
|
||||
}
|
||||
export interface MaterialInfoQuery {
|
||||
materialName: string | undefined
|
||||
encoding: string | undefined
|
||||
encodingPrint: string | undefined
|
||||
batch: string | undefined
|
||||
mark: string | undefined
|
||||
sort: Array<string>
|
||||
}
|
||||
|
||||
@@ -35,9 +44,6 @@ export function listMaterialInfo(query: MaterialInfoPageQuery) {
|
||||
return http.get<PageRes<MaterialInfoResp[]>>(`${BASE_URL}`, query)
|
||||
}
|
||||
|
||||
interface MaterialInfoDetailResp {
|
||||
}
|
||||
|
||||
/** @desc 下载物料信息导入模板 */
|
||||
export function downloadMaterialInfoImportTemplate() {
|
||||
return http.download(`${BASE_URL}/import/template`)
|
||||
@@ -45,7 +51,7 @@ export function downloadMaterialInfoImportTemplate() {
|
||||
|
||||
/** @desc 查询物料信息详情 */
|
||||
export function getMaterialInfo(id: string) {
|
||||
return http.get<MaterialInfoDetailResp>(`${BASE_URL}/${id}`)
|
||||
return http.get<MaterialInfoResp>(`${BASE_URL}/${id}`)
|
||||
}
|
||||
|
||||
/** @desc 新增物料信息 */
|
||||
@@ -59,7 +65,7 @@ export function updateMaterialInfo(data: any, id: string) {
|
||||
}
|
||||
|
||||
/** @desc 删除物料信息 */
|
||||
export function deleteMaterialInfo(id: string) {
|
||||
export function deleteMaterialInfo(id: string | Array<string>) {
|
||||
return http.del(`${BASE_URL}/${id}`)
|
||||
}
|
||||
|
||||
@@ -87,3 +93,26 @@ export function uploadMaterialPhotos(data: FormData) {
|
||||
export function catchPhoto(data: FormData) {
|
||||
return http.post(`${BASE_URL}/import/catch`, data)
|
||||
}
|
||||
|
||||
/* 批次导入结果类型 */
|
||||
export interface BatchImportResp {
|
||||
importKey: string
|
||||
totalRows: number
|
||||
validRows: number
|
||||
duplicateRows: number
|
||||
}
|
||||
|
||||
/** @desc 下载批次导入模板 */
|
||||
export function downloadBatchImportTemplate() {
|
||||
return http.download(`${BASE_URL}/batch/import/template`)
|
||||
}
|
||||
|
||||
/** @desc 解析批次导入数据 */
|
||||
export function parseBatchImport(data: FormData) {
|
||||
return http.post(`${BASE_URL}/batch/import/parse`, data)
|
||||
}
|
||||
|
||||
/** @desc 批次导入 */
|
||||
export function batchImport(data: any) {
|
||||
return http.post(`${BASE_URL}/batch/import`, data)
|
||||
}
|
||||
|
||||
68
src/apis/materialProcess/materialProcess.ts
Normal file
68
src/apis/materialProcess/materialProcess.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import http from '@/utils/http'
|
||||
import type {LabelValueState} from "@/types/global";
|
||||
|
||||
const BASE_URL = '/materialProcess/materialProcess'
|
||||
|
||||
export interface MaterialProcessResp {
|
||||
id: string
|
||||
processName: string
|
||||
processCode: string
|
||||
createTime: string
|
||||
updateTime: string
|
||||
createUser: string
|
||||
updateUser: string
|
||||
createUserString: string
|
||||
updateUserString: string
|
||||
disabled: boolean
|
||||
}
|
||||
export interface MaterialProcessQuery {
|
||||
processName: string | undefined
|
||||
processCode: string | undefined
|
||||
sort: Array<string>
|
||||
}
|
||||
export interface MaterialProcessPageQuery extends MaterialProcessQuery, PageQuery {}
|
||||
|
||||
/** @desc 查询海康物料流程列表 */
|
||||
export function listMaterialProcess(query: MaterialProcessPageQuery) {
|
||||
return http.get<PageRes<MaterialProcessResp[]>>(`${BASE_URL}`, query)
|
||||
}
|
||||
|
||||
/** @desc 查询海康物料流程详情 */
|
||||
export function selectList() {
|
||||
return http.get(`${BASE_URL}/selectList`)
|
||||
}
|
||||
|
||||
/** @desc 新增海康物料流程 */
|
||||
export function addMaterialProcess(data: any) {
|
||||
return http.post(`${BASE_URL}`, data)
|
||||
}
|
||||
|
||||
/** @desc 修改海康物料流程 */
|
||||
export function updateMaterialProcess(data: any, id: string) {
|
||||
return http.put(`${BASE_URL}/${id}`, data)
|
||||
}
|
||||
|
||||
/** @desc 删除海康物料流程 */
|
||||
export function deleteMaterialProcess(id: string | Array<string>) {
|
||||
return http.del(`${BASE_URL}/${id}`)
|
||||
}
|
||||
|
||||
/** @desc 导出海康物料流程 */
|
||||
export function exportMaterialProcess(query: MaterialProcessQuery) {
|
||||
return http.download(`${BASE_URL}/export`, query)
|
||||
}
|
||||
|
||||
/** @desc 下载物料流程导入模板 */
|
||||
export function downloadMaterialProcessImportTemplate() {
|
||||
return http.download(`${BASE_URL}/import/template`)
|
||||
}
|
||||
|
||||
/** @desc 解析物料流程导入数据 */
|
||||
export function parseImportMaterialProcess(data: FormData) {
|
||||
return http.post(`${BASE_URL}/import/parse`, data)
|
||||
}
|
||||
|
||||
/** @desc 导入物料流程 */
|
||||
export function importMaterialProcess(data: any) {
|
||||
return http.post(`${BASE_URL}/import`, data)
|
||||
}
|
||||
80
src/apis/materialType/materialType.ts
Normal file
80
src/apis/materialType/materialType.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import http from '@/utils/http'
|
||||
|
||||
const BASE_URL = '/materialType/materialType'
|
||||
|
||||
export interface MaterialTypeResp {
|
||||
id: string
|
||||
typeName: string
|
||||
upFloatRatio: string
|
||||
downFloatRatio: string
|
||||
createTime: string
|
||||
updateTime: string
|
||||
createUser: string
|
||||
updateUser: string
|
||||
createUserString: string
|
||||
updateUserString: string
|
||||
disabled: boolean
|
||||
}
|
||||
export interface MaterialTypeQuery {
|
||||
typeName: string | undefined
|
||||
sort: Array<string>
|
||||
}
|
||||
export interface MaterialTypePageQuery extends MaterialTypeQuery, PageQuery {}
|
||||
|
||||
export interface MaterialTypeImportResp {
|
||||
importKey: string
|
||||
totalRows: number
|
||||
validRows: number
|
||||
duplicateNameRows: number
|
||||
}
|
||||
|
||||
export interface MaterialTypeImportResult {
|
||||
insertRows: number
|
||||
updateRows: number
|
||||
totalRows: number
|
||||
}
|
||||
|
||||
/** @desc 查询物料品类列表 */
|
||||
export function listMaterialType(query: MaterialTypePageQuery) {
|
||||
return http.get<PageRes<MaterialTypeResp[]>>(`${BASE_URL}`, query)
|
||||
}
|
||||
|
||||
/** @desc 新增物料品类 */
|
||||
export function addMaterialType(data: any) {
|
||||
return http.post(`${BASE_URL}`, data)
|
||||
}
|
||||
|
||||
/** @desc 查询物料品类 */
|
||||
export function selectList() {
|
||||
return http.get(`${BASE_URL}/selectList`)
|
||||
}
|
||||
|
||||
/** @desc 修改物料品类 */
|
||||
export function updateMaterialType(data: any, id: string) {
|
||||
return http.put(`${BASE_URL}/${id}`, data)
|
||||
}
|
||||
|
||||
/** @desc 删除物料品类 */
|
||||
export function deleteMaterialType(id: string) {
|
||||
return http.del(`${BASE_URL}/${id}`)
|
||||
}
|
||||
|
||||
/** @desc 导出物料品类 */
|
||||
export function exportMaterialType(query: MaterialTypeQuery) {
|
||||
return http.download(`${BASE_URL}/export`, query)
|
||||
}
|
||||
|
||||
/** @desc 下载物料品类导入模板 */
|
||||
export function downloadMaterialTypeImportTemplate() {
|
||||
return http.download(`${BASE_URL}/importTemplate`)
|
||||
}
|
||||
|
||||
/** @desc 解析物料品类导入数据 */
|
||||
export function parseImportMaterialType(data: FormData) {
|
||||
return http.post<MaterialTypeImportResp>(`${BASE_URL}/parseImport`, data)
|
||||
}
|
||||
|
||||
/** @desc 导入物料品类 */
|
||||
export function importMaterialType(data: any) {
|
||||
return http.post<MaterialTypeImportResult>(`${BASE_URL}/import`, data)
|
||||
}
|
||||
23
src/apis/weightManage/light.ts
Normal file
23
src/apis/weightManage/light.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import http from '@/utils/http'
|
||||
|
||||
const BASE_URL = '/api/light'
|
||||
|
||||
/** @desc 连接灯光 */
|
||||
export function connect() {
|
||||
return http.post<any>(`${BASE_URL}/connect`)
|
||||
}
|
||||
|
||||
/** @desc 断开灯光连接 */
|
||||
export function disconnect() {
|
||||
return http.post<any>(`${BASE_URL}/disconnect`)
|
||||
}
|
||||
|
||||
/** @desc 设置灯光亮度 */
|
||||
export function brightness(materialId: string) {
|
||||
return http.post<any>(`${BASE_URL}/brightness`, { materialId: Number(materialId) })
|
||||
}
|
||||
|
||||
/** @desc 检查灯光状态 */
|
||||
export function status() {
|
||||
return http.get<any>(`${BASE_URL}/status`)
|
||||
}
|
||||
@@ -1,15 +1,21 @@
|
||||
import http from '@/utils/http'
|
||||
|
||||
const BASE_URL = '/weighManage/material'
|
||||
const BASE_URL = '/weighManage/workOrder'
|
||||
|
||||
export interface WeighManageResp {
|
||||
id: string
|
||||
encoding: string
|
||||
encodingPrint: string
|
||||
materialName: string
|
||||
materialSpec: string
|
||||
unitWeight: number
|
||||
photoUrl: string
|
||||
batch: string
|
||||
mark: string
|
||||
materialProcess: string
|
||||
matchResult: string
|
||||
downFloatRatio: string
|
||||
upFloatRatio: string
|
||||
}
|
||||
|
||||
export interface WeighManageQuery {
|
||||
@@ -21,7 +27,32 @@ export function getMaterialDetail(code: string) {
|
||||
return http.get<WeighManageResp>(`/admin/materialInfo/code/${code}`)
|
||||
}
|
||||
|
||||
/** @desc 新增人员管理 */
|
||||
export function addPeople(data: any) {
|
||||
return http.post(`${BASE_URL}`, data)
|
||||
/** @desc 校验称重信息 */
|
||||
export function validateWeighing(data: any) {
|
||||
return http.post(`${BASE_URL}/validateWeighing`, data)
|
||||
}
|
||||
|
||||
/** @desc 校验物料是否一致 */
|
||||
export function vmSend(materialCode: string) {
|
||||
return http.post<string>(`/vm/send`, { materialCode })
|
||||
}
|
||||
|
||||
/** @desc vm定时任务保存图片 */
|
||||
export function getVmSaveImageTask() {
|
||||
return http.post<any>(`/vm/start`)
|
||||
}
|
||||
|
||||
/** @desc vm关闭定时任务 */
|
||||
export function getVmCloseTask() {
|
||||
return http.post<any>(`/vm/stop`)
|
||||
}
|
||||
|
||||
/** @desc 启动电子称连接线程 */
|
||||
export function weighAHStart() {
|
||||
return http.post<any>(`/api/weigh/ah/init`)
|
||||
}
|
||||
|
||||
/** @desc 终止电子称连接线程 */
|
||||
export function weighAHStop() {
|
||||
return http.post<any>(`/api/weigh/ah/destroy`)
|
||||
}
|
||||
|
||||
23
src/apis/weightManage/ys.ts
Normal file
23
src/apis/weightManage/ys.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import http from '@/utils/http'
|
||||
|
||||
const BASE_URL = '/api/ys'
|
||||
|
||||
/** @desc 启动宇视SDK */
|
||||
export function getEnterWeighPage() {
|
||||
return http.get<any>(`${BASE_URL}/enter-weigh-page`)
|
||||
}
|
||||
|
||||
/** @desc 退出宇视SDK */
|
||||
export function getLeaveWeighPage() {
|
||||
return http.get<any>(`${BASE_URL}/leave-weigh-page`)
|
||||
}
|
||||
|
||||
/** @desc 抓拍图片 */
|
||||
export function getCaptureImage(data: any) {
|
||||
return http.get<any>(`${BASE_URL}/capture-image`, data)
|
||||
}
|
||||
|
||||
/** @desc 检查宇视SDK状态 */
|
||||
export function getCheckStatus() {
|
||||
return http.get<any>(`${BASE_URL}/status`)
|
||||
}
|
||||
@@ -11,11 +11,16 @@ export interface WorkOrderResp {
|
||||
unitWeight: string
|
||||
materialSpec: string
|
||||
photoUrl: string
|
||||
batch: string
|
||||
totalWeight: string
|
||||
totalCalculatedWeight: string
|
||||
totalCount: string
|
||||
createUserString: string
|
||||
updateUserString: string
|
||||
matchResult: string
|
||||
workOrderInfos: Array<WorkOrderInfoResp>
|
||||
qrCodeData: string
|
||||
mark: string
|
||||
}
|
||||
|
||||
export interface WorkOrderInfoResp {
|
||||
@@ -23,15 +28,19 @@ export interface WorkOrderInfoResp {
|
||||
workOrderId: string
|
||||
materialId: string
|
||||
weightTime: string
|
||||
batch: string
|
||||
quantity: string
|
||||
weight: string
|
||||
imgUrl: string
|
||||
calculatedWeight: string
|
||||
weightQuantity: string
|
||||
mark: string
|
||||
}
|
||||
|
||||
export interface WorkOrderQuery {
|
||||
orderNo: string | undefined
|
||||
materialName: string | undefined
|
||||
batch: string | undefined
|
||||
encoding: string | undefined
|
||||
userName: string | undefined
|
||||
carNo: string | undefined
|
||||
@@ -46,9 +55,14 @@ export function listWorkOrder(query: WorkOrderPageQuery) {
|
||||
return http.get<PageRes<WorkOrderResp[]>>(`${BASE_URL}`, query)
|
||||
}
|
||||
|
||||
/** @desc 查询工作订单详情 */
|
||||
export function getWorkOrderInfos(id: string) {
|
||||
return http.get<Array<WorkOrderInfoResp>>(`${BASE_URL}/info/${id}`)
|
||||
}
|
||||
|
||||
/** @desc 查询工作订单详情 */
|
||||
export function getWorkOrder(id: string) {
|
||||
return http.get<Array<WorkOrderInfoResp>>(`${BASE_URL}/${id}`)
|
||||
return http.get<WorkOrderResp>(`${BASE_URL}/${id}`)
|
||||
}
|
||||
|
||||
/** @desc 新增工作订单 */
|
||||
@@ -65,3 +79,8 @@ export function deleteWorkOrder(ids: string | Array<string>) {
|
||||
export function exportWorkOrder(query: WorkOrderQuery) {
|
||||
return http.download(`${BASE_URL}/export`, query)
|
||||
}
|
||||
|
||||
/** @desc 保存打印记录 */
|
||||
export function savePrintRecord(data: any) {
|
||||
return http.post(`/print/print`, data)
|
||||
}
|
||||
|
||||
BIN
src/assets/wav/tooLess.wav
Normal file
BIN
src/assets/wav/tooLess.wav
Normal file
Binary file not shown.
BIN
src/assets/wav/tooMany.wav
Normal file
BIN
src/assets/wav/tooMany.wav
Normal file
Binary file not shown.
@@ -38,11 +38,11 @@ export const FileIcon: FileExtendNameIconMap = {
|
||||
}
|
||||
|
||||
/** 图片类型 */
|
||||
export const ImageTypes = ['jpg', 'png', 'gif', 'jpeg']
|
||||
export const ImageTypes = ['jpg', 'png', 'gif', 'jpeg', 'bmp']
|
||||
|
||||
/** WPS、Office文件类型 */
|
||||
export const OfficeTypes = ['ppt', 'pptx', 'doc', 'docx', 'xls', 'xlsx', 'pdf']
|
||||
|
||||
export const WordTypes = ['doc', 'docx']
|
||||
|
||||
export const ExcelTypes = ['xls', 'xlsx']
|
||||
export const ExcelTypes = ['xls', 'xlsx']
|
||||
22
src/hooks/app/materialProcess.ts
Normal file
22
src/hooks/app/materialProcess.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ref } from 'vue'
|
||||
import { selectList } from '@/apis/materialProcess/materialProcess'
|
||||
import type { LabelValueState } from '@/types/global'
|
||||
|
||||
/** 物料品类模块 */
|
||||
export function materialProcess(options?: { onSuccess?: () => void }) {
|
||||
const loading = ref(false)
|
||||
const materialProcessList = ref<LabelValueState[]>([])
|
||||
|
||||
const getMaterialProcessSelect = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await selectList()
|
||||
materialProcessList.value = res.data
|
||||
// eslint-disable-next-line ts/no-unused-expressions
|
||||
options?.onSuccess && options.onSuccess()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
return { materialProcessList, getMaterialProcessSelect, loading }
|
||||
}
|
||||
22
src/hooks/app/materialType.ts
Normal file
22
src/hooks/app/materialType.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ref } from 'vue'
|
||||
import { selectList } from '@/apis/materialType/materialType'
|
||||
import type { LabelValueState } from '@/types/global'
|
||||
|
||||
/** 物料品类模块 */
|
||||
export function materialType(options?: { onSuccess?: () => void }) {
|
||||
const loading = ref(false)
|
||||
const materialTypeList = ref<LabelValueState[]>([])
|
||||
|
||||
const getMaterialTypeSelect = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await selectList()
|
||||
materialTypeList.value = res.data
|
||||
// eslint-disable-next-line ts/no-unused-expressions
|
||||
options?.onSuccess && options.onSuccess()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
return { materialTypeList, getMaterialTypeSelect, loading }
|
||||
}
|
||||
28
src/types/components.d.ts
vendored
28
src/types/components.d.ts
vendored
@@ -13,8 +13,15 @@ declare module 'vue' {
|
||||
ABreadcrumb: typeof import('@arco-design/web-vue')['Breadcrumb']
|
||||
ABreadcrumbItem: typeof import('@arco-design/web-vue')['BreadcrumbItem']
|
||||
AButton: typeof import('@arco-design/web-vue')['Button']
|
||||
AButtonGroup: typeof import('@arco-design/web-vue')['ButtonGroup']
|
||||
ACard: typeof import('@arco-design/web-vue')['Card']
|
||||
ACardMeta: typeof import('@arco-design/web-vue')['CardMeta']
|
||||
ACarousel: typeof import('@arco-design/web-vue')['Carousel']
|
||||
ACarouselItem: typeof import('@arco-design/web-vue')['CarouselItem']
|
||||
ACheckbox: typeof import('@arco-design/web-vue')['Checkbox']
|
||||
ACheckboxGroup: typeof import('@arco-design/web-vue')['CheckboxGroup']
|
||||
ACol: typeof import('@arco-design/web-vue')['Col']
|
||||
AColorPicker: typeof import('@arco-design/web-vue')['ColorPicker']
|
||||
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider']
|
||||
ADatePicker: typeof import('@arco-design/web-vue')['DatePicker']
|
||||
ADescriptions: typeof import('@arco-design/web-vue')['Descriptions']
|
||||
@@ -26,36 +33,55 @@ declare module 'vue' {
|
||||
AEmpty: typeof import('@arco-design/web-vue')['Empty']
|
||||
AForm: typeof import('@arco-design/web-vue')['Form']
|
||||
AFormItem: typeof import('@arco-design/web-vue')['FormItem']
|
||||
AIcon: typeof import('@arco-design/web-vue')['Icon']
|
||||
AGrid: typeof import('@arco-design/web-vue')['Grid']
|
||||
AGridItem: typeof import('@arco-design/web-vue')['GridItem']
|
||||
AImage: typeof import('@arco-design/web-vue')['Image']
|
||||
AInput: typeof import('@arco-design/web-vue')['Input']
|
||||
AInputGroup: typeof import('@arco-design/web-vue')['InputGroup']
|
||||
AInputNumber: typeof import('@arco-design/web-vue')['InputNumber']
|
||||
AInputPassword: typeof import('@arco-design/web-vue')['InputPassword']
|
||||
AInputSearch: typeof import('@arco-design/web-vue')['InputSearch']
|
||||
ALayout: typeof import('@arco-design/web-vue')['Layout']
|
||||
ALayoutContent: typeof import('@arco-design/web-vue')['LayoutContent']
|
||||
ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader']
|
||||
ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider']
|
||||
ALink: typeof import('@arco-design/web-vue')['Link']
|
||||
AMenu: typeof import('@arco-design/web-vue')['Menu']
|
||||
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
|
||||
AModal: typeof import('@arco-design/web-vue')['Modal']
|
||||
AOption: typeof import('@arco-design/web-vue')['Option']
|
||||
AOverflowList: typeof import('@arco-design/web-vue')['OverflowList']
|
||||
APagination: typeof import('@arco-design/web-vue')['Pagination']
|
||||
APopconfirm: typeof import('@arco-design/web-vue')['Popconfirm']
|
||||
APopover: typeof import('@arco-design/web-vue')['Popover']
|
||||
AProgress: typeof import('@arco-design/web-vue')['Progress']
|
||||
ARadio: typeof import('@arco-design/web-vue')['Radio']
|
||||
ARadioGroup: typeof import('@arco-design/web-vue')['RadioGroup']
|
||||
ARangePicker: typeof import('@arco-design/web-vue')['RangePicker']
|
||||
ARow: typeof import('@arco-design/web-vue')['Row']
|
||||
AScrollbar: typeof import('@arco-design/web-vue')['Scrollbar']
|
||||
ASelect: typeof import('@arco-design/web-vue')['Select']
|
||||
ASkeleton: typeof import('@arco-design/web-vue')['Skeleton']
|
||||
ASkeletonLine: typeof import('@arco-design/web-vue')['SkeletonLine']
|
||||
ASpace: typeof import('@arco-design/web-vue')['Space']
|
||||
ASpin: typeof import('@arco-design/web-vue')['Spin']
|
||||
AStatistic: typeof import('@arco-design/web-vue')['Statistic']
|
||||
ASubMenu: typeof import('@arco-design/web-vue')['SubMenu']
|
||||
ASwitch: typeof import('@arco-design/web-vue')['Switch']
|
||||
ATable: typeof import('@arco-design/web-vue')['Table']
|
||||
ATableColumn: typeof import('@arco-design/web-vue')['TableColumn']
|
||||
ATabPane: typeof import('@arco-design/web-vue')['TabPane']
|
||||
ATabs: typeof import('@arco-design/web-vue')['Tabs']
|
||||
ATag: typeof import('@arco-design/web-vue')['Tag']
|
||||
ATextarea: typeof import('@arco-design/web-vue')['Textarea']
|
||||
ATooltip: typeof import('@arco-design/web-vue')['Tooltip']
|
||||
ATree: typeof import('@arco-design/web-vue')['Tree']
|
||||
ATreeSelect: typeof import('@arco-design/web-vue')['TreeSelect']
|
||||
ATrigger: typeof import('@arco-design/web-vue')['Trigger']
|
||||
ATypographyParagraph: typeof import('@arco-design/web-vue')['TypographyParagraph']
|
||||
ATypographyText: typeof import('@arco-design/web-vue')['TypographyText']
|
||||
ATypographyTitle: typeof import('@arco-design/web-vue')['TypographyTitle']
|
||||
AUpload: typeof import('@arco-design/web-vue')['Upload']
|
||||
Avatar: typeof import('./../components/Avatar/index.vue')['default']
|
||||
AWatermark: typeof import('@arco-design/web-vue')['Watermark']
|
||||
Breadcrumb: typeof import('./../components/Breadcrumb/index.vue')['default']
|
||||
|
||||
25
src/utils/qrCodeGenerator.js
Normal file
25
src/utils/qrCodeGenerator.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import QRCode from 'qrcode'
|
||||
|
||||
/**
|
||||
* 生成二维码Base64图片
|
||||
* @param {string} data - 要编码的二维码数据
|
||||
* @param {Object} options - 二维码选项
|
||||
* @returns {Promise<string>} Base64编码的二维码图片
|
||||
*/
|
||||
export async function generateQRCode(data, options = {}) {
|
||||
try {
|
||||
const defaultOptions = {
|
||||
width: 120,
|
||||
margin: 1,
|
||||
color: {
|
||||
dark: '#000000',
|
||||
light: '#FFFFFF',
|
||||
},
|
||||
}
|
||||
const finalOptions = { ...defaultOptions, ...options }
|
||||
return await QRCode.toDataURL(data, finalOptions)
|
||||
} catch (error) {
|
||||
console.error('生成二维码失败:', error)
|
||||
return ''
|
||||
}
|
||||
}
|
||||
776
src/views/barcodePrint/index.vue
Normal file
776
src/views/barcodePrint/index.vue
Normal file
@@ -0,0 +1,776 @@
|
||||
<template>
|
||||
<div class="gi_page">
|
||||
<div class="container">
|
||||
<h2 class="page-title">标签打印</h2>
|
||||
|
||||
<!-- 标签参数设置 -->
|
||||
<div class="form-section">
|
||||
<a-form :model="formData" layout="vertical">
|
||||
<div class="form-grid">
|
||||
<div class="form-grid-item">
|
||||
<a-form-item label="物料名称">
|
||||
<a-input v-model="formData.materialName" placeholder="未获取到物料名称" :disabled="true" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div class="form-grid-item">
|
||||
<a-form-item label="物料编码">
|
||||
<a-input v-model="formData.encoding" placeholder="未获取到物料编码" :disabled="true" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div class="form-grid-item">
|
||||
<a-form-item label="工单编号">
|
||||
<a-input v-model="formData.orderNo" placeholder="未获取到工单编号" :disabled="true" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div class="form-grid-item">
|
||||
<a-form-item label="生产批次">
|
||||
<a-input v-model="formData.batch" placeholder="未获取到生产批次" :disabled="true" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<div class="form-grid-item">
|
||||
<a-form-item label="打印编码(国产替代)">
|
||||
<a-input v-model="formData.encodingPrint" allow-clear placeholder="请输入打印编码" @change="getMaterialName"/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div class="form-grid-item">
|
||||
<a-form-item label="打印物料名称(国产替代)">
|
||||
<a-input v-model="formData.materialNamePrint" allow-clear placeholder="请输入打印物料名称"/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</a-form>
|
||||
<div class="form-actions">
|
||||
<a-button type="primary" @click="generateDetailLabel">明细标签</a-button>
|
||||
<a-button type="primary" @click="generateOverallLabel">整体标签</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 标签预览 -->
|
||||
<div v-if="labelDataList.length > 0 || labelData.partName" class="label-preview-section">
|
||||
<h3>标签预览</h3>
|
||||
<div class="label-container" ref="labelContainer">
|
||||
<!-- 明细标签:显示多个标签 -->
|
||||
<template v-if="labelDataList.length > 0">
|
||||
<div class="label" v-for="(item, index) in labelDataList" :key="index">
|
||||
<table class="label-table">
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">零件名称</div>
|
||||
<div class="label-value">{{ item.partName }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">生产日期</div>
|
||||
<div class="label-value">{{ item.productionDate }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell qr-cell" rowspan="4">
|
||||
<div class="qr-code">
|
||||
<img v-if="item.qrCodeImage" :src="item.qrCodeImage" alt="QR Code" />
|
||||
<div class="mark-number">{{ item.mark || '' }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">零件号</div>
|
||||
<div class="label-value">{{ item.partNumber }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">数量</div>
|
||||
<div class="label-value">{{ item.totalCount }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">标重(g)</div>
|
||||
<div class="label-value">{{ item.totalCalculatedWeight }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">包装签字</div>
|
||||
<div class="label-value">{{ item.packingSignature || '' }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">实重(g)</div>
|
||||
<div class="label-value">{{ item.totalWeight || '' }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">检验签字</div>
|
||||
<div class="label-value">{{ item.inspectionSignature || '' }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 整体标签:显示单个标签 -->
|
||||
<template v-else>
|
||||
<div class="label">
|
||||
<table class="label-table">
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">零件名称</div>
|
||||
<div class="label-value">{{ labelData.partName }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">生产日期</div>
|
||||
<div class="label-value">{{ labelData.productionDate }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell qr-cell" rowspan="4">
|
||||
<div class="qr-code">
|
||||
<img v-if="labelData.qrCodeImage" :src="labelData.qrCodeImage" alt="QR Code" />
|
||||
<div class="mark-number">{{ labelData.mark || '' }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">零件号</div>
|
||||
<div class="label-value">{{ labelData.partNumber }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">数量</div>
|
||||
<div class="label-value">{{ labelData.totalCount }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">标重(g)</div>
|
||||
<div class="label-value">{{ labelData.totalCalculatedWeight }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">包装签字</div>
|
||||
<div class="label-value">{{ labelData.packingSignature || '' }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">实重(g)</div>
|
||||
<div class="label-value">{{ labelData.totalWeight || '' }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">检验签字</div>
|
||||
<div class="label-value">{{ labelData.inspectionSignature || '' }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="label-actions">
|
||||
<a-button type="primary" @click="printLabel">打印标签</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import {getWorkOrder, savePrintRecord, type WorkOrderInfoResp} from "@/apis/workOrder/workOrder"
|
||||
import QRCode from 'qrcode';
|
||||
import {getMaterialDetail} from "@/apis/weightManage/weightManage";
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
workerOrderId: '',
|
||||
encoding: '',
|
||||
encodingPrint: '',
|
||||
materialNamePrint: '',
|
||||
materialName: '',
|
||||
orderNo: '',
|
||||
totalCalculatedWeight: '',
|
||||
totalWeight: '',
|
||||
totalCount: '',
|
||||
batch: '',
|
||||
mark: '',
|
||||
qrCodeData: '',
|
||||
workOrderInfos: Array<WorkOrderInfoResp>(),
|
||||
})
|
||||
|
||||
// 标签数据数组
|
||||
const labelDataList = reactive<Array<{
|
||||
partName: string
|
||||
partNumber: string
|
||||
totalCalculatedWeight: string
|
||||
totalWeight: string
|
||||
productionDate: string
|
||||
totalCount: string
|
||||
packingSignature: string
|
||||
inspectionSignature: string
|
||||
qrCodeData: string
|
||||
qrCodeImage: string
|
||||
mark: string
|
||||
}>>([])
|
||||
|
||||
// 标签数据(用于整体标签)
|
||||
const labelData = reactive({
|
||||
partName: '',
|
||||
partNumber: '',
|
||||
totalCalculatedWeight: '',
|
||||
totalWeight: '',
|
||||
productionDate: '',
|
||||
totalCount: '',
|
||||
packingSignature: '',
|
||||
inspectionSignature: '',
|
||||
qrCodeData: '',
|
||||
qrCodeImage: '',
|
||||
mark: '',
|
||||
})
|
||||
|
||||
// 标签容器引用
|
||||
const labelContainer = ref<HTMLElement | null>(null)
|
||||
|
||||
const getMaterialName = async (value: string) => {
|
||||
try {
|
||||
const res = await getMaterialDetail(value)
|
||||
if (res.code == '0') {
|
||||
formData.materialNamePrint = res.data.materialName || ''
|
||||
} else {
|
||||
formData.materialNamePrint = '';
|
||||
Message.error('获取物料信息失败')
|
||||
}
|
||||
} catch (error) {
|
||||
formData.materialNamePrint = '';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 生成二维码
|
||||
const generateQRCode = async (data: string) => {
|
||||
try {
|
||||
return await QRCode.toDataURL(data, {
|
||||
width: 120,
|
||||
margin: 1,
|
||||
color: {
|
||||
dark: '#000000',
|
||||
light: '#FFFFFF'
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('生成二维码失败:', error)
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
// 生成明细标签
|
||||
const generateDetailLabel = async () => {
|
||||
if (!formData.workOrderInfos || formData.workOrderInfos.length === 0) {
|
||||
Message.error('未获取到工单明细信息')
|
||||
return
|
||||
}
|
||||
|
||||
if (!formData.batch) {
|
||||
Message.error('未获取到批次信息')
|
||||
return
|
||||
}
|
||||
if (!formData.materialName) {
|
||||
Message.error('未获取到物料信息')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 清空之前的标签数据
|
||||
labelDataList.length = 0
|
||||
|
||||
// 格式化生产日期为 yyyyMMddHHmm 格式
|
||||
const now = new Date()
|
||||
const formattedDate = now.getFullYear().toString() +
|
||||
String(now.getMonth() + 1).padStart(2, '0') +
|
||||
String(now.getDate()).padStart(2, '0') +
|
||||
String(now.getHours()).padStart(2, '0') +
|
||||
String(now.getMinutes()).padStart(2, '0')
|
||||
|
||||
const formattedDate2 = now.getFullYear().toString() +
|
||||
String(now.getMonth() + 1).padStart(2, '0') +
|
||||
String(now.getDate()).padStart(2, '0')
|
||||
|
||||
// 为每个工单明细生成一个标签
|
||||
for (const workOrderInfo of formData.workOrderInfos) {
|
||||
// 计算二维码数据
|
||||
const orderNo = formData.orderNo + workOrderInfo.id;
|
||||
const qrCodeData = `10#${formData.encodingPrint || formData.encoding}$11#9DP$12#${formData.batch}$17#${workOrderInfo.quantity}$20#${formattedDate2}$31#${orderNo}$DY`;
|
||||
|
||||
// 生成二维码图片
|
||||
const qrCodeImage = await generateQRCode(qrCodeData)
|
||||
|
||||
// 添加标签数据
|
||||
labelDataList.push({
|
||||
partName: formData.materialNamePrint || formData.materialName || '',
|
||||
partNumber: formData.encodingPrint || formData.encoding || '',
|
||||
totalCalculatedWeight: workOrderInfo.calculatedWeight || '',
|
||||
totalWeight: workOrderInfo.weight || '',
|
||||
productionDate: formattedDate,
|
||||
totalCount: workOrderInfo.quantity || '',
|
||||
packingSignature: '',
|
||||
inspectionSignature: '',
|
||||
qrCodeData: qrCodeData,
|
||||
qrCodeImage: qrCodeImage,
|
||||
mark: workOrderInfo.mark || '',
|
||||
})
|
||||
}
|
||||
|
||||
Message.success(`成功生成 ${labelDataList.length} 个明细标签`)
|
||||
} catch (error) {
|
||||
console.error('生成明细标签失败:', error)
|
||||
Message.error('生成明细标签失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 生成整体标签
|
||||
const generateOverallLabel = async () => {
|
||||
if (!formData.batch) {
|
||||
Message.error('未获取到批次信息')
|
||||
return
|
||||
}
|
||||
if (!formData.materialName) {
|
||||
Message.error('未获取到物料信息')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 清空明细标签数据
|
||||
labelDataList.length = 0
|
||||
|
||||
// 格式化生产日期为 yyyyMMddHHmm 格式
|
||||
const now = new Date()
|
||||
const formattedDate = now.getFullYear().toString() +
|
||||
String(now.getMonth() + 1).padStart(2, '0') +
|
||||
String(now.getDate()).padStart(2, '0') +
|
||||
String(now.getHours()).padStart(2, '0') +
|
||||
String(now.getMinutes()).padStart(2, '0')
|
||||
|
||||
const formattedDate2 = now.getFullYear().toString() +
|
||||
String(now.getMonth() + 1).padStart(2, '0') +
|
||||
String(now.getDate()).padStart(2, '0')
|
||||
|
||||
// 计算二维码数据
|
||||
const qrCodeData = `10#${formData.encodingPrint || formData.encoding}$11#9DP$12#${formData.batch}$17#${formData.totalCount}$20#${formattedDate2}$31#${formData.orderNo}$DY`
|
||||
|
||||
// 生成二维码图片
|
||||
const qrCodeImage = await generateQRCode(qrCodeData)
|
||||
|
||||
// 直接从 formData 中获取数据
|
||||
Object.assign(labelData, {
|
||||
partName: formData.materialNamePrint || formData.materialName || '',
|
||||
partNumber: formData.encodingPrint || formData.encoding || '',
|
||||
totalCalculatedWeight: formData.totalCalculatedWeight || '',
|
||||
totalWeight: formData.totalWeight || '',
|
||||
productionDate: formattedDate,
|
||||
totalCount: formData.totalCount || '',
|
||||
packingSignature: '',
|
||||
inspectionSignature: '',
|
||||
qrCodeData: qrCodeData,
|
||||
qrCodeImage: qrCodeImage,
|
||||
mark: formData.mark || '',
|
||||
})
|
||||
|
||||
Message.success('标签生成成功')
|
||||
} catch (error) {
|
||||
console.error('生成标签失败:', error)
|
||||
Message.error('生成标签失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时,检查是否有参数传递
|
||||
onMounted(() => {
|
||||
// 从路由参数中获取数据
|
||||
const workerOrderId = route.query.workerOrderId as string
|
||||
// 如果有参数传递,设置表单数据并标记为不可修改
|
||||
if (workerOrderId) {
|
||||
formData.workerOrderId = workerOrderId
|
||||
getWorkOrder(workerOrderId).then(res => {
|
||||
if (res.code == '0') {
|
||||
formData.encoding = res.data.encoding
|
||||
formData.materialName = res.data.materialName
|
||||
formData.orderNo = res.data.orderNo
|
||||
formData.batch = res.data.batch
|
||||
formData.totalCalculatedWeight = res.data.totalCalculatedWeight
|
||||
formData.totalWeight = res.data.totalWeight
|
||||
formData.totalCount = res.data.totalCount
|
||||
formData.workOrderInfos = res.data.workOrderInfos
|
||||
formData.mark = res.data.mark
|
||||
} else {
|
||||
Message.error('获取详情失败')
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
// 打印标签
|
||||
const printLabel = async () => {
|
||||
await nextTick()
|
||||
if (labelContainer.value) {
|
||||
// 创建打印窗口
|
||||
const printWindow = window.open('', '_blank')
|
||||
if (printWindow) {
|
||||
// 判断是打印明细标签还是整体标签
|
||||
const isDetailLabels = labelDataList.length > 0
|
||||
const labelsToPrint = isDetailLabels ? labelDataList : [labelData]
|
||||
|
||||
// 生成标签HTML
|
||||
const labelsHTML = labelsToPrint.map(item => `
|
||||
<div class="label">
|
||||
<table class="label-table">
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">零件名称</div>
|
||||
<div class="label-value">${item.partName}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">生产日期</div>
|
||||
<div class="label-value">${item.productionDate}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell qr-cell" rowspan="4">
|
||||
<div class="qr-code">
|
||||
<img src="${item.qrCodeImage}" alt="QR Code" />
|
||||
<div class="mark-number">${item.mark || ''}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">零件号</div>
|
||||
<div class="label-value">${item.partNumber}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">数量</div>
|
||||
<div class="label-value">${item.totalCount}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">标重(g)</div>
|
||||
<div class="label-value">${item.totalCalculatedWeight}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">包装签字</div>
|
||||
<div class="label-value">${item.packingSignature || ''}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">实重(g)</div>
|
||||
<div class="label-value">${item.totalWeight || ''}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">检验签字</div>
|
||||
<div class="label-value">${item.inspectionSignature || ''}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
`).join('')
|
||||
|
||||
// 构建打印内容,通过 matchMedia 监听打印确认事件(仅在确认打印时保存记录)
|
||||
const printHTML = `
|
||||
<html>
|
||||
<head>
|
||||
<title>标签打印</title>
|
||||
<style>
|
||||
@page { size: 80mm 50mm; margin: 0; }
|
||||
body { margin: 0; padding: 1mm; font-family: Arial, sans-serif; }
|
||||
.label-container { display: flex; flex-wrap: wrap; gap: 1mm; justify-content: center; align-items: center; transform-origin: center; }
|
||||
.label { width: 75mm; height: 45mm; border: 1px solid #000; padding: 0; box-sizing: border-box; page-break-inside: avoid; overflow: hidden; margin: 0 auto; }
|
||||
.label-table { width: 100%; height: 100%; border-collapse: collapse; table-layout: fixed; }
|
||||
.label-cell { border: 1px solid #000; padding: 0.8mm; vertical-align: top; }
|
||||
.qr-cell { width: 22mm; text-align: center; vertical-align: middle; border: 1px solid #000; }
|
||||
.label-row { display: flex; align-items: center; }
|
||||
.label-field { font-size: 7.5pt; font-weight: bold; margin-right: 1mm; min-width: 22pt; white-space: nowrap; }
|
||||
.label-value { font-size: 7.5pt; font-weight: bold; flex: 1; overflow-wrap: break-word; word-break: break-word; }
|
||||
.qr-code img { width: 22mm; height: 22mm; margin: 0.5mm 0; }
|
||||
.mark-number { font-size: 7pt; font-weight: bold; margin-top: 0.5mm; text-align: center; }
|
||||
.serial-number { font-size: 7.5pt; font-weight: bold; margin-top: 0.5mm; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="label-container">
|
||||
${labelsHTML}
|
||||
</div>
|
||||
<script>
|
||||
// 标记是否已保存打印记录(避免重复触发)
|
||||
var printRecordSaved = false;
|
||||
// 通过 matchMedia 监听打印状态变化
|
||||
// mediaQuery.matches === true 表示浏览器进入"打印"渲染状态(用户点击了"打印/确认"按钮)
|
||||
// 取消或关闭窗口不会触发此状态切换
|
||||
var mediaQuery = window.matchMedia('print');
|
||||
var handlePrintChange = function(mql) {
|
||||
if (mql.matches && !printRecordSaved) {
|
||||
printRecordSaved = true;
|
||||
// 向父窗口发送消息,通知保存打印记录
|
||||
window.opener && window.opener.postMessage({ type: 'PRINT_CONFIRMED' }, '*');
|
||||
}
|
||||
};
|
||||
if (mediaQuery.addEventListener) {
|
||||
mediaQuery.addEventListener('change', handlePrintChange);
|
||||
} else if (mediaQuery.addListener) {
|
||||
mediaQuery.addListener(handlePrintChange);
|
||||
}
|
||||
<\/script>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
printWindow.document.write(printHTML)
|
||||
printWindow.document.close()
|
||||
|
||||
// 监听子窗口发来的打印确认消息,保存打印记录
|
||||
const onPrintConfirmed = async (event: MessageEvent) => {
|
||||
if (event.data && event.data.type === 'PRINT_CONFIRMED') {
|
||||
try {
|
||||
const printRecords = labelsToPrint.map(item => ({
|
||||
workerOrderId: formData.workerOrderId,
|
||||
materialNamePrint: item.partName,
|
||||
encodingPrint: item.partNumber,
|
||||
totalCount: item.totalCount,
|
||||
totalCalculatedWeight: item.totalCalculatedWeight,
|
||||
totalWeight: item.totalWeight,
|
||||
qrCodeData: item.qrCodeData,
|
||||
batch: formData.batch,
|
||||
mark: item.mark,
|
||||
productionDate: item.productionDate,
|
||||
packingSignature: item.packingSignature == '' ? null : item.packingSignature,
|
||||
inspectionSignature: item.inspectionSignature == '' ? null : item.inspectionSignature,
|
||||
}))
|
||||
await savePrintRecord({
|
||||
workerOrderId: formData.workerOrderId,
|
||||
orderNo: formData.orderNo,
|
||||
encoding: formData.encoding,
|
||||
materialName: formData.materialName,
|
||||
materialNamePrint: formData.materialNamePrint,
|
||||
encodingPrint: formData.encodingPrint,
|
||||
batch: formData.batch,
|
||||
labelType: isDetailLabels ? 0 : 1,
|
||||
labelCount: labelsToPrint.length,
|
||||
printInfoList: printRecords,
|
||||
})
|
||||
Message.success('打印记录已保存')
|
||||
} catch (err) {
|
||||
console.error('保存打印记录失败:', err)
|
||||
Message.error('保存打印记录失败')
|
||||
}
|
||||
window.removeEventListener('message', onPrintConfirmed)
|
||||
}
|
||||
}
|
||||
window.addEventListener('message', onPrintConfirmed)
|
||||
|
||||
// 等待图片加载完成后再打印
|
||||
printWindow.onload = () => {
|
||||
setTimeout(() => {
|
||||
printWindow.focus()
|
||||
printWindow.print()
|
||||
printWindow.close()
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defineOptions({ name: 'print' })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 表单区域 */
|
||||
.form-section {
|
||||
background-color: var(--color-bg-2);
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-grid-item {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 标签预览 */
|
||||
.label-preview-section {
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
background-color: var(--color-bg-2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.label-preview-section h3 {
|
||||
margin: 0 0 20px 0;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.label-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 450px;
|
||||
height: 180px;
|
||||
border: 1px solid #000;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.label-table {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.label-cell {
|
||||
border: 1px solid #000;
|
||||
padding: 5px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.qr-cell {
|
||||
width: 120px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.label-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.label-field {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.label-value {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
flex: 1;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.qr-code img {
|
||||
width: 115px;
|
||||
height: 120px;
|
||||
margin: 0 0;
|
||||
}
|
||||
|
||||
.qr-code .loading {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.mark-number {
|
||||
font-size: 10px;
|
||||
margin-top: 5px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.serial-number {
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.label-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* 确保输入框宽度一致 */
|
||||
:deep(.arco-input-wrapper) {
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
:deep(.arco-input) {
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -32,13 +32,11 @@
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<NoticeDetailModal ref="NoticeDetailModalRef" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type DashboardNoticeResp, listDashboardNotice } from '@/apis'
|
||||
import { useDict } from '@/hooks/app'
|
||||
import NoticeDetailModal from '@/views/system/notice/NoticeDetailModal.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const { notice_type } = useDict('notice_type')
|
||||
@@ -56,11 +54,6 @@ const getDataList = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const NoticeDetailModalRef = ref<InstanceType<typeof NoticeDetailModal>>()
|
||||
// 详情
|
||||
const onDetail = (id: string) => {
|
||||
NoticeDetailModalRef.value?.onDetail(id)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDataList()
|
||||
|
||||
431
src/views/fullClaim/FullWorkOrderAddModal.vue
Normal file
431
src/views/fullClaim/FullWorkOrderAddModal.vue
Normal file
@@ -0,0 +1,431 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
title="新增整箱领取记录"
|
||||
:mask-closable="false"
|
||||
:esc-to-close="false"
|
||||
:width="width >= 800 ? 800 : '90%'"
|
||||
:style="{ height: '90vh', maxHeight: '900px' }"
|
||||
draggable
|
||||
@before-ok="save"
|
||||
@close="reset"
|
||||
>
|
||||
<a-form ref="formRef" v-model="form">
|
||||
<a-row :gutter="22">
|
||||
<a-col :span="22">
|
||||
<a-form-item label="物料编码" label-col-flex="100px">
|
||||
<a-input
|
||||
ref="materialCodeInput"
|
||||
v-model="form.inputMaterialCode"
|
||||
placeholder="请点击此处确保光标闪烁,且输入法为英文状态,使用扫码枪扫描物料编码"
|
||||
@keydown="handleKeyDown"
|
||||
@input="handleMaterialCodeChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="22">
|
||||
<a-col :span="11">
|
||||
<a-form-item label="手动输入编码" label-col-flex="100px">
|
||||
<a-input
|
||||
ref="inputMaterialCode"
|
||||
v-model="form.inputMaterialCode2"
|
||||
placeholder="请输入物料编码"
|
||||
@change="handleMaterialCodeChange2"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="11">
|
||||
<a-form-item label="序号">
|
||||
<a-input-number
|
||||
v-model="form.mark"
|
||||
:min="1"
|
||||
placeholder="请输入序号"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="22">
|
||||
<a-col :span="11">
|
||||
<a-form-item label="物料名称" label-col-flex="100px">
|
||||
<a-input
|
||||
ref="materialName"
|
||||
v-model="form.materialName"
|
||||
placeholder="-"
|
||||
disabled
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="11">
|
||||
<a-form-item label="批次">
|
||||
<a-input
|
||||
ref="batch"
|
||||
v-model="form.batch"
|
||||
placeholder="-"
|
||||
disabled
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="22">
|
||||
<a-col :span="11">
|
||||
<a-form-item label="数量" label-col-flex="100px">
|
||||
<a-input-number
|
||||
v-model="form.count"
|
||||
placeholder="整箱数量"
|
||||
:min="0"
|
||||
:disabled = "disableCount"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="22">
|
||||
<a-col :span="22">
|
||||
<a-form-item label="图片" label-col-flex="100px">
|
||||
<div class="image-container">
|
||||
<img
|
||||
:src="imgData.imgUrl"
|
||||
alt="图片"
|
||||
style="width: 100%; height: 100%; object-fit: cover; border-radius: 4px;"
|
||||
/>
|
||||
<!-- 错误状态 -->
|
||||
<div v-if="weighingPageStatus === 'error'" class="video-overlay error">
|
||||
<icon-close-circle-fill style="color: #ff4d4f; font-size: 24px;" />
|
||||
<span>连接异常</span>
|
||||
<Button size="small" type="primary" @click="enterWeighPage">重试</Button>
|
||||
</div>
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="weighingPageStatus === 'entering'" class="video-overlay">
|
||||
<Spin />
|
||||
<span style="margin-left: 8px;">加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'
|
||||
import {type FormInstance, Message, Spin, Button, Icon} from '@arco-design/web-vue'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import { addFullWorkOrder } from '@/apis/fullWorkOrder/fullWorkOrder'
|
||||
import {getCaptureImage, getEnterWeighPage, getLeaveWeighPage} from '@/apis/weightManage/ys'
|
||||
import { useResetReactive } from '@/hooks'
|
||||
import {getMaterialDetail} from "@/apis/weightManage/weightManage";
|
||||
import {number} from "echarts";
|
||||
const emit = defineEmits<{
|
||||
(e: 'save-success'): void
|
||||
}>()
|
||||
|
||||
const { width, height } = useWindowSize()
|
||||
|
||||
const dataId = ref('')
|
||||
const visible = ref(false)
|
||||
const disableCount = ref(false)
|
||||
const materialCodeInput = ref<any>(null)
|
||||
|
||||
// 称重页面状态
|
||||
const weighingPageStatus = ref<'idle' | 'entering' | 'entered' | 'error'>('idle')
|
||||
|
||||
const [form, resetForm] = useResetReactive({
|
||||
materialCode: '',
|
||||
encodingPrint: '',
|
||||
imgUrl: '',
|
||||
materialName: '',
|
||||
batch: '',
|
||||
count: undefined,
|
||||
inputMaterialCode: '',
|
||||
inputMaterialCode2: '',
|
||||
mark: undefined,
|
||||
})
|
||||
|
||||
|
||||
const imgData = reactive({
|
||||
imgUrl: 'http://localhost:6609/file/ys/carousel.jpg', // 称重页面图片URL
|
||||
baseUrl: 'http://localhost:6609/file/ys/carousel.jpg' // 基础URL
|
||||
})
|
||||
|
||||
// 图片刷新定时器
|
||||
let imageRefreshTimer: any = null
|
||||
|
||||
// 重置
|
||||
const reset = () => {
|
||||
resetForm()
|
||||
}
|
||||
|
||||
// 防抖函数
|
||||
const debounce = <T extends (...args: any[]) => any>(func: T, delay: number): ((...args: Parameters<T>) => void) => {
|
||||
let timer: ReturnType<typeof setTimeout> | null = null
|
||||
return function (...args: Parameters<T>) {
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(() => {
|
||||
func(...args)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
// 原始的物料编码变化处理函数
|
||||
const originalHandleMaterialCodeChange = async () => {
|
||||
// 确保输入框内容是完整的物料编码
|
||||
// todo
|
||||
const materialCode = form.inputMaterialCode?.trim()
|
||||
// const materialCode = "831002839562,1,0.12,KP,0A2005,0A200520260325";
|
||||
|
||||
// 无论是否有输入,先重置所有物料相关字段,确保新数据能完全覆盖旧数据
|
||||
form.materialCode = ''
|
||||
form.materialName = ''
|
||||
form.inputMaterialCode2 = ''
|
||||
form.batch = ''
|
||||
form.encodingPrint = ''
|
||||
form.count = undefined
|
||||
form.mark = undefined
|
||||
|
||||
// 如果有物料编码输入,获取物料数据
|
||||
if (materialCode) {
|
||||
try {
|
||||
disableCount.value = true
|
||||
const parts = materialCode.split(',');
|
||||
form.count = parseFloat(parts[2]) * 1000;
|
||||
console.log("form.count", form.count);
|
||||
await fetchMaterialData(materialCode)
|
||||
} catch (error) {
|
||||
console.error('获取物料数据失败:', error)
|
||||
// 即使获取失败,也保持字段为空,避免显示旧数据
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 带防抖的物料编码变化处理函数
|
||||
const handleMaterialCodeChange = debounce(originalHandleMaterialCodeChange, 500) // 500ms防抖延迟
|
||||
|
||||
// 扫码核验获取物料信息
|
||||
const fetchMaterialData = async (code: string) => {
|
||||
const res = await getMaterialDetail(code);
|
||||
if (res.code === '0') {
|
||||
// 更新表单数据
|
||||
form.materialCode = res.data?.encoding || ''
|
||||
form.encodingPrint = res.data?.encodingPrint || ''
|
||||
form.materialName = res.data?.materialName || ''
|
||||
form.batch = res.data?.batch || ''
|
||||
}
|
||||
}
|
||||
|
||||
const handleMaterialCodeChange2 = async (code: string) => {
|
||||
if (!code || code?.trim()=== '') {
|
||||
form.materialCode = ''
|
||||
form.encodingPrint = ''
|
||||
form.materialName = ''
|
||||
form.batch = ''
|
||||
form.mark = undefined
|
||||
form.count = undefined
|
||||
return
|
||||
}
|
||||
disableCount.value = false
|
||||
form.inputMaterialCode = ''
|
||||
form.mark = undefined
|
||||
form.count = undefined
|
||||
const res = await getMaterialDetail(code);
|
||||
if (res.code === '0') {
|
||||
// 更新表单数据
|
||||
form.materialCode = res.data?.encoding || ''
|
||||
form.encodingPrint = res.data?.encodingPrint || ''
|
||||
form.materialName = res.data?.materialName || ''
|
||||
form.batch = res.data?.batch || ''
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 保存
|
||||
const save = async () => {
|
||||
if (!form.materialCode || form.materialCode.trim() === '') {
|
||||
Message.error('未找到物料信息');
|
||||
return false
|
||||
}
|
||||
if (!form.count) {
|
||||
Message.error('数量不能为空');
|
||||
return false
|
||||
}
|
||||
if (!form.batch || form.batch.trim() === '') {
|
||||
Message.error('未找到批次');
|
||||
return false
|
||||
}
|
||||
if (!form.mark) {
|
||||
Message.error('请输入序号');
|
||||
return false
|
||||
}
|
||||
try {
|
||||
//手动抓图
|
||||
const data = {
|
||||
type: 2,
|
||||
}
|
||||
// todo
|
||||
const response = await getCaptureImage(data);
|
||||
// const response = {'data': 'http://localhost:6609/file/ys/carousel.jpg'};
|
||||
if (response) {
|
||||
form.imgUrl = response.data;
|
||||
} else {
|
||||
Message.error('抓图失败');
|
||||
return false;
|
||||
}
|
||||
await addFullWorkOrder(form)
|
||||
Message.success('新增成功')
|
||||
emit('save-success')
|
||||
return true
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 启动宇视SDK
|
||||
const enterWeighPage = async () => {
|
||||
weighingPageStatus.value = 'entering'
|
||||
try {
|
||||
await getEnterWeighPage()
|
||||
weighingPageStatus.value = 'entered'
|
||||
} catch (error) {
|
||||
console.error('启动宇视SDK失败:', error)
|
||||
weighingPageStatus.value = 'error'
|
||||
}
|
||||
}
|
||||
|
||||
// 退出宇视SDK
|
||||
const leaveWeighPage = async () => {
|
||||
try {
|
||||
await getLeaveWeighPage()
|
||||
} catch (error) {
|
||||
console.error('退出宇视SDK失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 新增
|
||||
const onAdd = async () => {
|
||||
reset()
|
||||
dataId.value = ''
|
||||
visible.value = true
|
||||
// 启动宇视SDK
|
||||
await enterWeighPage()
|
||||
// 聚焦到物料编码输入框
|
||||
nextTick(() => {
|
||||
if (materialCodeInput.value) {
|
||||
materialCodeInput.value.focus()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 记录上次输入时间,用于判断是否是扫码枪输入
|
||||
let lastInputTime = 0
|
||||
// 记录是否正在接收扫码输入
|
||||
let isScanning = false
|
||||
|
||||
// 处理键盘按下事件
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
// 获取当前时间
|
||||
const currentTime = Date.now()
|
||||
|
||||
// 计算时间差
|
||||
const timeDiff = currentTime - lastInputTime
|
||||
// 检查是否是回车键(扫码枪通常以回车键结束)
|
||||
if (event.key === 'Enter') {
|
||||
// 扫码结束,标记为不在扫描中
|
||||
isScanning = false
|
||||
// 不阻止默认行为,让表单可以正常提交
|
||||
}
|
||||
// 检查是否是新的扫码开始
|
||||
// 当时间间隔大于300ms,且不是修饰键,且不是功能键时,认为是新的扫码开始
|
||||
else if (timeDiff > 300 && !event.ctrlKey && !event.altKey && !event.metaKey) {
|
||||
form.inputMaterialCode = ''
|
||||
// 标记为开始扫描
|
||||
isScanning = true
|
||||
}
|
||||
|
||||
// 更新上次输入时间
|
||||
lastInputTime = currentTime
|
||||
}
|
||||
|
||||
// 组件挂载时
|
||||
onMounted(() => {
|
||||
// 初始时不启动图片刷新
|
||||
})
|
||||
|
||||
// 组件卸载时
|
||||
onBeforeUnmount(() => {
|
||||
// 清除图片刷新定时器
|
||||
if (imageRefreshTimer) {
|
||||
clearInterval(imageRefreshTimer)
|
||||
imageRefreshTimer = null
|
||||
}
|
||||
// 退出宇视SDK
|
||||
leaveWeighPage()
|
||||
})
|
||||
|
||||
// 监听visible变化
|
||||
watch(visible, async (newVal) => {
|
||||
if (newVal) {
|
||||
// 当弹窗打开时,启动图片自动刷新,每1.5秒更新一次
|
||||
imageRefreshTimer = setInterval(() => {
|
||||
// 添加时间戳参数,避免浏览器缓存
|
||||
imgData.imgUrl = `${imgData.baseUrl}?t=${Date.now()}`
|
||||
}, 1500)
|
||||
} else {
|
||||
// 当弹窗关闭时,退出宇视SDK并停止图片刷新
|
||||
await leaveWeighPage()
|
||||
if (imageRefreshTimer) {
|
||||
clearInterval(imageRefreshTimer)
|
||||
imageRefreshTimer = null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({ onAdd })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.image-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 视频覆盖层样式(用于错误和加载状态) */
|
||||
.video-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.video-overlay.error {
|
||||
background: rgba(255, 77, 79, 0.2);
|
||||
}
|
||||
|
||||
/* 禁用输入框样式 - 黑色加粗字体 */
|
||||
:deep(.arco-input-wrapper.arco-input-disabled) {
|
||||
background-color: #ffffff !important;
|
||||
border-color: #d9d9d9 !important;
|
||||
}
|
||||
|
||||
:deep(.arco-input-wrapper.arco-input-disabled .arco-input) {
|
||||
color: #000000 !important;
|
||||
background-color: transparent !important;
|
||||
opacity: 1 !important;
|
||||
-webkit-text-fill-color: #000000 !important;
|
||||
}
|
||||
</style>
|
||||
69
src/views/fullClaim/FullWorkOrderDetailListModal.vue
Normal file
69
src/views/fullClaim/FullWorkOrderDetailListModal.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
title="原材料详情"
|
||||
:width="800"
|
||||
:footer="false"
|
||||
@cancel="handleClose"
|
||||
>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data="detailList"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
bordered
|
||||
>
|
||||
<template #imgUrl="{ record }">
|
||||
<a-image
|
||||
width="80"
|
||||
height="60"
|
||||
:src="record.imgUrl"
|
||||
fit="cover"
|
||||
/>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { getFullWorkOrderDetailList } from '@/apis/fullWorkOrder/fullWorkOrder'
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const detailList = ref<any[]>([])
|
||||
|
||||
const columns = [
|
||||
{ title: '称重重量(g)', dataIndex: 'weight', key: 'weight' },
|
||||
{ title: '截图', dataIndex: 'imgUrl', key: 'imgUrl', slotName: 'imgUrl' }
|
||||
]
|
||||
|
||||
const onOpen = async (id: string) => {
|
||||
visible.value = true
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getFullWorkOrderDetailList(id)
|
||||
if (res.code === '0') {
|
||||
detailList.value = res.data || []
|
||||
} else {
|
||||
Message.error(res.msg || '获取详情失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取详情失败:', error)
|
||||
Message.error('获取详情失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
detailList.value = []
|
||||
}
|
||||
|
||||
defineExpose({ onOpen })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
368
src/views/fullClaim/FullWorkOrderDetailModal.vue
Normal file
368
src/views/fullClaim/FullWorkOrderDetailModal.vue
Normal file
@@ -0,0 +1,368 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
title="新增原材料详情"
|
||||
:mask-closable="false"
|
||||
:esc-to-close="false"
|
||||
:width="1200"
|
||||
:style="{ height: '80vh' }"
|
||||
draggable
|
||||
@before-ok="save"
|
||||
@close="reset"
|
||||
>
|
||||
<div class="detail-container">
|
||||
<div class="left-section">
|
||||
<div class="weight-input">
|
||||
<label>称重重量(g)</label>
|
||||
<a-input v-model="weightValue" placeholder="等待电子秤数据..." disabled />
|
||||
</div>
|
||||
<div class="camera-section">
|
||||
<div class="image-container">
|
||||
<img
|
||||
:src="imgData.imgUrl"
|
||||
alt="宇视摄像头实时画面"
|
||||
style="width: 100%; height: 100%; object-fit: cover; border-radius: 4px;"
|
||||
/>
|
||||
<div v-if="cameraStatus === 'error'" class="video-overlay error">
|
||||
<icon-close-circle-fill style="color: #ff4d4f; font-size: 24px;" />
|
||||
<span>连接异常</span>
|
||||
<a-button size="small" type="primary" @click="enterCamera">重试</a-button>
|
||||
</div>
|
||||
<div v-if="cameraStatus === 'entering'" class="video-overlay">
|
||||
<a-spin />
|
||||
<span style="margin-left: 8px;">加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="confirm-button">
|
||||
<a-button type="primary" @click="handleConfirm">确定</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-section">
|
||||
<div class="detail-list">
|
||||
<a-table :columns="detailColumns" :data="detailList" :pagination="false" bordered>
|
||||
<template #imgUrl="{ record }">
|
||||
<a-image width="80" height="60" :src="record.imgUrl" />
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<a-button type="text" status="danger" @click="handleDeleteDetail(record)">
|
||||
删除
|
||||
</a-button>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { getCaptureImage, getEnterWeighPage, getLeaveWeighPage } from '@/apis/weightManage/ys'
|
||||
import { weighAHStart, weighAHStop } from '@/apis/weightManage/weightManage'
|
||||
import { saveFullWorkOrderDetail } from '@/apis/fullWorkOrder/fullWorkOrder'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'save-success'): void
|
||||
}>()
|
||||
|
||||
const visible = ref(false)
|
||||
const weightValue = ref('')
|
||||
const cameraStatus = ref<'idle' | 'entering' | 'entered' | 'error'>('idle')
|
||||
const fullWorkOrder = ref('')
|
||||
|
||||
const imgData = reactive({
|
||||
imgUrl: 'http://localhost:6609/file/ys/carousel.jpg',
|
||||
baseUrl: 'http://localhost:6609/file/ys/carousel.jpg'
|
||||
})
|
||||
|
||||
let imageRefreshTimer: any = null
|
||||
|
||||
const detailList = ref<any[]>([])
|
||||
|
||||
const detailColumns = [
|
||||
{ title: '称重重量(g)', dataIndex: 'weight', key: 'weight' },
|
||||
{ title: '截图', dataIndex: 'imgUrl', key: 'imgUrl', slotName: 'imgUrl' },
|
||||
{ title: '操作', dataIndex: 'action', key: 'action', slotName: 'action', width: 80 }
|
||||
]
|
||||
|
||||
// WebSocket连接
|
||||
const ws = ref<WebSocket | null>(null)
|
||||
const wsConnected = ref(false)
|
||||
|
||||
// 建立WebSocket连接(电子称)
|
||||
const establishWebSocket = () => {
|
||||
try {
|
||||
const wsUrl = 'ws://localhost:6609/ws/scale'
|
||||
ws.value = new WebSocket(wsUrl)
|
||||
|
||||
ws.value.onopen = () => {
|
||||
wsConnected.value = true
|
||||
}
|
||||
|
||||
ws.value.onmessage = (event) => {
|
||||
try {
|
||||
if (event.data) {
|
||||
weightValue.value = event.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('WebSocket消息解析失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
ws.value.onclose = () => {
|
||||
wsConnected.value = false
|
||||
}
|
||||
|
||||
ws.value.onerror = (error) => {
|
||||
console.error('WebSocket错误:', error)
|
||||
wsConnected.value = false
|
||||
Message.error('称重连接失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('建立WebSocket连接失败:', error)
|
||||
Message.error('无法建立称重连接')
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭WebSocket连接(电子称)
|
||||
const closeWebSocket = () => {
|
||||
if (ws.value) {
|
||||
ws.value.close()
|
||||
ws.value = null
|
||||
wsConnected.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 连接电子称
|
||||
const connectScale = async () => {
|
||||
try {
|
||||
await weighAHStart()
|
||||
establishWebSocket()
|
||||
} catch (error) {
|
||||
console.error('连接电子称失败:', error)
|
||||
Message.error('与电子称的连接建立失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 断开电子称
|
||||
const disconnectScale = async () => {
|
||||
try {
|
||||
await weighAHStop()
|
||||
} catch (error) {
|
||||
console.error('断开电子称失败:', error)
|
||||
}
|
||||
closeWebSocket()
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
weightValue.value = ''
|
||||
detailList.value = []
|
||||
fullWorkOrder.value = ''
|
||||
}
|
||||
|
||||
const enterCamera = async () => {
|
||||
cameraStatus.value = 'entering'
|
||||
try {
|
||||
await getEnterWeighPage()
|
||||
cameraStatus.value = 'entered'
|
||||
} catch (error) {
|
||||
console.error('进入摄像头页面失败:', error)
|
||||
cameraStatus.value = 'error'
|
||||
}
|
||||
}
|
||||
|
||||
const leaveCamera = async () => {
|
||||
try {
|
||||
await getLeaveWeighPage()
|
||||
} catch (error) {
|
||||
console.error('离开摄像头页面失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const onAdd = async (id: string) => {
|
||||
reset()
|
||||
fullWorkOrder.value = id
|
||||
visible.value = true
|
||||
// 独立连接电子称和摄像头
|
||||
await Promise.all([
|
||||
enterCamera(),
|
||||
connectScale()
|
||||
])
|
||||
}
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (!weightValue.value || weightValue.value.trim() === '') {
|
||||
Message.error('电子秤称重结果为空!')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const data = {
|
||||
type: 2,
|
||||
}
|
||||
// todo
|
||||
const response = await getCaptureImage(data)
|
||||
// const response = {
|
||||
// data: 'http://localhost:6609/file/ys/workOrder/fullOrder_1774322914630.jpg',
|
||||
// }
|
||||
if (response && response.data) {
|
||||
const newItem = {
|
||||
key: Date.now().toString(),
|
||||
weight: weightValue.value,
|
||||
imgUrl: response.data
|
||||
}
|
||||
detailList.value.push(newItem)
|
||||
weightValue.value = ''
|
||||
Message.success('添加成功')
|
||||
} else {
|
||||
Message.error('抓图失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('抓图失败:', error)
|
||||
Message.error('抓图失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteDetail = (record: any) => {
|
||||
detailList.value = detailList.value.filter(item => item.key !== record.key)
|
||||
Message.success('删除成功')
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
if (detailList.value.length === 0) {
|
||||
Message.error('请至少添加一条详情记录')
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const data = detailList.value.map(item => ({
|
||||
weight: item.weight,
|
||||
imgUrl: item.imgUrl,
|
||||
fullWorkOrderId: fullWorkOrder.value,
|
||||
}))
|
||||
|
||||
|
||||
const res = await saveFullWorkOrderDetail(data)
|
||||
if (res.code === '0') {
|
||||
Message.success('保存成功')
|
||||
emit('save-success')
|
||||
return true
|
||||
} else {
|
||||
Message.error(res.msg || '保存失败')
|
||||
return false
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error)
|
||||
Message.error('保存失败')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (imageRefreshTimer) {
|
||||
clearInterval(imageRefreshTimer)
|
||||
imageRefreshTimer = null
|
||||
}
|
||||
leaveCamera()
|
||||
})
|
||||
|
||||
watch(visible, async (newVal) => {
|
||||
if (newVal) {
|
||||
imageRefreshTimer = setInterval(() => {
|
||||
imgData.imgUrl = `${imgData.baseUrl}?t=${Date.now()}`
|
||||
}, 1500)
|
||||
} else {
|
||||
// 独立断开电子称和摄像头
|
||||
await Promise.all([
|
||||
leaveCamera(),
|
||||
disconnectScale()
|
||||
])
|
||||
if (imageRefreshTimer) {
|
||||
clearInterval(imageRefreshTimer)
|
||||
imageRefreshTimer = null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({ onAdd })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.detail-container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.left-section {
|
||||
width: 45%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.weight-input {
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.camera-section {
|
||||
flex: 1;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.video-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.video-overlay.error {
|
||||
background: rgba(255, 77, 79, 0.2);
|
||||
}
|
||||
|
||||
.confirm-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.right-section {
|
||||
width: 55%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.detail-list {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
351
src/views/fullClaim/index.vue
Normal file
351
src/views/fullClaim/index.vue
Normal file
@@ -0,0 +1,351 @@
|
||||
<template>
|
||||
<div class="gi_table_page">
|
||||
<GiTable
|
||||
title="整箱领取记录管理"
|
||||
row-key="id"
|
||||
:data="dataList"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
|
||||
:pagination="pagination"
|
||||
:disabled-tools="['size']"
|
||||
:disabled-column-keys="['name']"
|
||||
@refresh="search"
|
||||
>
|
||||
<template #toolbar-left>
|
||||
<a-input-search v-model="queryForm.orderNo" placeholder="请输入任务工单号" allow-clear @search="search" />
|
||||
<a-input-search v-model="queryForm.materialCode" placeholder="请输入物料编码" allow-clear @search="search" />
|
||||
<a-input-search v-model="queryForm.materialName" placeholder="请输入物料名称" allow-clear @search="search" />
|
||||
<a-input-search v-model="queryForm.batch" placeholder="请输入批次号" allow-clear @search="search" />
|
||||
<a-range-picker
|
||||
v-model="queryForm.createTime"
|
||||
:show-time="true"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
style="height: 32px"
|
||||
:allow-clear="true"
|
||||
@change="search"
|
||||
/>
|
||||
<a-button @click="reset">
|
||||
<template #icon><icon-refresh /></template>
|
||||
<template #default>重置</template>
|
||||
</a-button>
|
||||
</template>
|
||||
<template #toolbar-right>
|
||||
<a-button v-permission="['fullWorkOrder:fullWorkOrder:add']" type="primary" @click="onAdd">
|
||||
<template #icon><icon-plus /></template>
|
||||
<template #default>新增</template>
|
||||
</a-button>
|
||||
<a-button v-permission="['fullWorkOrder:fullWorkOrder:export']" @click="onExport">
|
||||
<template #icon><icon-download /></template>
|
||||
<template #default>导出</template>
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<template #imgUrl="{ record }">
|
||||
<a-image
|
||||
width="60"
|
||||
:src="record.imgUrl"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-link
|
||||
@click="onAddDetail(record.id)"
|
||||
>
|
||||
新增
|
||||
</a-link>
|
||||
<a-link
|
||||
@click="onViewDetail(record)"
|
||||
>
|
||||
详情
|
||||
</a-link>
|
||||
<a-link
|
||||
@click="onPrint(record)"
|
||||
>
|
||||
打印
|
||||
</a-link>
|
||||
<a-link
|
||||
v-permission="['fullWorkOrder:fullWorkOrder:delete']"
|
||||
status="danger"
|
||||
:disabled="record.disabled"
|
||||
:title="record.disabled ? '不可删除' : '删除'"
|
||||
@click="onDelete(record)"
|
||||
>
|
||||
删除
|
||||
</a-link>
|
||||
</a-space>
|
||||
</template>
|
||||
</GiTable>
|
||||
|
||||
<FullWorkOrderAddModal ref="FullWorkOrderAddModalRef" @save-success="search" />
|
||||
<FullWorkOrderDetailModal ref="FullWorkOrderDetailModalRef" @save-success="search" />
|
||||
<FullWorkOrderDetailListModal ref="FullWorkOrderDetailListModalRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import FullWorkOrderAddModal from './FullWorkOrderAddModal.vue'
|
||||
import FullWorkOrderDetailModal from './FullWorkOrderDetailModal.vue'
|
||||
import FullWorkOrderDetailListModal from './FullWorkOrderDetailListModal.vue'
|
||||
import { type FullWorkOrderResp, type FullWorkOrderQuery, deleteFullWorkOrder, exportFullWorkOrder, listFullWorkOrder } from '@/apis/fullWorkOrder/fullWorkOrder'
|
||||
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
||||
import { useDownload, useTable } from '@/hooks'
|
||||
import { isMobile } from '@/utils'
|
||||
import has from '@/utils/has'
|
||||
import type {WorkOrderResp} from "@/apis/workOrder/workOrder";
|
||||
import QRCode from 'qrcode';
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
|
||||
defineOptions({ name: 'FullWorkOrder' })
|
||||
|
||||
|
||||
const queryForm = reactive<FullWorkOrderQuery>({
|
||||
orderNo: undefined,
|
||||
materialCode: undefined,
|
||||
materialName: undefined,
|
||||
batch: undefined,
|
||||
createTime: undefined,
|
||||
sort: ['f.id,desc']
|
||||
})
|
||||
|
||||
const {
|
||||
tableData: dataList,
|
||||
loading,
|
||||
pagination,
|
||||
search,
|
||||
handleDelete
|
||||
} = useTable((page) => listFullWorkOrder({ ...queryForm, ...page }), { immediate: true })
|
||||
const columns = ref<TableInstanceColumns[]>([
|
||||
{ title: '标题', dataIndex: 'title', slotName: 'title' },
|
||||
{ title: '任务工单号', dataIndex: 'orderNo', slotName: 'orderNo' },
|
||||
{ title: '物料名称', dataIndex: 'materialName', slotName: 'materialName' },
|
||||
{ title: '物料编码', dataIndex: 'materialCode', slotName: 'materialCode' },
|
||||
{ title: '批次号', dataIndex: 'batch', slotName: 'batch' },
|
||||
{ title: '数量', dataIndex: 'count', slotName: 'count' },
|
||||
{ title: '标记号', dataIndex: 'mark', slotName: 'mark' },
|
||||
{ title: '抓拍图', dataIndex: 'imgUrl', slotName: 'imgUrl' },
|
||||
{ title: '创建人', dataIndex: 'createUserString', slotName: 'createUser' },
|
||||
{ title: '创建时间', dataIndex: 'createTime', slotName: 'createTime' },
|
||||
{ title: '修改人', dataIndex: 'updateUserString', slotName: 'updateUser', show: false },
|
||||
{ title: '修改时间', dataIndex: 'updateTime', slotName: 'updateTime', show: false },
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
slotName: 'action',
|
||||
width: 220,
|
||||
align: 'center',
|
||||
fixed: !isMobile() ? 'right' : undefined,
|
||||
show: has.hasPermOr(['fullWorkOrder:fullWorkOrder:detail', 'fullWorkOrder:fullWorkOrder:update', 'fullWorkOrder:fullWorkOrder:delete'])
|
||||
}
|
||||
]);
|
||||
|
||||
// 重置
|
||||
const reset = () => {
|
||||
queryForm.orderNo = undefined
|
||||
queryForm.materialCode = undefined
|
||||
queryForm.materialName = undefined
|
||||
queryForm.batch = undefined
|
||||
queryForm.createTime = undefined
|
||||
search()
|
||||
}
|
||||
|
||||
// 删除
|
||||
const onDelete = (record: FullWorkOrderResp) => {
|
||||
return handleDelete(() => deleteFullWorkOrder(record.id), {
|
||||
content: `是否确定删除该条数据?`,
|
||||
showModal: true
|
||||
})
|
||||
}
|
||||
|
||||
// 生成二维码
|
||||
const generateQRCode = async (data: string) => {
|
||||
try {
|
||||
return await QRCode.toDataURL(data, {
|
||||
width: 120,
|
||||
margin: 1,
|
||||
color: {
|
||||
dark: '#000000',
|
||||
light: '#FFFFFF'
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('生成二维码失败:', error)
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
const onPrint = async (record: FullWorkOrderResp) => {
|
||||
if (!record.batch || record.batch.trim() === '') {
|
||||
Message.error('该条记录批次号为空,无法打印!')
|
||||
return
|
||||
}
|
||||
if (!record.mark || record.mark.trim() === '') {
|
||||
Message.error('该条记录标记号为空,无法打印!')
|
||||
return
|
||||
}
|
||||
if (!record.count) {
|
||||
Message.error('该条记录数量为空,无法打印!')
|
||||
return
|
||||
}
|
||||
try {
|
||||
// 格式化生产日期为 yyyyMMddHHmm 格式
|
||||
const now = new Date()
|
||||
const formattedDate = now.getFullYear().toString() +
|
||||
String(now.getMonth() + 1).padStart(2, '0') +
|
||||
String(now.getDate()).padStart(2, '0') +
|
||||
String(now.getHours()).padStart(2, '0') +
|
||||
String(now.getMinutes()).padStart(2, '0')
|
||||
|
||||
const formattedDate2 = now.getFullYear().toString() +
|
||||
String(now.getMonth() + 1).padStart(2, '0') +
|
||||
String(now.getDate()).padStart(2, '0')
|
||||
|
||||
// 计算二维码数据
|
||||
const qrCodeData = `10#${record.materialCode || ''}$11#9DP$12#${record.batch || ''}$17#${record.count || ''}$20#${formattedDate2}$31#${record.orderNo || ''}$DY`
|
||||
|
||||
// 生成二维码图片
|
||||
const qrCodeImage = await generateQRCode(qrCodeData)
|
||||
|
||||
// 直接生成打印标签
|
||||
const printWindow = window.open('', '_blank');
|
||||
if (!printWindow) return;
|
||||
|
||||
// 构建打印内容
|
||||
const printContent = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>标签打印</title>
|
||||
<style>
|
||||
@page { size: 80mm 50mm; margin: 0; }
|
||||
body { margin: 0; padding: 1mm; font-family: Arial, sans-serif; }
|
||||
.label-container { display: flex; flex-wrap: wrap; gap: 1mm; justify-content: center; align-items: center; transform-origin: center; }
|
||||
.label { width: 75mm; height: 45mm; border: 1px solid #000; padding: 0; box-sizing: border-box; page-break-inside: avoid; overflow: hidden; margin: 0 auto; }
|
||||
.label-table { width: 100%; height: 100%; border-collapse: collapse; table-layout: fixed; }
|
||||
.label-cell { border: 1px solid #000; padding: 0.8mm; vertical-align: top; }
|
||||
.qr-cell { width: 22mm; text-align: center; vertical-align: middle; border: 1px solid #000; }
|
||||
.label-row { display: flex; align-items: center; }
|
||||
.label-field { font-size: 7.5pt; font-weight: bold; margin-right: 1mm; min-width: 22pt; white-space: nowrap; }
|
||||
.label-value { font-size: 7.5pt; font-weight: bold; flex: 1; overflow-wrap: break-word; word-break: break-word; }
|
||||
.qr-code img { width: 22mm; height: 22mm; margin: 0.5mm 0; }
|
||||
.mark-number { font-size: 7pt; font-weight: bold; margin-top: 0.5mm; text-align: center; }
|
||||
.serial-number { font-size: 7.5pt; font-weight: bold; margin-top: 0.5mm; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="label-container">
|
||||
<div class="label">
|
||||
<table class="label-table">
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">零件名称</div>
|
||||
<div class="label-value">${record.materialName || ''}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">生产日期</div>
|
||||
<div class="label-value">${formattedDate}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell qr-cell" rowspan="4">
|
||||
<div class="qr-code">
|
||||
<img src="${qrCodeImage}" alt="QR Code" />
|
||||
<div class="mark-number">${record.mark || ''}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">零件号</div>
|
||||
<div class="label-value">${record.materialCode || ''}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">数量</div>
|
||||
<div class="label-value">${record.count || ''}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">标重(g)</div>
|
||||
<div class="label-value"></div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">包装签字</div>
|
||||
<div class="label-value"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">实重(g)</div>
|
||||
<div class="label-value"></div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">检验签字</div>
|
||||
<div class="label-value"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
printWindow.document.write(printContent);
|
||||
printWindow.document.close();
|
||||
|
||||
// 等待页面加载完成后打印
|
||||
printWindow.onload = function() {
|
||||
setTimeout(() => {
|
||||
printWindow.focus();
|
||||
printWindow.print();
|
||||
printWindow.close();
|
||||
}, 500);
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('打印标签失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 导出
|
||||
const onExport = () => {
|
||||
useDownload(() => exportFullWorkOrder(queryForm))
|
||||
}
|
||||
|
||||
const FullWorkOrderAddModalRef = ref<InstanceType<typeof FullWorkOrderAddModal>>()
|
||||
const FullWorkOrderDetailModalRef = ref<InstanceType<typeof FullWorkOrderDetailModal>>()
|
||||
const FullWorkOrderDetailListModalRef = ref<InstanceType<typeof FullWorkOrderDetailListModal>>()
|
||||
|
||||
// 新增
|
||||
const onAdd = () => {
|
||||
FullWorkOrderAddModalRef.value?.onAdd()
|
||||
}
|
||||
|
||||
// 新增原材料详情
|
||||
const onAddDetail = (id: string) => {
|
||||
FullWorkOrderDetailModalRef.value?.onAdd(id)
|
||||
}
|
||||
|
||||
// 查看原材料详情
|
||||
const onViewDetail = (record: FullWorkOrderResp) => {
|
||||
FullWorkOrderDetailListModalRef.value?.onOpen(record.id)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -20,8 +20,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ref, reactive, onMounted, onUnmounted } from 'vue'
|
||||
import { type FormInstance, Message } from '@arco-design/web-vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useTabsStore, useUserStore } from '@/stores'
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
@@ -42,12 +43,22 @@ const loading = ref(false)
|
||||
|
||||
// 登录
|
||||
const handleLogin = async () => {
|
||||
console.log("卡号登录handleLogin")
|
||||
|
||||
const isInvalid = await formRef.value?.validate()
|
||||
if (isInvalid) return
|
||||
try {
|
||||
console.log("卡号-校验通过")
|
||||
loading.value = true
|
||||
// 调用后端接口校验卡号
|
||||
await userStore.cardLogin(form)
|
||||
|
||||
// 登录成功后移除事件监听器
|
||||
if (keyDownListener) {
|
||||
document.removeEventListener('keydown', keyDownListener)
|
||||
keyDownListener = null
|
||||
}
|
||||
|
||||
tabsStore.reset()
|
||||
const { redirect, ...othersQuery } = router.currentRoute.value.query
|
||||
await router.push({
|
||||
@@ -59,29 +70,37 @@ const handleLogin = async () => {
|
||||
Message.success('欢迎使用')
|
||||
} catch (error) {
|
||||
// 登录失败处理
|
||||
console.error('刷卡登录失败:', error)
|
||||
Message.error( '登录失败,请刷新页面')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 键盘事件监听器引用
|
||||
let keyDownListener: ((e: KeyboardEvent) => void) | null = null
|
||||
|
||||
// 监听键盘输入,实现自动识别卡号
|
||||
onMounted(() => {
|
||||
// 监听全局键盘事件,用于识别刷卡输入
|
||||
document.addEventListener('keydown', (e) => {
|
||||
// 只监听Enter键,用于自动登录
|
||||
keyDownListener = (e) => {
|
||||
// 刷卡器通常会快速输入卡号并以Enter键结束
|
||||
if (e.key === 'Enter') {
|
||||
// 遇到Enter键,尝试登录
|
||||
if (form.cardNumber.trim()) {
|
||||
handleLogin()
|
||||
}
|
||||
} else if (e.key.length === 1 && !e.ctrlKey && !e.altKey && !e.metaKey) {
|
||||
// 只捕获单个字符的按键,忽略控制键和功能键
|
||||
form.cardNumber += e.key
|
||||
} else if (e.key === 'Backspace') {
|
||||
// 处理退格键
|
||||
form.cardNumber = form.cardNumber.slice(0, -1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', keyDownListener)
|
||||
})
|
||||
|
||||
// 组件卸载时移除事件监听器
|
||||
onUnmounted(() => {
|
||||
if (keyDownListener) {
|
||||
document.removeEventListener('keydown', keyDownListener)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
163
src/views/material/BatchImport.vue
Normal file
163
src/views/material/BatchImport.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
v-model:visible="visible"
|
||||
title="批次导入"
|
||||
:mask-closable="false"
|
||||
:esc-to-close="false"
|
||||
:width="width >= 600 ? 600 : '100%'"
|
||||
ok-text="确认导入"
|
||||
cancel-text="取消导入"
|
||||
@before-ok="save"
|
||||
@close="reset"
|
||||
>
|
||||
<a-form ref="formRef" :model="form" size="large" auto-label-width>
|
||||
<a-alert v-if="!form.disabled" style="margin-bottom: 15px">
|
||||
请按照模板要求填写数据,填写完毕后,请先上传并进行解析。
|
||||
<template #action>
|
||||
<a-link @click="downloadTemplate">
|
||||
<template #icon><GiSvgIcon name="file-excel" :size="16" /></template>
|
||||
<template #default>下载模板</template>
|
||||
</a-link>
|
||||
</template>
|
||||
</a-alert>
|
||||
<fieldset>
|
||||
<legend>1.解析数据</legend>
|
||||
<div class="file-box">
|
||||
<a-upload
|
||||
draggable
|
||||
:custom-request="handleUpload"
|
||||
:limit="1"
|
||||
:show-retry-butto="false"
|
||||
:show-cancel-button="false" tip="仅支持xls、xlsx格式"
|
||||
:file-list="uploadFile"
|
||||
accept=".xls, .xlsx, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="dataResult.importKey">
|
||||
<div class="file-box">
|
||||
<a-space size="large">
|
||||
<a-statistic title="总计行数" :value="dataResult.totalRows" />
|
||||
<a-statistic title="正常行数" :value="dataResult.validRows" />
|
||||
</a-space>
|
||||
</div>
|
||||
<div class="file-box">
|
||||
<a-space size="large">
|
||||
<a-statistic title="已存在批次" :value="dataResult.duplicateRows" />
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</a-form>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type FormInstance, Message, type RequestOption } from '@arco-design/web-vue'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import {
|
||||
type BatchImportResp,
|
||||
downloadBatchImportTemplate,
|
||||
batchImport,
|
||||
parseBatchImport,
|
||||
} from '@/apis/material/materialInfo'
|
||||
import { useDownload, useResetReactive } from '@/hooks'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'save-success'): void
|
||||
}>()
|
||||
|
||||
const { width } = useWindowSize()
|
||||
|
||||
const visible = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const uploadFile = ref([])
|
||||
|
||||
const [form, resetForm] = useResetReactive({
|
||||
importKey: '',
|
||||
})
|
||||
|
||||
const dataResult = ref<BatchImportResp>({
|
||||
importKey: '',
|
||||
totalRows: 0,
|
||||
validRows: 0,
|
||||
duplicateRows: 0,
|
||||
})
|
||||
|
||||
const reset = () => {
|
||||
formRef.value?.resetFields()
|
||||
dataResult.value.importKey = ''
|
||||
uploadFile.value = []
|
||||
resetForm()
|
||||
}
|
||||
|
||||
const downloadTemplate = () => {
|
||||
useDownload(() => downloadBatchImportTemplate())
|
||||
}
|
||||
|
||||
const handleUpload = (options: RequestOption) => {
|
||||
const controller = new AbortController();
|
||||
(async function requestWrap() {
|
||||
const { onProgress, onError, onSuccess, fileItem, name = 'file' } = options
|
||||
onProgress(20)
|
||||
const formData = new FormData()
|
||||
formData.append(name as string, fileItem.file as Blob)
|
||||
try {
|
||||
const res = await parseBatchImport(formData)
|
||||
dataResult.value = res.data
|
||||
Message.success('上传解析成功')
|
||||
onSuccess(res)
|
||||
} catch (error) {
|
||||
onError(error)
|
||||
}
|
||||
})()
|
||||
return {
|
||||
abort() {
|
||||
controller.abort()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
try {
|
||||
if (!dataResult.value.importKey) {
|
||||
Message.warning('请先上传文件,解析导入数据')
|
||||
return false
|
||||
}
|
||||
form.importKey = dataResult.value.importKey
|
||||
const res = await batchImport(form)
|
||||
Message.success(`导入成功! 表格总数${res.data.totalRows}, 修改${res.data.updateRows}`)
|
||||
emit('save-success')
|
||||
return true
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const onOpen = () => {
|
||||
reset()
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
defineExpose({ onOpen })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
fieldset {
|
||||
padding: 15px 15px 0 15px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid var(--color-neutral-3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
fieldset legend {
|
||||
color: rgb(var(--gray-10));
|
||||
padding: 2px 5px 2px 5px;
|
||||
border: 1px solid var(--color-neutral-3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.file-box {
|
||||
margin-bottom: 20px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -19,6 +19,9 @@ import { useWindowSize } from '@vueuse/core'
|
||||
import { addMaterialInfo, getMaterialInfo, updateMaterialInfo } from '@/apis/material/materialInfo'
|
||||
import { type ColumnItem, GiForm } from '@/components/GiForm'
|
||||
import { useResetReactive } from '@/hooks'
|
||||
import { materialType } from "@/hooks/app/materialType";
|
||||
import {materialProcess} from "@/hooks/app/materialProcess";
|
||||
import {useDict} from "@/hooks/app";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'save-success'): void
|
||||
@@ -32,6 +35,11 @@ const isUpdate = computed(() => !!dataId.value)
|
||||
const title = computed(() => (isUpdate.value ? '修改物料管理' : '新增物料管理'))
|
||||
const formRef = ref<InstanceType<typeof GiForm>>()
|
||||
|
||||
const { materialTypeList, getMaterialTypeSelect } = materialType()
|
||||
const { light_level } = useDict('light_level')
|
||||
|
||||
|
||||
|
||||
const [form, resetForm] = useResetReactive({
|
||||
// todo 待补充
|
||||
})
|
||||
@@ -52,14 +60,63 @@ const columns: ColumnItem[] = reactive([
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: '物料单位重量,单位(g)',
|
||||
label: '物料单位重量(g)',
|
||||
field: 'unitWeight',
|
||||
type: 'input-number',
|
||||
span: 24,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: '物料规格',
|
||||
label: '物料品类',
|
||||
field: 'materialTypeId',
|
||||
type: 'select',
|
||||
span: 24,
|
||||
required: true,
|
||||
props: {
|
||||
options: materialTypeList,
|
||||
allowClear: true,
|
||||
allowSearch: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '物料流程编码',
|
||||
field: 'materialProcess',
|
||||
type: 'input',
|
||||
span: 24,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: '批次',
|
||||
field: 'batch',
|
||||
type: 'input',
|
||||
span: 24,
|
||||
},
|
||||
{
|
||||
label: '灯光等级',
|
||||
field: 'lightLevel',
|
||||
type: 'input-number',
|
||||
props: {
|
||||
mode: 'button',
|
||||
min: 1,
|
||||
max: 300,
|
||||
step: 1,
|
||||
},
|
||||
span: 24,
|
||||
},
|
||||
{
|
||||
label: '物料直径',
|
||||
field: 'materialSpec',
|
||||
type: 'input-number',
|
||||
props: {
|
||||
min: 0,
|
||||
mode: 'button',
|
||||
step: 0.001,
|
||||
},
|
||||
span: 24,
|
||||
},
|
||||
{
|
||||
label: '物料颜色',
|
||||
field: 'color',
|
||||
type: 'input',
|
||||
span: 24,
|
||||
},
|
||||
@@ -101,6 +158,9 @@ const save = async () => {
|
||||
const onAdd = async () => {
|
||||
reset()
|
||||
dataId.value = ''
|
||||
if (!materialTypeList.value.length) {
|
||||
await getMaterialTypeSelect()
|
||||
}
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
@@ -109,6 +169,9 @@ const onUpdate = async (id: string) => {
|
||||
reset()
|
||||
dataId.value = id
|
||||
const { data } = await getMaterialInfo(id)
|
||||
if (!materialTypeList.value.length) {
|
||||
await getMaterialTypeSelect()
|
||||
}
|
||||
Object.assign(form, data)
|
||||
visible.value = true
|
||||
}
|
||||
@@ -116,4 +179,4 @@ const onUpdate = async (id: string) => {
|
||||
defineExpose({ onAdd, onUpdate })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -20,7 +20,7 @@
|
||||
:limit="1"
|
||||
:show-retry-button="false"
|
||||
:show-cancel-button="false"
|
||||
tip="仅支持zip格式"
|
||||
tip="仅支持zip格式,请确保图片名称和物料编码一致"
|
||||
:file-list="uploadFile"
|
||||
accept=".zip"
|
||||
/>
|
||||
|
||||
@@ -10,18 +10,27 @@
|
||||
:pagination="pagination"
|
||||
:disabled-tools="['size']"
|
||||
:disabled-column-keys="['name']"
|
||||
:selected-keys="selectedKeys"
|
||||
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
|
||||
@select-all="selectAll"
|
||||
@select="select"
|
||||
@refresh="search"
|
||||
>
|
||||
<!-- toolbar 部分保持不变 -->
|
||||
<template #toolbar-left>
|
||||
<a-input-search v-model="queryForm.materialName" placeholder="请输入物料名称" allow-clear @search="search" />
|
||||
<a-input-search v-model="queryForm.encoding" placeholder="请输入物料编码" allow-clear @search="search" />
|
||||
<a-input-search v-model="queryForm.batch" placeholder="请输入批次" allow-clear @search="search" />
|
||||
<a-button @click="reset">
|
||||
<template #icon><icon-refresh /></template>
|
||||
<template #default>重置</template>
|
||||
</a-button>
|
||||
</template>
|
||||
<template #toolbar-right>
|
||||
<a-button v-permission="['admin:materialInfo:delete']" type="outline" status="danger" @click="onDelete">
|
||||
<template #icon><icon-delete /></template>
|
||||
<template #default>删除</template>
|
||||
</a-button>
|
||||
<a-button v-permission="['admin:materialInfo:add']" type="primary" @click="onAdd">
|
||||
<template #icon><icon-plus /></template>
|
||||
<template #default>新增</template>
|
||||
@@ -36,32 +45,19 @@
|
||||
</a-button>
|
||||
<a-button v-permission="['admin:materialInfo:import']" @click="onPhotosImport">
|
||||
<template #icon><icon-upload /></template>
|
||||
<template #default>照片批量导入</template>
|
||||
<template #default>照片导入</template>
|
||||
</a-button>
|
||||
<a-button v-permission="['admin:materialInfo:import']" @click="onBatchImport">
|
||||
<template #icon><icon-upload /></template>
|
||||
<template #default>批次导入</template>
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<!-- 【修改点】照片列插槽 -->
|
||||
<template #photoUrl="{ record }">
|
||||
<div class="photo-container">
|
||||
<!-- 1. 正常显示图片:有地址 且 未标记为错误 -->
|
||||
<img
|
||||
v-if="record.photoUrl && !record.photoLoadError"
|
||||
:src="record.photoUrl"
|
||||
alt="物料照片"
|
||||
class="material-photo"
|
||||
@load="handleImgLoad(record)"
|
||||
@error="handleImgError(record, $event)"
|
||||
/>
|
||||
|
||||
<!-- 2. 无地址 -->
|
||||
<span v-else-if="!record.photoUrl" class="no-photo">暂无照片</span>
|
||||
|
||||
<!-- 3. 地址存在但加载失败 -->
|
||||
<span v-else class="photo-error">
|
||||
<icon-exclamation-circle-fill class="error-icon" />
|
||||
照片异常
|
||||
</span>
|
||||
</div>
|
||||
<a-image
|
||||
width="60"
|
||||
:src="record.photoUrl"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #action="{ record }">
|
||||
@@ -72,7 +68,7 @@
|
||||
status="danger"
|
||||
:disabled="record.disabled"
|
||||
:title="record.disabled ? '不可删除' : '删除'"
|
||||
@click="onDelete(record)"
|
||||
@click="onDeleteOne(record)"
|
||||
>
|
||||
删除
|
||||
</a-link>
|
||||
@@ -83,38 +79,39 @@
|
||||
<MaterialInfoAddModal ref="MaterialInfoAddModalRef" @save-success="search" />
|
||||
<MaterialInfoImportDrawer ref="MaterialInfoImportDrawerRef" @save-success="search" />
|
||||
<PhotosImport ref="PhotosImportRef" @save-success="search"/>
|
||||
<BatchImport ref="BatchImportRef" @save-success="search"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// ... 保持原有的 import 不变
|
||||
import MaterialInfoAddModal from './MaterialInfoAddModal.vue'
|
||||
import MaterialInfoImportDrawer from './MaterialInfoImportDrawer.vue'
|
||||
import PhotosImport from '@/views/material/PhotosImport.vue';
|
||||
import BatchImport from '@/views/material/BatchImport.vue';
|
||||
import { type MaterialInfoQuery, type MaterialInfoResp, deleteMaterialInfo, exportMaterialInfo, listMaterialInfo } from '@/apis/material/materialInfo'
|
||||
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
||||
import { useDownload, useTable } from '@/hooks'
|
||||
import { isMobile } from '@/utils'
|
||||
import has from '@/utils/has'
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
|
||||
defineOptions({ name: 'MaterialInfo' })
|
||||
|
||||
// 扩展类型定义,允许动态添加 photoLoadError 属性
|
||||
// 如果 TS 报错,可以在 MaterialInfoResp 接口定义中添加 photoLoadError?: boolean;
|
||||
interface MaterialInfoRespWithStatus extends MaterialInfoResp {
|
||||
photoLoadError?: boolean;
|
||||
}
|
||||
|
||||
const queryForm = reactive<MaterialInfoQuery>({
|
||||
materialName: undefined,
|
||||
encoding: undefined,
|
||||
sort: ['id,desc'],
|
||||
batch: undefined,
|
||||
mark: undefined,
|
||||
sort: ['mi.id,desc'],
|
||||
})
|
||||
|
||||
const {
|
||||
tableData: dataList,
|
||||
loading,
|
||||
pagination,
|
||||
selectedKeys,
|
||||
select,
|
||||
selectAll,
|
||||
search,
|
||||
handleDelete,
|
||||
} = useTable((page) => listMaterialInfo({ ...queryForm, ...page }), { immediate: true })
|
||||
@@ -122,9 +119,14 @@ const {
|
||||
const columns = ref<TableInstanceColumns[]>([
|
||||
{ title: '物料名称', dataIndex: 'materialName', slotName: 'materialName' },
|
||||
{ title: '物料编码', dataIndex: 'encoding', slotName: 'encoding' },
|
||||
{ title: '物料单位重量(g)', dataIndex: 'unitWeight', slotName: 'unitWeight' },
|
||||
{ title: '物料规格', dataIndex: 'materialSpec', slotName: 'materialSpec' },
|
||||
{ title: '物料照片', dataIndex: 'photoUrl', slotName: 'photoUrl', width: 120, align: 'center' },
|
||||
{ title: '物料单位重量(g)', dataIndex: 'unitWeight', slotName: 'unitWeight' },
|
||||
{ title: '物料品类', dataIndex: 'typeName' },
|
||||
{ title: '批次', dataIndex: 'batch' },
|
||||
{ title: '物料直径', dataIndex: 'materialSpec', slotName: 'materialSpec', show: false },
|
||||
{ title: '物料颜色', dataIndex: 'color', slotName: 'color', show: false },
|
||||
{ title: '物料流程', dataIndex: 'materialProcess', slotName: 'materialProcess', show: false },
|
||||
{ title: '灯光等级', dataIndex: 'lightLevel', slotName: 'lightLevel', show: false },
|
||||
{ title: '创建人', dataIndex: 'createUserString', slotName: 'createUser' },
|
||||
{ title: '创建时间', dataIndex: 'createTime', slotName: 'createTime' },
|
||||
{
|
||||
@@ -144,28 +146,28 @@ const reset = () => {
|
||||
search()
|
||||
}
|
||||
|
||||
const onDelete = (record: MaterialInfoResp) => {
|
||||
const onDeleteOne = (record: MaterialInfoResp) => {
|
||||
return handleDelete(() => deleteMaterialInfo(record.id), {
|
||||
content: `是否确定删除该条数据?`,
|
||||
showModal: true,
|
||||
})
|
||||
}
|
||||
|
||||
// 删除
|
||||
const onDelete = () => {
|
||||
if (!selectedKeys.value.length) {
|
||||
return Message.warning('请选择数据')
|
||||
}
|
||||
return handleDelete(() => deleteMaterialInfo(selectedKeys.value), {
|
||||
content: `是否确定删除选中的 ${selectedKeys.value.length} 条数据?`,
|
||||
showModal: true
|
||||
})
|
||||
}
|
||||
|
||||
const onExport = () => {
|
||||
useDownload(() => exportMaterialInfo(queryForm))
|
||||
}
|
||||
|
||||
// 【修改点】图片加载处理逻辑
|
||||
const handleImgLoad = (record: MaterialInfoRespWithStatus) => {
|
||||
record.photoLoadError = false
|
||||
}
|
||||
|
||||
const handleImgError = (record: MaterialInfoRespWithStatus, e: Event) => {
|
||||
// 标记为加载错误,触发模板渲染 "照片异常"
|
||||
record.photoLoadError = true
|
||||
console.warn(`物料照片加载失败: ${record.photoUrl}`)
|
||||
}
|
||||
|
||||
const MaterialInfoAddModalRef = ref<InstanceType<typeof MaterialInfoAddModal>>()
|
||||
const onAdd = () => {
|
||||
MaterialInfoAddModalRef.value?.onAdd()
|
||||
@@ -184,6 +186,12 @@ const PhotosImportRef = ref<InstanceType<typeof PhotosImport>>()
|
||||
const onPhotosImport = () => {
|
||||
PhotosImportRef.value?.onOpen()
|
||||
}
|
||||
|
||||
const BatchImportRef = ref<InstanceType<typeof BatchImport>>()
|
||||
const onBatchImport = () => {
|
||||
BatchImportRef.value?.onOpen()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -226,4 +234,4 @@ const onPhotosImport = () => {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
103
src/views/materialProcess/MaterialProcessAddModal.vue
Normal file
103
src/views/materialProcess/MaterialProcessAddModal.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
:title="title"
|
||||
:mask-closable="false"
|
||||
:esc-to-close="false"
|
||||
:width="width >= 600 ? 600 : '100%'"
|
||||
draggable
|
||||
@before-ok="save"
|
||||
@close="reset"
|
||||
>
|
||||
<GiForm ref="formRef" v-model="form" :columns="columns" />
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import {
|
||||
addMaterialProcess,
|
||||
updateMaterialProcess,
|
||||
type MaterialProcessResp
|
||||
} from '@/apis/materialProcess/materialProcess'
|
||||
import { type ColumnItem, GiForm } from '@/components/GiForm'
|
||||
import { useResetReactive } from '@/hooks'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'save-success'): void
|
||||
}>()
|
||||
|
||||
const { width } = useWindowSize()
|
||||
|
||||
const dataId = ref('')
|
||||
const visible = ref(false)
|
||||
const isUpdate = computed(() => !!dataId.value)
|
||||
const title = computed(() => (isUpdate.value ? '修改海康物料流程' : '新增海康物料流程'))
|
||||
const formRef = ref<InstanceType<typeof GiForm>>()
|
||||
|
||||
const [form, resetForm] = useResetReactive({
|
||||
// todo 待补充
|
||||
})
|
||||
|
||||
const columns: ColumnItem[] = reactive([
|
||||
{
|
||||
label: '流程名称',
|
||||
field: 'processName',
|
||||
type: 'input',
|
||||
span: 24,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: '流程编码',
|
||||
field: 'processCode',
|
||||
type: 'input',
|
||||
span: 24,
|
||||
required: true,
|
||||
},
|
||||
])
|
||||
|
||||
// 重置
|
||||
const reset = () => {
|
||||
formRef.value?.formRef?.resetFields()
|
||||
resetForm()
|
||||
}
|
||||
|
||||
// 保存
|
||||
const save = async () => {
|
||||
try {
|
||||
const isInvalid = await formRef.value?.formRef?.validate()
|
||||
if (isInvalid) return false
|
||||
if (isUpdate.value) {
|
||||
await updateMaterialProcess(form, dataId.value)
|
||||
Message.success('修改成功')
|
||||
} else {
|
||||
await addMaterialProcess(form)
|
||||
Message.success('新增成功')
|
||||
}
|
||||
emit('save-success')
|
||||
return true
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 新增
|
||||
const onAdd = async () => {
|
||||
reset()
|
||||
dataId.value = ''
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
// 修改
|
||||
const onUpdate = async (record: MaterialProcessResp) => {
|
||||
reset()
|
||||
dataId.value = record.id
|
||||
Object.assign(form, record)
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
defineExpose({ onAdd, onUpdate })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
185
src/views/materialProcess/MaterialProcessImportDrawer.vue
Normal file
185
src/views/materialProcess/MaterialProcessImportDrawer.vue
Normal file
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
v-model:visible="visible"
|
||||
title="导入物料流程"
|
||||
:mask-closable="false"
|
||||
:esc-to-close="false"
|
||||
:width="width >= 600 ? 600 : '100%'"
|
||||
ok-text="确认导入"
|
||||
cancel-text="取消导入"
|
||||
@before-ok="save"
|
||||
@close="reset"
|
||||
>
|
||||
<a-form ref="formRef" :model="form" size="large" auto-label-width>
|
||||
<a-alert v-if="!form.disabled" style="margin-bottom: 15px">
|
||||
请按照模板要求填写数据,填写完毕后,请先上传并进行解析。
|
||||
<template #action>
|
||||
<a-link @click="downloadTemplate">
|
||||
<template #icon><icon-download /></template>
|
||||
<template #default>下载模板</template>
|
||||
</a-link>
|
||||
</template>
|
||||
</a-alert>
|
||||
<fieldset>
|
||||
<legend>1.解析数据</legend>
|
||||
<div class="file-box">
|
||||
<a-upload
|
||||
draggable
|
||||
:custom-request="handleUpload"
|
||||
:limit="1"
|
||||
:show-retry-butto="false"
|
||||
:show-cancel-button="false" tip="仅支持xls、xlsx格式"
|
||||
:file-list="uploadFile"
|
||||
accept=".xls, .xlsx, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="dataResult.importKey">
|
||||
<div class="file-box">
|
||||
<a-space size="large">
|
||||
<a-statistic title="总计行数" :value="dataResult.totalRows" />
|
||||
<a-statistic title="正常行数" :value="dataResult.validRows" />
|
||||
</a-space>
|
||||
</div>
|
||||
<div class="file-box">
|
||||
<a-space size="large">
|
||||
<a-statistic title="已存在流程名称" :value="dataResult.duplicateNameRows" />
|
||||
<a-statistic title="已存在流程编码" :value="dataResult.duplicateCodeRows" />
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>2.导入策略</legend>
|
||||
<a-form-item label="流程名称已存在" field="duplicateName">
|
||||
<a-radio-group v-model="form.duplicateName" type="button">
|
||||
<a-radio :value="1">跳过该行</a-radio>
|
||||
<a-radio :value="2">修改数据</a-radio>
|
||||
<a-radio :value="3">停止导入</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="流程编码已存在" field="duplicateCode">
|
||||
<a-radio-group v-model="form.duplicateCode" type="button">
|
||||
<a-radio :value="1">跳过该行</a-radio>
|
||||
<a-radio :value="2">修改数据</a-radio>
|
||||
<a-radio :value="3">停止导入</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</fieldset>
|
||||
</a-form>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type FormInstance, Message, type RequestOption } from '@arco-design/web-vue'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import { downloadMaterialProcessImportTemplate, importMaterialProcess, parseImportMaterialProcess } from '@/apis/materialProcess/materialProcess'
|
||||
import { useDownload, useResetReactive } from '@/hooks'
|
||||
import { IconDownload as iconDownload } from '@arco-design/web-vue/es/icon'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'save-success'): void
|
||||
}>()
|
||||
|
||||
const { width } = useWindowSize()
|
||||
|
||||
const visible = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const uploadFile = ref([])
|
||||
|
||||
const [form, resetForm] = useResetReactive({
|
||||
importKey: '',
|
||||
duplicateName: 1,
|
||||
duplicateCode: 1,
|
||||
})
|
||||
|
||||
const dataResult = ref({
|
||||
importKey: '',
|
||||
totalRows: 0,
|
||||
validRows: 0,
|
||||
duplicateNameRows: 0,
|
||||
duplicateCodeRows: 0,
|
||||
})
|
||||
|
||||
// 重置
|
||||
const reset = () => {
|
||||
formRef.value?.resetFields()
|
||||
dataResult.value.importKey = ''
|
||||
uploadFile.value = []
|
||||
resetForm()
|
||||
}
|
||||
|
||||
// 下载模板
|
||||
const downloadTemplate = () => {
|
||||
useDownload(() => downloadMaterialProcessImportTemplate())
|
||||
}
|
||||
|
||||
// 上传解析导入数据
|
||||
const handleUpload = (options: RequestOption) => {
|
||||
const controller = new AbortController();
|
||||
(async function requestWrap() {
|
||||
const { onProgress, onError, onSuccess, fileItem, name = 'file' } = options
|
||||
onProgress(20)
|
||||
const formData = new FormData()
|
||||
formData.append(name as string, fileItem.file as Blob)
|
||||
try {
|
||||
const res = await parseImportMaterialProcess(formData)
|
||||
dataResult.value = res.data
|
||||
Message.success('上传解析成功')
|
||||
onSuccess(res)
|
||||
} catch (error) {
|
||||
onError(error)
|
||||
}
|
||||
})()
|
||||
return {
|
||||
abort() {
|
||||
controller.abort()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 执行导入
|
||||
const save = async () => {
|
||||
try {
|
||||
if (!dataResult.value.importKey) {
|
||||
Message.warning('请先上传文件,解析导入数据')
|
||||
return false
|
||||
}
|
||||
form.importKey = dataResult.value.importKey
|
||||
const res = await importMaterialProcess(form)
|
||||
Message.success(`导入成功! 新增${res.data.insertCount}, 修改${res.data.updateCount},总计处理${res.data.totalHandleCount}`)
|
||||
emit('save-success')
|
||||
return true
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 打开
|
||||
const onOpen = () => {
|
||||
reset()
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
defineExpose({ onOpen })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
fieldset {
|
||||
padding: 15px 15px 0 15px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid var(--color-neutral-3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
fieldset legend {
|
||||
color: rgb(var(--gray-10));
|
||||
padding: 2px 5px 2px 5px;
|
||||
border: 1px solid var(--color-neutral-3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.file-box {
|
||||
margin-bottom: 20px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
166
src/views/materialProcess/index.vue
Normal file
166
src/views/materialProcess/index.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<div class="gi_table_page">
|
||||
<GiTable
|
||||
title="物料流程管理"
|
||||
row-key="id"
|
||||
:data="dataList"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
|
||||
:pagination="pagination"
|
||||
:disabled-tools="['size']"
|
||||
:disabled-column-keys="['name']"
|
||||
:selected-keys="selectedKeys"
|
||||
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
|
||||
@select-all="selectAll"
|
||||
@select="select"
|
||||
@refresh="search"
|
||||
>
|
||||
<template #toolbar-left>
|
||||
<a-input-search v-model="queryForm.processName" placeholder="请输入流程名称" allow-clear @search="search" />
|
||||
<a-input-search v-model="queryForm.processCode" placeholder="请输入流程编码" allow-clear @search="search" />
|
||||
<a-button @click="reset">
|
||||
<template #icon><icon-refresh /></template>
|
||||
<template #default>重置</template>
|
||||
</a-button>
|
||||
</template>
|
||||
<template #toolbar-right>
|
||||
<a-button v-permission="['materialProcess:materialProcess:delete']" type="outline" status="danger" @click="onDelete">
|
||||
<template #icon><icon-delete /></template>
|
||||
<template #default>删除</template>
|
||||
</a-button>
|
||||
<a-button v-permission="['materialProcess:materialProcess:add']" type="primary" @click="onAdd">
|
||||
<template #icon><icon-plus /></template>
|
||||
<template #default>新增</template>
|
||||
</a-button>
|
||||
<a-button v-permission="['materialProcess:materialProcess:import']" @click="onImport">
|
||||
<template #icon><icon-upload /></template>
|
||||
<template #default>导入</template>
|
||||
</a-button>
|
||||
<a-button v-permission="['materialProcess:materialProcess:export']" @click="onExport">
|
||||
<template #icon><icon-download /></template>
|
||||
<template #default>导出</template>
|
||||
</a-button>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-link v-permission="['materialProcess:materialProcess:update']" title="修改" @click="onUpdate(record)">修改</a-link>
|
||||
<a-link
|
||||
v-permission="['materialProcess:materialProcess:delete']"
|
||||
status="danger"
|
||||
:disabled="record.disabled"
|
||||
:title="record.disabled ? '不可删除' : '删除'"
|
||||
@click="onDeleteOne(record)"
|
||||
>
|
||||
删除
|
||||
</a-link>
|
||||
</a-space>
|
||||
</template>
|
||||
</GiTable>
|
||||
|
||||
<MaterialProcessAddModal ref="MaterialProcessAddModalRef" @save-success="search" />
|
||||
<MaterialProcessImportDrawer ref="MaterialProcessImportDrawerRef" @save-success="search" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MaterialProcessAddModal from './MaterialProcessAddModal.vue'
|
||||
import MaterialProcessImportDrawer from './MaterialProcessImportDrawer.vue'
|
||||
import { type MaterialProcessResp, type MaterialProcessQuery, deleteMaterialProcess, exportMaterialProcess, listMaterialProcess } from '@/apis/materialProcess/materialProcess'
|
||||
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
||||
import { useDownload, useTable } from '@/hooks'
|
||||
import { isMobile } from '@/utils'
|
||||
import has from '@/utils/has'
|
||||
import { IconUpload as iconUpload, IconDownload as iconDownload } from '@arco-design/web-vue/es/icon'
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
|
||||
defineOptions({ name: 'MaterialProcess' })
|
||||
|
||||
|
||||
const queryForm = reactive<MaterialProcessQuery>({
|
||||
processName: undefined,
|
||||
processCode: undefined,
|
||||
sort: ['id,desc']
|
||||
})
|
||||
|
||||
const {
|
||||
tableData: dataList,
|
||||
loading,
|
||||
pagination,
|
||||
selectedKeys,
|
||||
selectAll,
|
||||
select,
|
||||
search,
|
||||
handleDelete
|
||||
} = useTable((page) => listMaterialProcess({ ...queryForm, ...page }), { immediate: true })
|
||||
const columns = ref<TableInstanceColumns[]>([
|
||||
{ title: '流程名称', dataIndex: 'processName', slotName: 'processName' },
|
||||
{ title: '流程编码', dataIndex: 'processCode', slotName: 'processCode' },
|
||||
{ title: '创建时间', dataIndex: 'createTime', slotName: 'createTime' },
|
||||
{ title: '更新时间', dataIndex: 'updateTime', slotName: 'updateTime', show: false },
|
||||
{ title: '创建人', dataIndex: 'createUserString', slotName: 'createUser' },
|
||||
{ title: '更新人', dataIndex: 'updateUserString', slotName: 'updateUser', show: false },
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
slotName: 'action',
|
||||
width: 160,
|
||||
align: 'center',
|
||||
fixed: !isMobile() ? 'right' : undefined,
|
||||
show: has.hasPermOr(['materialProcess:materialProcess:detail', 'materialProcess:materialProcess:update', 'materialProcess:materialProcess:delete'])
|
||||
}
|
||||
]);
|
||||
|
||||
// 重置
|
||||
const reset = () => {
|
||||
queryForm.processName = undefined
|
||||
queryForm.processCode = undefined
|
||||
search()
|
||||
}
|
||||
|
||||
// 删除
|
||||
const onDeleteOne = (record: MaterialProcessResp) => {
|
||||
return handleDelete(() => deleteMaterialProcess(record.id), {
|
||||
content: `是否确定删除该条数据?`,
|
||||
showModal: true
|
||||
})
|
||||
}
|
||||
|
||||
// 删除
|
||||
const onDelete = () => {
|
||||
if (!selectedKeys.value.length) {
|
||||
return Message.warning('请选择数据')
|
||||
}
|
||||
return handleDelete(() => deleteMaterialProcess(selectedKeys.value), {
|
||||
content: `是否确定删除选中的 ${selectedKeys.value.length} 条数据?`,
|
||||
showModal: true
|
||||
})
|
||||
}
|
||||
|
||||
// 导出
|
||||
const onExport = () => {
|
||||
useDownload(() => exportMaterialProcess(queryForm))
|
||||
}
|
||||
|
||||
const MaterialProcessAddModalRef = ref<InstanceType<typeof MaterialProcessAddModal>>()
|
||||
const MaterialProcessImportDrawerRef = ref<InstanceType<typeof MaterialProcessImportDrawer>>()
|
||||
|
||||
// 新增
|
||||
const onAdd = () => {
|
||||
MaterialProcessAddModalRef.value?.onAdd()
|
||||
}
|
||||
|
||||
// 修改
|
||||
const onUpdate = (record: MaterialProcessResp) => {
|
||||
MaterialProcessAddModalRef.value?.onUpdate(record)
|
||||
}
|
||||
|
||||
// 导入
|
||||
const onImport = () => {
|
||||
MaterialProcessImportDrawerRef.value?.onOpen()
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
122
src/views/materialType/MaterialTypeAddModal.vue
Normal file
122
src/views/materialType/MaterialTypeAddModal.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
:title="title"
|
||||
:mask-closable="false"
|
||||
:esc-to-close="false"
|
||||
:width="width >= 600 ? 600 : '100%'"
|
||||
draggable
|
||||
@before-ok="save"
|
||||
@close="reset"
|
||||
>
|
||||
<GiForm ref="formRef" v-model="form" :columns="columns">
|
||||
<template #floatRatio>
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<a-input-number
|
||||
v-model="form.downFloatRatio"
|
||||
style="width: 45%"
|
||||
:min="-100"
|
||||
:max="100"
|
||||
:mode="'button'"
|
||||
placeholder="最小值"
|
||||
required: true,
|
||||
/>
|
||||
<span style="color: #999;">至</span>
|
||||
<a-input-number
|
||||
v-model="form.upFloatRatio"
|
||||
style="width: 45%"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:mode="'button'"
|
||||
placeholder="最大值"
|
||||
required: true,
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</GiForm>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import { addMaterialType, type MaterialTypeResp, updateMaterialType} from '@/apis/materialType/materialType'
|
||||
import { type ColumnItem, GiForm } from '@/components/GiForm'
|
||||
import { useResetReactive } from '@/hooks'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'save-success'): void
|
||||
}>()
|
||||
|
||||
const { width } = useWindowSize()
|
||||
|
||||
const dataId = ref('')
|
||||
const visible = ref(false)
|
||||
const isUpdate = computed(() => !!dataId.value)
|
||||
const title = computed(() => (isUpdate.value ? '修改物料品类' : '新增物料品类'))
|
||||
const formRef = ref<InstanceType<typeof GiForm>>()
|
||||
|
||||
const [form, resetForm] = useResetReactive({
|
||||
|
||||
})
|
||||
|
||||
const columns: ColumnItem[] = reactive([
|
||||
{
|
||||
label: '品类名称',
|
||||
field: 'typeName',
|
||||
type: 'input',
|
||||
span: 24,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: '浮动范围(%)',
|
||||
field: 'floatRatio',
|
||||
type: 'custom',
|
||||
span: 24,
|
||||
},
|
||||
])
|
||||
|
||||
// 重置
|
||||
const reset = () => {
|
||||
formRef.value?.formRef?.resetFields()
|
||||
resetForm()
|
||||
}
|
||||
|
||||
// 保存
|
||||
const save = async () => {
|
||||
try {
|
||||
const isInvalid = await formRef.value?.formRef?.validate()
|
||||
if (isInvalid) return false
|
||||
if (isUpdate.value) {
|
||||
await updateMaterialType(form, dataId.value)
|
||||
Message.success('修改成功')
|
||||
} else {
|
||||
await addMaterialType(form)
|
||||
Message.success('新增成功')
|
||||
}
|
||||
emit('save-success')
|
||||
return true
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 新增
|
||||
const onAdd = async () => {
|
||||
reset()
|
||||
dataId.value = ''
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
// 修改
|
||||
const onUpdate = async (data: MaterialTypeResp) => {
|
||||
reset()
|
||||
dataId.value = data.id
|
||||
Object.assign(form, data)
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
defineExpose({ onAdd, onUpdate })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
179
src/views/materialType/MaterialTypeImportDrawer.vue
Normal file
179
src/views/materialType/MaterialTypeImportDrawer.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
v-model:visible="visible"
|
||||
title="导入物料品类"
|
||||
:mask-closable="false"
|
||||
:esc-to-close="false"
|
||||
:width="width >= 600 ? 600 : '100%'"
|
||||
ok-text="确认导入"
|
||||
cancel-text="取消导入"
|
||||
@before-ok="save"
|
||||
@close="reset"
|
||||
>
|
||||
<a-form ref="formRef" :model="form" size="large" auto-label-width>
|
||||
<a-alert v-if="!form.disabled" style="margin-bottom: 15px">
|
||||
请按照模板要求填写数据,填写完毕后,请先上传并进行解析。
|
||||
<template #action>
|
||||
<a-link @click="downloadTemplate">
|
||||
<template #icon><GiSvgIcon name="file-excel" :size="16" /></template>
|
||||
<template #default>下载模板</template>
|
||||
</a-link>
|
||||
</template>
|
||||
</a-alert>
|
||||
<fieldset>
|
||||
<legend>1.解析数据</legend>
|
||||
<div class="file-box">
|
||||
<a-upload
|
||||
draggable
|
||||
:custom-request="handleUpload"
|
||||
:limit="1"
|
||||
:show-retry-butto="false"
|
||||
:show-cancel-button="false" tip="仅支持xls、xlsx格式"
|
||||
:file-list="uploadFile"
|
||||
accept=".xls, .xlsx, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="dataResult.importKey">
|
||||
<div class="file-box">
|
||||
<a-space size="large">
|
||||
<a-statistic title="总计行数" :value="dataResult.totalRows" />
|
||||
<a-statistic title="正常行数" :value="dataResult.validRows" />
|
||||
</a-space>
|
||||
</div>
|
||||
<div class="file-box">
|
||||
<a-space size="large">
|
||||
<a-statistic title="已存在品类名" :value="dataResult.duplicateNameRows" />
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>2.导入策略</legend>
|
||||
<a-form-item label="品类名已存在" field="duplicateTypeName">
|
||||
<a-radio-group v-model="form.duplicateTypeName" type="button">
|
||||
<a-radio :value="1">跳过该行</a-radio>
|
||||
<a-radio :value="2">修改数据</a-radio>
|
||||
<a-radio :value="3">停止导入</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</fieldset>
|
||||
</a-form>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type FormInstance, Message, type RequestOption } from '@arco-design/web-vue'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import {
|
||||
type MaterialTypeImportResp,
|
||||
downloadMaterialTypeImportTemplate,
|
||||
importMaterialType,
|
||||
parseImportMaterialType,
|
||||
} from '@/apis/materialType/materialType'
|
||||
import { useDownload, useResetReactive } from '@/hooks'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'save-success'): void
|
||||
}>()
|
||||
|
||||
const { width } = useWindowSize()
|
||||
|
||||
const visible = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const uploadFile = ref([])
|
||||
|
||||
const [form, resetForm] = useResetReactive({
|
||||
importKey: '',
|
||||
duplicateTypeName: 1,
|
||||
})
|
||||
|
||||
const dataResult = ref<MaterialTypeImportResp>({
|
||||
importKey: '',
|
||||
totalRows: 0,
|
||||
validRows: 0,
|
||||
duplicateNameRows: 0,
|
||||
})
|
||||
|
||||
// 重置
|
||||
const reset = () => {
|
||||
formRef.value?.resetFields()
|
||||
dataResult.value.importKey = ''
|
||||
uploadFile.value = []
|
||||
resetForm()
|
||||
}
|
||||
|
||||
// 下载模板
|
||||
const downloadTemplate = () => {
|
||||
useDownload(() => downloadMaterialTypeImportTemplate())
|
||||
}
|
||||
|
||||
// 上传解析导入数据
|
||||
const handleUpload = (options: RequestOption) => {
|
||||
const controller = new AbortController();
|
||||
(async function requestWrap() {
|
||||
const { onProgress, onError, onSuccess, fileItem, name = 'file' } = options
|
||||
onProgress(20)
|
||||
const formData = new FormData()
|
||||
formData.append(name as string, fileItem.file as Blob)
|
||||
try {
|
||||
const res = await parseImportMaterialType(formData)
|
||||
dataResult.value = res.data
|
||||
Message.success('上传解析成功')
|
||||
onSuccess(res)
|
||||
} catch (error) {
|
||||
onError(error)
|
||||
}
|
||||
})()
|
||||
return {
|
||||
abort() {
|
||||
controller.abort()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 执行导入
|
||||
const save = async () => {
|
||||
try {
|
||||
if (!dataResult.value.importKey) {
|
||||
Message.warning('请先上传文件,解析导入数据')
|
||||
return false
|
||||
}
|
||||
form.importKey = dataResult.value.importKey
|
||||
const res = await importMaterialType(form)
|
||||
Message.success(`导入成功! 新增${res.data.insertRows}, 修改${res.data.updateRows},总计处理${res.data.totalRows}`)
|
||||
emit('save-success')
|
||||
return true
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 打开
|
||||
const onOpen = () => {
|
||||
reset()
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
defineExpose({ onOpen })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
fieldset {
|
||||
padding: 15px 15px 0 15px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid var(--color-neutral-3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
fieldset legend {
|
||||
color: rgb(var(--gray-10));
|
||||
padding: 2px 5px 2px 5px;
|
||||
border: 1px solid var(--color-neutral-3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.file-box {
|
||||
margin-bottom: 20px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
149
src/views/materialType/index.vue
Normal file
149
src/views/materialType/index.vue
Normal file
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<div class="gi_table_page">
|
||||
<GiTable
|
||||
title="物料品类管理"
|
||||
row-key="id"
|
||||
:data="dataList"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
|
||||
:pagination="pagination"
|
||||
:disabled-tools="['size']"
|
||||
:disabled-column-keys="['name']"
|
||||
:selected-keys="selectedKeys"
|
||||
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
|
||||
@select-all="selectAll"
|
||||
@select="select"
|
||||
@refresh="search"
|
||||
>
|
||||
<template #toolbar-left>
|
||||
<a-input-search v-model="queryForm.typeName" placeholder="请输入品类名称" allow-clear @search="search" />
|
||||
<a-button @click="reset">
|
||||
<template #icon><icon-refresh /></template>
|
||||
<template #default>重置</template>
|
||||
</a-button>
|
||||
</template>
|
||||
<template #toolbar-right>
|
||||
<a-button v-permission="['materialType:materialType:add']" type="primary" @click="onAdd">
|
||||
<template #icon><icon-plus /></template>
|
||||
<template #default>新增</template>
|
||||
</a-button>
|
||||
<a-button v-permission="['materialType:materialType:import']" @click="onImport">
|
||||
<template #icon><icon-upload /></template>
|
||||
<template #default>导入</template>
|
||||
</a-button>
|
||||
<a-button v-permission="['materialType:materialType:export']" @click="onExport">
|
||||
<template #icon><icon-download /></template>
|
||||
<template #default>导出</template>
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<template #floatRatio="{ record }">
|
||||
{{ record.downFloatRatio + '% ~ ' + record.upFloatRatio + '%' }}
|
||||
</template>
|
||||
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-link v-permission="['materialType:materialType:update']" title="修改" @click="onUpdate(record)">修改</a-link>
|
||||
<a-link
|
||||
v-permission="['materialType:materialType:delete']"
|
||||
status="danger"
|
||||
@click="onDelete(record.id)"
|
||||
>
|
||||
删除
|
||||
</a-link>
|
||||
</a-space>
|
||||
</template>
|
||||
</GiTable>
|
||||
|
||||
<MaterialTypeAddModal ref="MaterialTypeAddModalRef" @save-success="search" />
|
||||
<MaterialTypeImportDrawer ref="MaterialTypeImportDrawerRef" @save-success="search" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import MaterialTypeAddModal from './MaterialTypeAddModal.vue'
|
||||
import MaterialTypeImportDrawer from './MaterialTypeImportDrawer.vue'
|
||||
import { type MaterialTypeResp, type MaterialTypeQuery, deleteMaterialType, exportMaterialType, listMaterialType } from '@/apis/materialType/materialType'
|
||||
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
||||
import { useDownload, useTable } from '@/hooks'
|
||||
import { isMobile } from '@/utils'
|
||||
import has from '@/utils/has'
|
||||
|
||||
defineOptions({ name: 'MaterialType' })
|
||||
|
||||
|
||||
const queryForm = reactive<MaterialTypeQuery>({
|
||||
typeName: undefined,
|
||||
sort: ['id,desc']
|
||||
})
|
||||
|
||||
const {
|
||||
tableData: dataList,
|
||||
loading,
|
||||
pagination,
|
||||
selectedKeys,
|
||||
select,
|
||||
selectAll,
|
||||
search,
|
||||
handleDelete
|
||||
} = useTable((page) => listMaterialType({ ...queryForm, ...page }), { immediate: true })
|
||||
const columns = ref<TableInstanceColumns[]>([
|
||||
{ title: '品类名称', dataIndex: 'typeName', slotName: 'typeName' },
|
||||
{ title: '品类上下浮动范围(%)', dataIndex: 'upFloatRatio', slotName: 'floatRatio' },
|
||||
{ title: '创建时间', dataIndex: 'createTime', slotName: 'createTime' },
|
||||
{ title: '更新时间', dataIndex: 'updateTime', slotName: 'updateTime', show: false },
|
||||
{ title: '创建人', dataIndex: 'createUserString', slotName: 'createUser' },
|
||||
{ title: '更新人', dataIndex: 'updateUserString', slotName: 'updateUser', show: false },
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
slotName: 'action',
|
||||
width: 160,
|
||||
align: 'center',
|
||||
fixed: !isMobile() ? 'right' : undefined,
|
||||
show: has.hasPermOr(['materialType:materialType:detail', 'materialType:materialType:update', 'materialType:materialType:delete'])
|
||||
}
|
||||
]);
|
||||
|
||||
// 重置
|
||||
const reset = () => {
|
||||
queryForm.typeName = undefined
|
||||
search()
|
||||
}
|
||||
|
||||
// 删除
|
||||
const onDelete = (id) => {
|
||||
return handleDelete(() => deleteMaterialType(id), {
|
||||
content: `是否确定删除该条数据?`,
|
||||
showModal: true
|
||||
})
|
||||
}
|
||||
|
||||
// 导出
|
||||
const onExport = () => {
|
||||
useDownload(() => exportMaterialType(queryForm))
|
||||
}
|
||||
|
||||
const MaterialTypeAddModalRef = ref<InstanceType<typeof MaterialTypeAddModal>>()
|
||||
const MaterialTypeImportDrawerRef = ref<InstanceType<typeof MaterialTypeImportDrawer>>()
|
||||
|
||||
// 新增
|
||||
const onAdd = () => {
|
||||
MaterialTypeAddModalRef.value?.onAdd()
|
||||
}
|
||||
|
||||
// 修改
|
||||
const onUpdate = (record: MaterialTypeResp) => {
|
||||
MaterialTypeAddModalRef.value?.onUpdate(record)
|
||||
}
|
||||
|
||||
// 导入
|
||||
const onImport = () => {
|
||||
MaterialTypeImportDrawerRef.value?.onOpen()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -1,377 +0,0 @@
|
||||
<template>
|
||||
<div class="gi_page">
|
||||
<div class="container">
|
||||
<h2 class="page-title">标签打印</h2>
|
||||
|
||||
<!-- 标签参数设置 -->
|
||||
<div class="form-section">
|
||||
<a-form :model="formData" layout="vertical">
|
||||
<div class="form-grid">
|
||||
<div class="form-grid-item">
|
||||
<a-form-item label="物料名称">
|
||||
<a-input v-model="formData.materialName" placeholder="请输入物料名称" :disabled="isFromWorkOrder" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div class="form-grid-item">
|
||||
<a-form-item label="物料编码">
|
||||
<a-input v-model="formData.encoding" placeholder="请输入物料编码" :disabled="isFromWorkOrder" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div class="form-grid-item">
|
||||
<a-form-item label="工单编号">
|
||||
<a-input v-model="formData.orderNo" placeholder="请输入工单编号" :disabled="isFromWorkOrder" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div class="form-grid-item">
|
||||
<a-form-item label="生产批次" required>
|
||||
<a-input v-model="formData.productionBatch" placeholder="请输入生产批次" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<a-button type="primary" @click="generateLabel" :disabled="!formData.productionBatch">生成标签</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<!-- 标签预览 -->
|
||||
<div v-if="labelData.partName" class="label-preview-section">
|
||||
<h3>标签预览</h3>
|
||||
<div class="label-container" ref="labelContainer">
|
||||
<div class="label" v-for="index in 1" :key="index">
|
||||
<table class="label-table">
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-field">零件名称</div>
|
||||
<div class="label-value">{{ labelData.partName }}</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-field">生产日期</div>
|
||||
<div class="label-value">{{ labelData.productionDate }}</div>
|
||||
</td>
|
||||
<td class="label-cell qr-cell" rowspan="4">
|
||||
<div class="qr-code">
|
||||
<img :src="`https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${encodeURIComponent(labelData.qrCodeData)}`" alt="QR Code" />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-field">零件号</div>
|
||||
<div class="label-value">{{ labelData.partNumber }}</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-field">数量</div>
|
||||
<div class="label-value">{{ labelData.quantity }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-field">标重(kg)</div>
|
||||
<div class="label-value">{{ labelData.standardWeight }}</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-field">包装签字</div>
|
||||
<div class="label-value">{{ labelData.packingSignature || '' }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-field">实重(kg)</div>
|
||||
<div class="label-value">{{ labelData.actualWeight || '' }}</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-field">检验签字</div>
|
||||
<div class="label-value">{{ labelData.inspectionSignature || '' }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- <tr>-->
|
||||
<!-- <td class="label-cell" colspan="2"></td>-->
|
||||
<!-- <td class="label-cell qr-cell">-->
|
||||
<!-- <div class="serial-number">序号: {{ labelData.serialNumber }}</div>-->
|
||||
<!-- </td>-->
|
||||
<!-- </tr>-->
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="label-actions">
|
||||
<a-button type="primary" @click="printLabel">打印标签</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
encoding: '',
|
||||
materialName: '',
|
||||
orderNo: '',
|
||||
productionBatch: ''
|
||||
})
|
||||
|
||||
// 是否是从工单页面跳转过来的
|
||||
const isFromWorkOrder = ref(false)
|
||||
|
||||
// 标签数据
|
||||
const labelData = reactive({
|
||||
partName: '',
|
||||
partNumber: '',
|
||||
standardWeight: '',
|
||||
actualWeight: '',
|
||||
productionDate: '',
|
||||
quantity: '',
|
||||
packingSignature: '',
|
||||
inspectionSignature: '',
|
||||
serialNumber: '',
|
||||
qrCodeData: '',
|
||||
})
|
||||
|
||||
// 标签容器引用
|
||||
const labelContainer = ref<HTMLElement | null>(null)
|
||||
|
||||
// 模拟获取标签数据的接口
|
||||
const fetchLabelData = async (params: any) => {
|
||||
// 模拟后端接口延迟
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
// 格式化生产日期为 yyyyMMddHHmm 格式
|
||||
const now = new Date()
|
||||
const formattedDate = now.getFullYear().toString() +
|
||||
String(now.getMonth() + 1).padStart(2, '0') +
|
||||
String(now.getDate()).padStart(2, '0') +
|
||||
String(now.getHours()).padStart(2, '0') +
|
||||
String(now.getMinutes()).padStart(2, '0')
|
||||
|
||||
// 模拟返回数据
|
||||
const mockData = {
|
||||
partName: params.materialName || 'PP0449002护套',
|
||||
partNumber: params.encoding || 'PP0449002',
|
||||
standardWeight: '0.39500',
|
||||
actualWeight: '',
|
||||
productionDate: formattedDate,
|
||||
quantity: '200',
|
||||
packingSignature: '',
|
||||
inspectionSignature: '',
|
||||
serialNumber: '4',
|
||||
qrCodeData: `PART:${params.encoding || 'PP0449002'},NAME:${params.materialName || 'PP0449002护套'},DATE:${formattedDate},QTY:200`,
|
||||
}
|
||||
resolve(mockData)
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
|
||||
// 生成标签
|
||||
const generateLabel = async () => {
|
||||
|
||||
if (!formData.productionBatch) {
|
||||
Message.error('请输入生产批次')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取标签数据
|
||||
const result = await fetchLabelData(formData)
|
||||
Object.assign(labelData, result)
|
||||
Message.success('标签生成成功')
|
||||
} catch (error) {
|
||||
console.error('生成标签失败:', error)
|
||||
Message.error('生成标签失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时,检查是否有参数传递
|
||||
onMounted(() => {
|
||||
// 从路由参数中获取数据
|
||||
const materialName = route.query.materialName as string
|
||||
const encoding = route.query.encoding as string
|
||||
const orderNo = route.query.orderNo as string
|
||||
|
||||
// 如果有参数传递,设置表单数据并标记为不可修改
|
||||
if (materialName && encoding && orderNo) {
|
||||
formData.materialName = materialName
|
||||
formData.encoding = encoding
|
||||
formData.orderNo = orderNo
|
||||
isFromWorkOrder.value = true
|
||||
|
||||
// 自动生成标签
|
||||
generateLabel()
|
||||
}
|
||||
})
|
||||
|
||||
// 打印标签
|
||||
const printLabel = async () => {
|
||||
await nextTick()
|
||||
if (labelContainer.value) {
|
||||
// 克隆标签容器内容用于打印
|
||||
const printContent = labelContainer.value.cloneNode(true) as HTMLElement
|
||||
|
||||
// 创建打印窗口
|
||||
const printWindow = window.open('', '_blank')
|
||||
if (printWindow) {
|
||||
printWindow.document.write(`
|
||||
<html>
|
||||
<head>
|
||||
<title>标签打印</title>
|
||||
<style>
|
||||
body { margin: 0; padding: 10mm; font-family: Arial, sans-serif; }
|
||||
.label-container { display: flex; flex-wrap: wrap; gap: 10mm; justify-content: center; }
|
||||
.label { width: 90mm; height: 36mm; border: 1px solid #000; padding: 0; box-sizing: border-box; page-break-inside: avoid; }
|
||||
.label-table { width: 100%; height: 100%; border-collapse: collapse; }
|
||||
.label-cell { border: 1px solid #000; padding: 1mm; vertical-align: top; }
|
||||
.qr-cell { width: 24mm; text-align: center; vertical-align: middle; }
|
||||
.label-field { font-size: 7pt; font-weight: bold; margin-bottom: 0.5mm; }
|
||||
.label-value { font-size: 7pt; }
|
||||
.qr-code img { width: 20mm; height: 20mm; margin: 1mm 0; }
|
||||
.serial-number { font-size: 7pt; margin-top: 1mm; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
${printContent.innerHTML}
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
printWindow.document.close()
|
||||
printWindow.focus()
|
||||
printWindow.print()
|
||||
printWindow.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defineOptions({ name: 'BarcodePrint' })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 表单区域 */
|
||||
.form-section {
|
||||
background-color: var(--color-bg-2);
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-grid-item {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 标签预览 */
|
||||
.label-preview-section {
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
background-color: var(--color-bg-2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.label-preview-section h3 {
|
||||
margin: 0 0 20px 0;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.label-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 450px;
|
||||
height: 180px;
|
||||
border: 1px solid #000;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.label-table {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.label-cell {
|
||||
border: 1px solid #000;
|
||||
padding: 5px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.qr-cell {
|
||||
width: 120px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.label-field {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.label-value {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.qr-code img {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.serial-number {
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.label-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* 确保输入框宽度一致 */
|
||||
:deep(.arco-input-wrapper) {
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
:deep(.arco-input) {
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -49,7 +49,7 @@ const columns: ColumnItem[] = reactive([
|
||||
field: 'configKey',
|
||||
type: 'input',
|
||||
span: 24,
|
||||
disabled: true,
|
||||
disabled: isUpdate.value,
|
||||
},
|
||||
{
|
||||
label: '参数键值',
|
||||
|
||||
667
src/views/weightManage/LabelPrint.vue
Normal file
667
src/views/weightManage/LabelPrint.vue
Normal file
@@ -0,0 +1,667 @@
|
||||
<template>
|
||||
<div class="label-print-container">
|
||||
|
||||
<div class="form-section">
|
||||
<a-form :model="formData" layout="vertical">
|
||||
<div class="form-grid">
|
||||
<div class="form-grid-item">
|
||||
<a-form-item label="物料名称">
|
||||
<a-input v-model="formData.materialName" placeholder="未获取到物料名称" :disabled="true" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div class="form-grid-item">
|
||||
<a-form-item label="物料编码">
|
||||
<a-input v-model="formData.encoding" placeholder="未获取到物料编码" :disabled="true" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div class="form-grid-item">
|
||||
<a-form-item label="工单编号">
|
||||
<a-input v-model="formData.orderNo" placeholder="未获取到工单编号" :disabled="true" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div class="form-grid-item">
|
||||
<a-form-item label="生产批次">
|
||||
<a-input v-model="formData.batch" placeholder="未获取到生产批次" :disabled="true" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</a-form>
|
||||
<div class="form-actions">
|
||||
<a-button @click="handlePrevious">继续称重</a-button>
|
||||
<a-button type="primary" @click="generateDetailLabel">明细标签</a-button>
|
||||
<a-button type="primary" @click="generateOverallLabel">整体标签</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="labelDataList.length > 0 || labelData.partName" class="label-preview-section">
|
||||
<h3>标签预览</h3>
|
||||
<div class="label-container" ref="labelContainer">
|
||||
<template v-if="labelDataList.length > 0">
|
||||
<div class="label" v-for="(item, index) in labelDataList" :key="index">
|
||||
<table class="label-table">
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">零件名称</div>
|
||||
<div class="label-value">{{ item.partName }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">生产日期</div>
|
||||
<div class="label-value">{{ item.productionDate }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell qr-cell" rowspan="4">
|
||||
<div class="qr-code">
|
||||
<img v-if="item.qrCodeImage" :src="item.qrCodeImage" alt="QR Code" />
|
||||
<div class="mark-number">{{ item.mark || '' }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">零件号</div>
|
||||
<div class="label-value">{{ item.partNumber }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">数量</div>
|
||||
<div class="label-value">{{ item.totalCount }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">标重(g)</div>
|
||||
<div class="label-value">{{ item.totalCalculatedWeight }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">包装签字</div>
|
||||
<div class="label-value">{{ item.packingSignature || '' }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">实重(g)</div>
|
||||
<div class="label-value">{{ item.totalWeight || '' }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">检验签字</div>
|
||||
<div class="label-value">{{ item.inspectionSignature || '' }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="label">
|
||||
<table class="label-table">
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">零件名称</div>
|
||||
<div class="label-value">{{ labelData.partName }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">生产日期</div>
|
||||
<div class="label-value">{{ labelData.productionDate }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell qr-cell" rowspan="4">
|
||||
<div class="qr-code">
|
||||
<img v-if="labelData.qrCodeImage" :src="labelData.qrCodeImage" alt="QR Code" />
|
||||
<div class="mark-number">{{ labelData.mark || '' }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">零件号</div>
|
||||
<div class="label-value">{{ labelData.partNumber }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">数量</div>
|
||||
<div class="label-value">{{ labelData.totalCount }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">标重(g)</div>
|
||||
<div class="label-value">{{ labelData.totalCalculatedWeight }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">包装签字</div>
|
||||
<div class="label-value">{{ labelData.packingSignature || '' }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">实重(g)</div>
|
||||
<div class="label-value">{{ labelData.totalWeight || '' }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">检验签字</div>
|
||||
<div class="label-value">{{ labelData.inspectionSignature || '' }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="label-actions">
|
||||
<a-button type="primary" @click="printLabel">打印标签</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, nextTick, onMounted, watch } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { getWorkOrder, type WorkOrderInfoResp } from "@/apis/workOrder/workOrder"
|
||||
import QRCode from 'qrcode';
|
||||
|
||||
const emit = defineEmits<{
|
||||
previous: []
|
||||
}>()
|
||||
|
||||
interface LabelPrintProps {
|
||||
workerOrderId?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<LabelPrintProps>(), {
|
||||
workerOrderId: ''
|
||||
})
|
||||
|
||||
const formData = reactive({
|
||||
workerOrderId: '',
|
||||
encoding: '',
|
||||
materialName: '',
|
||||
orderNo: '',
|
||||
totalCalculatedWeight: '',
|
||||
totalWeight: '',
|
||||
totalCount: '',
|
||||
batch: '',
|
||||
mark: '',
|
||||
qrCodeData: '',
|
||||
workOrderInfos: Array<WorkOrderInfoResp>(),
|
||||
})
|
||||
|
||||
const labelDataList = reactive<Array<{
|
||||
partName: string
|
||||
partNumber: string
|
||||
totalCalculatedWeight: string
|
||||
totalWeight: string
|
||||
productionDate: string
|
||||
totalCount: string
|
||||
packingSignature: string
|
||||
inspectionSignature: string
|
||||
qrCodeData: string
|
||||
qrCodeImage: string
|
||||
mark: string
|
||||
}>>([])
|
||||
|
||||
const labelData = reactive({
|
||||
partName: '',
|
||||
partNumber: '',
|
||||
totalCalculatedWeight: '',
|
||||
totalWeight: '',
|
||||
productionDate: '',
|
||||
totalCount: '',
|
||||
packingSignature: '',
|
||||
inspectionSignature: '',
|
||||
qrCodeData: '',
|
||||
qrCodeImage: '',
|
||||
mark: '',
|
||||
})
|
||||
|
||||
const labelContainer = ref<HTMLElement | null>(null)
|
||||
|
||||
const handlePrevious = () => {
|
||||
emit('previous')
|
||||
}
|
||||
|
||||
const fetchWorkOrderData = async (workerOrderId: string) => {
|
||||
if (!workerOrderId) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await getWorkOrder(workerOrderId)
|
||||
if (res.code == '0') {
|
||||
formData.encoding = res.data.encoding
|
||||
formData.materialName = res.data.materialName
|
||||
formData.orderNo = res.data.orderNo
|
||||
formData.batch = res.data.batch
|
||||
formData.totalCalculatedWeight = res.data.totalCalculatedWeight
|
||||
formData.totalWeight = res.data.totalWeight
|
||||
formData.totalCount = res.data.totalCount
|
||||
formData.workOrderInfos = res.data.workOrderInfos
|
||||
formData.mark = res.data.mark
|
||||
} else {
|
||||
Message.error('获取工单详情失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取工单详情失败:', error)
|
||||
Message.error('获取工单详情失败')
|
||||
}
|
||||
}
|
||||
|
||||
const generateQRCode = async (data: string) => {
|
||||
try {
|
||||
return await QRCode.toDataURL(data, {
|
||||
width: 120,
|
||||
margin: 1,
|
||||
color: {
|
||||
dark: '#000000',
|
||||
light: '#FFFFFF'
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('生成二维码失败:', error)
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
const generateDetailLabel = async () => {
|
||||
if (!formData.workOrderInfos || formData.workOrderInfos.length === 0) {
|
||||
Message.error('未获取到工单明细信息')
|
||||
return
|
||||
}
|
||||
|
||||
if (!formData.batch) {
|
||||
Message.error('未获取到批次信息')
|
||||
return
|
||||
}
|
||||
if (!formData.materialName) {
|
||||
Message.error('未获取到物料信息')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
labelDataList.length = 0
|
||||
|
||||
const now = new Date()
|
||||
const formattedDate = now.getFullYear().toString() +
|
||||
String(now.getMonth() + 1).padStart(2, '0') +
|
||||
String(now.getDate()).padStart(2, '0') +
|
||||
String(now.getHours()).padStart(2, '0') +
|
||||
String(now.getMinutes()).padStart(2, '0')
|
||||
|
||||
const formattedDate2 = now.getFullYear().toString() +
|
||||
String(now.getMonth() + 1).padStart(2, '0') +
|
||||
String(now.getDate()).padStart(2, '0')
|
||||
|
||||
for (const workOrderInfo of formData.workOrderInfos) {
|
||||
const orderNo = formData.orderNo + workOrderInfo.id;
|
||||
const qrCodeData = `10#${formData.encoding}$11#9DP$12#${formData.batch}$17#${workOrderInfo.quantity}$20#${formattedDate2}$31#${orderNo}$DY`
|
||||
|
||||
const qrCodeImage = await generateQRCode(qrCodeData)
|
||||
console.log("========", workOrderInfo.mark);
|
||||
|
||||
labelDataList.push({
|
||||
partName: formData.materialName || '',
|
||||
partNumber: formData.encoding || '',
|
||||
totalCalculatedWeight: workOrderInfo.calculatedWeight || '',
|
||||
totalWeight: workOrderInfo.weight || '',
|
||||
productionDate: formattedDate,
|
||||
totalCount: workOrderInfo.quantity || '',
|
||||
packingSignature: '',
|
||||
inspectionSignature: '',
|
||||
qrCodeData: qrCodeData,
|
||||
qrCodeImage: qrCodeImage,
|
||||
mark: workOrderInfo.mark || ''
|
||||
})
|
||||
}
|
||||
|
||||
Message.success(`成功生成 ${labelDataList.length} 个明细标签`)
|
||||
} catch (error) {
|
||||
console.error('生成明细标签失败:', error)
|
||||
Message.error('生成明细标签失败')
|
||||
}
|
||||
}
|
||||
|
||||
const generateOverallLabel = async () => {
|
||||
if (!formData.batch) {
|
||||
Message.error('未获取到批次信息')
|
||||
return
|
||||
}
|
||||
if (!formData.materialName) {
|
||||
Message.error('未获取到物料信息')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
labelDataList.length = 0
|
||||
|
||||
const now = new Date()
|
||||
const formattedDate = now.getFullYear().toString() +
|
||||
String(now.getMonth() + 1).padStart(2, '0') +
|
||||
String(now.getDate()).padStart(2, '0') +
|
||||
String(now.getHours()).padStart(2, '0') +
|
||||
String(now.getMinutes()).padStart(2, '0')
|
||||
|
||||
const formattedDate2 = now.getFullYear().toString() +
|
||||
String(now.getMonth() + 1).padStart(2, '0') +
|
||||
String(now.getDate()).padStart(2, '0')
|
||||
|
||||
const qrCodeData = `10#${formData.encoding}$11#9DP$12#${formData.batch}$17#${formData.totalCount}$20#${formattedDate2}$31#${formData.orderNo}$DY`
|
||||
|
||||
const qrCodeImage = await generateQRCode(qrCodeData)
|
||||
|
||||
Object.assign(labelData, {
|
||||
partName: formData.materialName || '',
|
||||
partNumber: formData.encoding || '',
|
||||
totalCalculatedWeight: formData.totalCalculatedWeight || '',
|
||||
totalWeight: formData.totalWeight || '',
|
||||
productionDate: formattedDate,
|
||||
totalCount: formData.totalCount || '',
|
||||
packingSignature: '',
|
||||
inspectionSignature: '',
|
||||
qrCodeData: qrCodeData,
|
||||
qrCodeImage: qrCodeImage,
|
||||
mark: formData.mark || ''
|
||||
})
|
||||
|
||||
Message.success('标签生成成功')
|
||||
} catch (error) {
|
||||
console.error('生成标签失败:', error)
|
||||
Message.error('生成标签失败')
|
||||
}
|
||||
}
|
||||
|
||||
const printLabel = async () => {
|
||||
await nextTick()
|
||||
if (labelContainer.value) {
|
||||
const printWindow = window.open('', '_blank')
|
||||
if (printWindow) {
|
||||
const isDetailLabels = labelDataList.length > 0
|
||||
const labelsToPrint = isDetailLabels ? labelDataList : [labelData]
|
||||
|
||||
const labelsHTML = labelsToPrint.map(item => `
|
||||
<div class="label">
|
||||
<table class="label-table">
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">零件名称</div>
|
||||
<div class="label-value">${item.partName}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">生产日期</div>
|
||||
<div class="label-value">${item.productionDate}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell qr-cell" rowspan="4">
|
||||
<div class="qr-code">
|
||||
<img src="${item.qrCodeImage}" alt="QR Code" />
|
||||
<div class="mark-number">${item.mark || ''}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">零件号</div>
|
||||
<div class="label-value">${item.partNumber}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">数量</div>
|
||||
<div class="label-value">${item.totalCount}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">标重(g)</div>
|
||||
<div class="label-value">${item.totalCalculatedWeight}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">包装签字</div>
|
||||
<div class="label-value">${item.packingSignature || ''}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">实重(g)</div>
|
||||
<div class="label-value">${item.totalWeight || ''}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label-cell">
|
||||
<div class="label-row">
|
||||
<div class="label-field">检验签字</div>
|
||||
<div class="label-value">${item.inspectionSignature || ''}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
`).join('')
|
||||
|
||||
const printHTML = `
|
||||
<html>
|
||||
<head>
|
||||
<title>标签打印</title>
|
||||
<style>
|
||||
@page { size: 80mm 50mm; margin: 0; }
|
||||
body { margin: 0; padding: 1mm; font-family: Arial, sans-serif; }
|
||||
.label-container { display: flex; flex-wrap: wrap; gap: 1mm; justify-content: center; align-items: center; transform-origin: center; }
|
||||
.label { width: 75mm; height: 45mm; border: 1px solid #000; padding: 0; box-sizing: border-box; page-break-inside: avoid; overflow: hidden; margin: 0 auto; }
|
||||
.label-table { width: 100%; height: 100%; border-collapse: collapse; table-layout: fixed; }
|
||||
.label-cell { border: 1px solid #000; padding: 0.8mm; vertical-align: top; }
|
||||
.qr-cell { width: 22mm; text-align: center; vertical-align: middle; border: 1px solid #000; }
|
||||
.label-row { display: flex; align-items: center; }
|
||||
.label-field { font-size: 7.5pt; font-weight: bold; margin-right: 1mm; min-width: 22pt; white-space: nowrap; }
|
||||
.label-value { font-size: 7.5pt; font-weight: bold; flex: 1; overflow-wrap: break-word; word-break: break-word; }
|
||||
.qr-code img { width: 22mm; height: 22mm; margin: 0.5mm 0; }
|
||||
.mark-number { font-size: 7pt; font-weight: bold; margin-top: 0.5mm; text-align: center; }
|
||||
.serial-number { font-size: 7.5pt; font-weight: bold; margin-top: 0.5mm; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="label-container">
|
||||
${labelsHTML}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
printWindow.document.write(printHTML)
|
||||
printWindow.document.close()
|
||||
|
||||
printWindow.onload = () => {
|
||||
setTimeout(() => {
|
||||
printWindow.focus()
|
||||
printWindow.print()
|
||||
printWindow.close()
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.workerOrderId) {
|
||||
fetchWorkOrderData(props.workerOrderId)
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.workerOrderId, (newWorkerOrderId) => {
|
||||
if (newWorkerOrderId) {
|
||||
fetchWorkOrderData(newWorkerOrderId)
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
generateDetailLabel,
|
||||
generateOverallLabel
|
||||
})
|
||||
|
||||
defineOptions({ name: 'LabelPrint' })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.label-print-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background-color: var(--color-bg-2);
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-grid-item {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mark-number {
|
||||
font-size: 10px;
|
||||
margin-top: 5px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.label-preview-section {
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
background-color: var(--color-bg-2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.label-preview-section h3 {
|
||||
margin: 0 0 20px 0;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.label-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 450px;
|
||||
height: 180px;
|
||||
border: 1px solid #000;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.label-table {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.label-cell {
|
||||
border: 1px solid #000;
|
||||
padding: 5px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.qr-cell {
|
||||
width: 120px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.label-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.label-field {
|
||||
font-size: 10pt;
|
||||
font-weight: bold;
|
||||
margin-right: 8px;
|
||||
min-width: 35px;
|
||||
}
|
||||
|
||||
.label-value {
|
||||
font-size: 10pt;
|
||||
flex: 1;
|
||||
font-weight: bold;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.qr-code img {
|
||||
width: 115px;
|
||||
height: 120px;
|
||||
margin: 0 0;
|
||||
}
|
||||
|
||||
.loading {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.label-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
||||
:data="dataList"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1600 }"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1800 }"
|
||||
:pagination="pagination"
|
||||
:disabled-tools="['size']"
|
||||
:disabled-column-keys="['name']"
|
||||
@@ -20,6 +20,7 @@
|
||||
<a-input-search v-model="queryForm.carNo" placeholder="请输入人员卡号" allow-clear @search="search" />
|
||||
<a-input-search v-model="queryForm.orderNo" placeholder="请输入任务工单号" allow-clear @search="search" />
|
||||
<a-input-search v-model="queryForm.materialName" placeholder="请输入物料名称" allow-clear @search="search" />
|
||||
<a-input-search v-model="queryForm.batch" placeholder="请输入批次" allow-clear @search="search" />
|
||||
<a-input-search v-model="queryForm.encoding" placeholder="请输入物料编码" allow-clear @search="search" />
|
||||
<a-date-picker
|
||||
v-model="queryForm.startDate"
|
||||
@@ -44,11 +45,11 @@
|
||||
</a-button>
|
||||
</template>
|
||||
<template #toolbar-right>
|
||||
<a-button v-permission="['workOrder:record:delete']" type="outline" status="danger" @click="onDelete">
|
||||
<a-button v-permission="['weighManage:workOrder:delete']" type="outline" status="danger" @click="onDelete">
|
||||
<template #icon><icon-delete /></template>
|
||||
<template #default>删除</template>
|
||||
</a-button>
|
||||
<a-button v-permission="['workOrder:record:export']" @click="onExport">
|
||||
<a-button v-permission="['weighManage:workOrder:export']" @click="onExport">
|
||||
<template #icon><icon-download /></template>
|
||||
<template #default>导出</template>
|
||||
</a-button>
|
||||
@@ -63,12 +64,15 @@
|
||||
<template #totalWeight="{ record }">
|
||||
{{ record.totalWeight + 'g' }}
|
||||
</template>
|
||||
<template #totalCalculatedWeight="{ record }">
|
||||
{{ record.totalCalculatedWeight + 'g' }}
|
||||
</template>
|
||||
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-link v-permission="['workOrder:record:detail']" title="详情" @click="onDetail(record)">详情</a-link>
|
||||
<a-link v-permission="['workOrder:record:print']" title="打印" @click="onPrint(record)">打印</a-link>
|
||||
<a-link v-permission="['workOrder:record:delete']" status="danger" :title="'删除'" @click="onDeleteOne(record.id)">删除</a-link>
|
||||
<a-link v-permission="['weighManage:workOrder:delete']" status="danger" :title="'删除'" @click="onDeleteOne(record.id)">删除</a-link>
|
||||
</a-space>
|
||||
</template>
|
||||
</GiTable>
|
||||
@@ -85,7 +89,7 @@
|
||||
row-key="id"
|
||||
:data="detailData"
|
||||
:columns="detailColumns"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 700 }"
|
||||
:scroll="{ x: '100%', y: '100%', width: 800 }"
|
||||
>
|
||||
<template #imgUrl="{ record }">
|
||||
<a-image :src="record.imgUrl" width="50" />
|
||||
@@ -117,7 +121,7 @@ import {Message} from "@arco-design/web-vue";
|
||||
import { useRouter } from 'vue-router';
|
||||
import {
|
||||
deleteWorkOrder,
|
||||
exportWorkOrder, getWorkOrder,
|
||||
exportWorkOrder, getWorkOrderInfos,
|
||||
listWorkOrder,
|
||||
type WorkOrderQuery,
|
||||
type WorkOrderResp
|
||||
@@ -134,6 +138,7 @@ const queryForm = reactive<WorkOrderQuery>({
|
||||
encoding: undefined,
|
||||
userName: undefined,
|
||||
carNo: undefined,
|
||||
batch: undefined,
|
||||
startDate: undefined,
|
||||
endDate: undefined,
|
||||
sort: ['w.id,desc']
|
||||
@@ -164,15 +169,17 @@ const processColumns = (columns: TableInstanceColumns[]): TableInstanceColumns[]
|
||||
// 定义列配置,使用工具函数处理
|
||||
const columns = ref<TableInstanceColumns[]>(processColumns([
|
||||
{ title: '标题', dataIndex: 'title', width: 180 },
|
||||
{ title: '创建人', dataIndex: 'createUserString' },
|
||||
{ title: '人员卡号', dataIndex: 'cardNo' },
|
||||
{ title: '任务工单号', dataIndex: 'orderNo' },
|
||||
{ title: '批次', dataIndex: 'batch' },
|
||||
{ title: '物料图片', dataIndex: 'photoUrl', slotName: 'photoUrl', minWidth: 180, ellipsis: true, tooltip: true },
|
||||
{ title: '物料名称', dataIndex: 'materialName' },
|
||||
{ title: '物料编码', dataIndex: 'encoding' },
|
||||
{ title: '单位克重', dataIndex: 'unitWeight' ,slotName: 'unitWeight'},
|
||||
{ title: '总重量', dataIndex: 'totalWeight' ,slotName: 'totalWeight'},
|
||||
{ title: '称重次数', dataIndex: 'totalCount' },
|
||||
{ title: '总数量', dataIndex: 'totalCount' },
|
||||
{ title: '标准总重量', dataIndex: 'totalCalculatedWeight' ,slotName: 'totalCalculatedWeight'},
|
||||
{ title: '实际总重量', dataIndex: 'totalWeight' ,slotName: 'totalWeight'},
|
||||
{ title: '人员卡号', dataIndex: 'cardNo' },
|
||||
{ title: '创建人', dataIndex: 'createUserString' },
|
||||
{ title: '创建时间', dataIndex: 'createTime', width: 180 },
|
||||
{
|
||||
title: '操作',
|
||||
@@ -181,7 +188,7 @@ const columns = ref<TableInstanceColumns[]>(processColumns([
|
||||
width: 160,
|
||||
align: 'center',
|
||||
fixed: !isMobile() ? 'right' : undefined,
|
||||
show: has.hasPermOr(['workOrder:record:detail', 'workOrder:record:update', 'workOrder:record:delete'])
|
||||
show: has.hasPermOr(['workOrder:record:detail', 'workOrder:record:update', 'weighManage:workOrder:delete'])
|
||||
}
|
||||
]))
|
||||
|
||||
@@ -206,9 +213,10 @@ const detailData = ref<any[]>([])
|
||||
const detailColumns = ref<TableInstanceColumns[]>([
|
||||
{ title: '称重次数', dataIndex: 'weightTime' },
|
||||
{ title: '物料名称', dataIndex: 'materialName' },
|
||||
{ title: '数量', dataIndex: 'quantity' },
|
||||
{ title: '重量', dataIndex: 'weight', slotName: 'weight' },
|
||||
{ title: '计算重量', dataIndex: 'calculatedWeight', slotName: 'calculatedWeight' },
|
||||
{ title: '输入数量', dataIndex: 'quantity' },
|
||||
{ title: '标准重量(g)', dataIndex: 'calculatedWeight', slotName: 'calculatedWeight' },
|
||||
{ title: '称重数量', dataIndex: 'weightQuantity' },
|
||||
{ title: '称重重量(g)', dataIndex: 'weight', slotName: 'weight' },
|
||||
{ title: '抓拍图片', dataIndex: 'imgUrl', slotName: 'imgUrl' }
|
||||
])
|
||||
|
||||
@@ -218,7 +226,7 @@ const onDetail = async (record: WorkOrderResp) => {
|
||||
detailModalVisible.value = true
|
||||
detailData.value = []
|
||||
|
||||
getWorkOrder(record.id).then(res => {
|
||||
getWorkOrderInfos(record.id).then(res => {
|
||||
if (res.code == '0') {
|
||||
detailData.value = res.data;
|
||||
detailLoading.value = false
|
||||
@@ -258,9 +266,7 @@ const onPrint = (record: WorkOrderResp) => {
|
||||
router.push({
|
||||
path: '/print',
|
||||
query: {
|
||||
materialName: record.materialName,
|
||||
encoding: record.encoding,
|
||||
orderNo: record.orderNo
|
||||
workerOrderId: record.id,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user