Compare commits

..

No commits in common. 'main' and 'liutao_branch' have entirely different histories.

@ -8,9 +8,8 @@ VITE_DEV=true
# 线上环境
# VITE_BASE_URL='https://besure.ngsk.tech:7001'
# 本地联调
VITE_BASE_URL='http://192.168.5.106:48081'
# VITE_BASE_URL='http://192.168.5.164:48081'
# VITE_BASE_URL='http://192.168.5.5:48081'
# VITE_BASE_URL='http://192.168.5.107:48081'
VITE_BASE_URL='http://localhost:48081'
# 文件上传类型server - 后端上传, client - 前端直连上传,仅支持 S3 服务

4
.gitignore vendored

@ -9,7 +9,3 @@ auto-*.d.ts
.history
pnpm-lock.yaml
/.vscode
.env.cklocal
.vscode/*
.vscode/settings.json
launch.json

@ -0,0 +1,144 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"npm.packageManager": "pnpm",
"editor.tabSize": 2,
"prettier.printWidth": 100, //
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.eol": "\n",
"search.exclude": {
"**/node_modules": true,
"**/*.log": true,
"**/*.log*": true,
"**/bower_components": true,
"**/dist": true,
"**/elehukouben": true,
"**/.git": true,
"**/.gitignore": true,
"**/.svn": true,
"**/.DS_Store": true,
"**/.idea": true,
"**/.vscode": false,
"**/yarn.lock": true,
"**/tmp": true,
"out": true,
"dist": true,
"node_modules": true,
"CHANGELOG.md": true,
"examples": true,
"res": true,
"screenshots": true,
"yarn-error.log": true,
"**/.yarn": true
},
"files.exclude": {
"**/.cache": true,
"**/.editorconfig": true,
"**/.eslintcache": true,
"**/bower_components": true,
"**/.idea": true,
"**/tmp": true,
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true
},
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/.vscode/**": true,
"**/node_modules/**": true,
"**/tmp/**": true,
"**/bower_components/**": true,
"**/dist/**": true,
"**/yarn.lock": true
},
"stylelint.enable": true,
"stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"],
"path-intellisense.mappings": {
"@/": "${workspaceRoot}/src"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
},
"[typescriptreact]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
},
"[less]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[vue]": {
"editor.defaultFormatter": "Vue.volar"
},
"i18n-ally.localesPaths": ["src/locales"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": false,
"i18n-ally.enabledParsers": ["ts"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"],
"cSpell.words": [
"brotli",
"browserslist",
"codemirror",
"commitlint",
"cropperjs",
"echart",
"echarts",
"esnext",
"esno",
"iconify",
"INTLIFY",
"lintstagedrc",
"logicflow",
"nprogress",
"pinia",
"pnpm",
"qrcode",
"sider",
"sortablejs",
"stylelint",
"svgs",
"unocss",
"unplugin",
"unref",
"videojs",
"VITE",
"vitejs",
"vueuse",
"wangeditor",
"xingyu",
"yudao",
"zxcvbn"
],
//
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.expand": false,
"explorer.fileNesting.patterns": {
"*.ts": "$(capture).test.ts, $(capture).test.tsx",
"*.tsx": "$(capture).test.ts, $(capture).test.tsx",
"*.env": "$(capture).env.*",
"package.json": "pnpm-lock.yaml,yarn.lock,LICENSE,README*,CHANGELOG*,CNAME,.gitattributes,.eslintrc-auto-import.json,.gitignore,prettier.config.js,stylelint.config.js,commitlint.config.js,.stylelintignore,.prettierignore,.gitpod.yml,.eslintrc.js,.eslintignore"
},
"terminal.integrated.scrollback": 10000,
"nuxt.isNuxtApp": false
}

@ -10,11 +10,11 @@
"pro": "vite --mode env.prod",
"dev-server": "vite --mode dev",
"ts:check": "vue-tsc --noEmit",
"build:local": "node --max_old_space_size=6144 ./node_modules/vite/bin/vite.js build",
"build:dev": "node --max_old_space_size=6144 ./node_modules/vite/bin/vite.js build --mode dev",
"build:test": "node --max_old_space_size=6144 ./node_modules/vite/bin/vite.js build --mode test",
"build:stage": "node --max_old_space_size=6144 ./node_modules/vite/bin/vite.js build --mode stage",
"build:prod": "node --max_old_space_size=6144 ./node_modules/vite/bin/vite.js build --mode prod",
"build:local": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build",
"build:dev": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode dev",
"build:test": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode test",
"build:stage": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode stage",
"build:prod": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode prod",
"serve:dev": "vite preview --mode dev",
"serve:prod": "vite preview --mode prod",
"preview": "pnpm build:local && vite preview",
@ -44,7 +44,6 @@
"cropperjs": "^1.6.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.10",
"dhtmlx-gantt": "^9.1.3",
"diagram-js": "^12.8.0",
"driver.js": "^1.3.1",
"echarts": "^5.5.0",
@ -73,7 +72,6 @@
"vue": "3.5.12",
"vue-dompurify-html": "^4.1.4",
"vue-i18n": "9.10.2",
"vue-plugin-hiprint": "^0.0.60",
"vue-router": "4.4.5",
"vue-types": "^5.1.1",
"vuedraggable": "^4.1.0",

@ -62,9 +62,6 @@ importers:
dayjs:
specifier: ^1.11.10
version: 1.11.19
dhtmlx-gantt:
specifier: ^9.1.3
version: 9.1.3
diagram-js:
specifier: ^12.8.0
version: 12.8.1
@ -149,9 +146,6 @@ importers:
vue-i18n:
specifier: 9.10.2
version: 9.10.2(vue@3.5.12(typescript@5.3.3))
vue-plugin-hiprint:
specifier: ^0.0.60
version: 0.0.60
vue-router:
specifier: 4.4.5
version: 4.4.5(vue@3.5.12(typescript@5.3.3))
@ -917,11 +911,6 @@ packages:
'@carbon/icons@11.71.0':
resolution: {integrity: sha512-PwOJ7C2MNQbHvaOsPhiKHNikJ9flGtyVeGIsOHIkyNndcxAUOleppHcGofOo4NvCeIUZwnr+EN8fMaU+ZdsH9w==}
'@claviska/jquery-minicolors@2.3.6':
resolution: {integrity: sha512-8Ro6D4GCrmOl41+6w4NFhEOpx8vjxwVRI69bulXsFDt49uVRKhLU5TnzEV7AmOJrylkVq+ugnYNMiGHBieeKUQ==}
peerDependencies:
jquery: '>= 1.7.x'
'@codemirror/autocomplete@6.20.0':
resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==}
@ -1669,9 +1658,6 @@ packages:
'@sinclair/typebox@0.27.8':
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
'@socket.io/component-emitter@3.1.2':
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
'@swc/core-darwin-arm64@1.15.7':
resolution: {integrity: sha512-+hNVUfezUid7LeSHqnhoC6Gh3BROABxjlDNInuZ/fie1RUxaEX4qzDwdTgozJELgHhvYxyPIg1ro8ibnKtgO4g==}
engines: {node: '>=10'}
@ -1896,9 +1882,6 @@ packages:
'@types/qs@6.14.0':
resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==}
'@types/raf@3.4.3':
resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==}
'@types/semver@7.7.1':
resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==}
@ -2359,10 +2342,6 @@ packages:
slate: ^0.72.0
snabbdom: ^3.1.0
'@wtto00/html2canvas@1.4.3':
resolution: {integrity: sha512-jwsb+xL8N+gjrSNABSaFdxmWtE4c7RNFjP20lo1G7gs63Qqo1phhxVBTzxc/apDVh6LgXsU2l5bwKtXd9uz65w==}
engines: {node: '>=8.0.0'}
'@xmldom/xmldom@0.8.11':
resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==}
engines: {node: '>=10.0.0'}
@ -2544,10 +2523,6 @@ packages:
balanced-match@2.0.0:
resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==}
base64-arraybuffer@1.0.2:
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
engines: {node: '>= 0.6.0'}
base@0.11.2:
resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==}
engines: {node: '>=0.10.0'}
@ -2618,18 +2593,9 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
btoa@1.2.1:
resolution: {integrity: sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==}
engines: {node: '>= 0.4.0'}
hasBin: true
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
bwip-js@4.8.0:
resolution: {integrity: sha512-gUDkDHSTv8/DJhomSIbO0fX/Dx0MO/sgllLxJyJfu4WixCQe9nfGJzmHm64ZCbxo+gUYQEsQcRmqcwcwPRwUkg==}
hasBin: true
cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
@ -2674,10 +2640,6 @@ packages:
caniuse-lite@1.0.30001761:
resolution: {integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==}
canvg@3.0.11:
resolution: {integrity: sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==}
engines: {node: '>=10.0.0'}
chalk@1.1.3:
resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==}
engines: {node: '>=0.10.0'}
@ -2905,9 +2867,6 @@ packages:
resolution: {integrity: sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==}
engines: {node: '>=12 || >=16'}
css-line-break@2.1.0:
resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==}
css-select@4.3.0:
resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
@ -3167,9 +3126,6 @@ packages:
engines: {node: '>=0.10'}
hasBin: true
dhtmlx-gantt@9.1.3:
resolution: {integrity: sha512-3FjvcsGVLFgpZgQKjzKnw1vZh2dIKwLt1Zm6G9Oo/bW2Ogm6/PeQ2cNHO033MWicuxJZn2mpLmg6yssUK3RAiQ==}
diagram-js-direct-editing@3.2.0:
resolution: {integrity: sha512-+pyxeQGBSdLiZX0/tmmsm2qZSvm9YtVzod5W3RMHSTR7VrkUMD6E7EX/W9JQv3ebxO7oIdqFmytmNDDpSHnYEw==}
peerDependencies:
@ -3241,9 +3197,6 @@ packages:
resolution: {integrity: sha512-rmvrrmWQPD/X1A/nPBfIVg4r05792QdG9Z4Prk6oQG0F9zBMDkr0GKAdds1wjb2dq1rTz/ywc4ZxpZbgz0tttg==}
engines: {node: '>=18'}
dompurify@2.5.9:
resolution: {integrity: sha512-i6mvVmWN4xo9LrhCOZrDgSs9noW6nOahbrmzjRbPF36YPyj5Ue5lgok0MHDWkG7xzpWFO2OYttXdzM7rJxHvNA==}
dompurify@3.3.1:
resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==}
@ -3317,13 +3270,6 @@ packages:
resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
engines: {node: '>= 4'}
engine.io-client@6.6.4:
resolution: {integrity: sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==}
engine.io-parser@5.2.3:
resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
engines: {node: '>=10.0.0'}
entities@1.1.2:
resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==}
@ -3572,9 +3518,6 @@ packages:
feelin@3.2.0:
resolution: {integrity: sha512-GFDbHsTYk7YXO1tyw1dOjb7IODeAZvNIosdGZThUwPx5XcD/XhO0hnPZXsIbAzSsIdrgGlTEEdby9fZ2gixysA==}
fflate@0.8.2:
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
file-entry-cache@11.1.1:
resolution: {integrity: sha512-TPVFSDE7q91Dlk1xpFLvFllf8r0HyOMOlnWy7Z2HBku5H3KhIeOGInexrIeg2D64DosVB/JXkrrk6N/7Wriq4A==}
@ -3860,10 +3803,6 @@ packages:
html-void-elements@2.0.1:
resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==}
html2canvas@1.4.1:
resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
engines: {node: '>=8.0.0'}
htmlparser2@3.10.1:
resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==}
@ -4170,9 +4109,6 @@ packages:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
jquery@3.7.1:
resolution: {integrity: sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==}
js-base64@2.6.4:
resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==}
@ -4195,9 +4131,6 @@ packages:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
jsbarcode@3.12.3:
resolution: {integrity: sha512-CuHU9hC6dPsHF5oVFMo8NW76uQVjH4L22CsP4hW+dNnGywJHC/B0ThA1CTDVLnxKLrrpYdicBLnd2xsgTfRnvg==}
jsencrypt@3.5.4:
resolution: {integrity: sha512-kNjfYEMNASxrDGsmcSQh/rUTmcoRfSUkxnAz+MMywM8jtGu+fFEZ3nJjHM58zscVnwR0fYmG9sGkTDjqUdpiwA==}
@ -4241,9 +4174,6 @@ packages:
resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
engines: {'0': node >= 0.2.0}
jspdf@2.5.2:
resolution: {integrity: sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==}
katex@0.16.27:
resolution: {integrity: sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==}
hasBin: true
@ -4673,9 +4603,6 @@ packages:
nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
nzh@1.0.14:
resolution: {integrity: sha512-wKgaqCSZdrySvB4RWop5g+v6IDv2IErsT6rjq06Bg0yiT9hiHYZO12GMGx/xweGVLcO2lDjX5RqWD0S/Jy9z5Q==}
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
@ -4837,9 +4764,6 @@ packages:
perfect-debounce@1.0.0:
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
performance-now@2.1.0:
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@ -5047,9 +4971,6 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
raf@3.4.1:
resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==}
rd@2.0.1:
resolution: {integrity: sha512-/XdKU4UazUZTXFmI0dpABt8jSXPWcEyaGdk340KdHnsEOdkTctlX23aAK7ChQDn39YGNlAJr1M5uvaKt4QnpNw==}
@ -5079,9 +5000,6 @@ packages:
regenerate@1.4.2:
resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==}
regenerator-runtime@0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
regenerator-runtime@0.14.1:
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
@ -5166,10 +5084,6 @@ packages:
rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
rgbcolor@1.0.1:
resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==}
engines: {node: '>= 0.8.15'}
rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
deprecated: Rimraf versions prior to v4 are no longer supported
@ -5349,14 +5263,6 @@ packages:
resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==}
engines: {node: '>=0.10.0'}
socket.io-client@4.8.3:
resolution: {integrity: sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==}
engines: {node: '>=10.0.0'}
socket.io-parser@4.2.6:
resolution: {integrity: sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==}
engines: {node: '>=10.0.0'}
sortablejs@1.14.0:
resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==}
@ -5404,10 +5310,6 @@ packages:
resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==}
deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility'
stackblur-canvas@2.7.0:
resolution: {integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==}
engines: {node: '>=0.1.14'}
static-extend@0.1.2:
resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==}
engines: {node: '>=0.10.0'}
@ -5536,10 +5438,6 @@ packages:
svg-baker@1.7.0:
resolution: {integrity: sha512-nibslMbkXOIkqKVrfcncwha45f97fGuAOn1G99YwnwTj8kF9YiM6XexPcUso97NxOm6GsP0SIvYVIosBis1xLg==}
svg-pathdata@6.0.3:
resolution: {integrity: sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==}
engines: {node: '>=12.0.0'}
svg-tags@1.0.0:
resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==}
@ -5578,9 +5476,6 @@ packages:
resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==}
engines: {node: '>=8'}
text-segmentation@1.0.3:
resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
@ -5795,9 +5690,6 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
utrie@1.0.2:
resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==}
uuid@10.0.0:
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
hasBin: true
@ -5909,10 +5801,6 @@ packages:
peerDependencies:
vue: ^3.0.0
vue-plugin-hiprint@0.0.60:
resolution: {integrity: sha512-a5uOMn6Nr4qlYYaVNbQKwRZJa8UcNMTflfi6J430/NDtySJB+5ArE8I8+NLjgVV56x3/qdUBs/GWuZCX5Umv1w==}
engines: {node: '>=16'}
vue-router@4.4.5:
resolution: {integrity: sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==}
peerDependencies:
@ -6025,18 +5913,6 @@ packages:
resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
ws@8.18.3:
resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
xml-js@1.6.11:
resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
hasBin: true
@ -6045,10 +5921,6 @@ packages:
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
engines: {node: '>=12'}
xmlhttprequest-ssl@2.1.2:
resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==}
engines: {node: '>=0.4.0'}
y18n@4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
@ -6885,10 +6757,6 @@ snapshots:
dependencies:
'@ibm/telemetry-js': 1.10.2
'@claviska/jquery-minicolors@2.3.6(jquery@3.7.1)':
dependencies:
jquery: 3.7.1
'@codemirror/autocomplete@6.20.0':
dependencies:
'@codemirror/language': 6.12.1
@ -7591,8 +7459,6 @@ snapshots:
'@sinclair/typebox@0.27.8': {}
'@socket.io/component-emitter@3.1.2': {}
'@swc/core-darwin-arm64@1.15.7':
optional: true
@ -7807,8 +7673,6 @@ snapshots:
'@types/qs@6.14.0': {}
'@types/raf@3.4.3': {}
'@types/semver@7.7.1': {}
'@types/svgo@2.6.4':
@ -8519,11 +8383,6 @@ snapshots:
slate: 0.72.8
snabbdom: 3.6.3
'@wtto00/html2canvas@1.4.3':
dependencies:
css-line-break: 2.1.0
text-segmentation: 1.0.3
'@xmldom/xmldom@0.8.11': {}
'@zxcvbn-ts/core@3.0.4':
@ -8699,8 +8558,6 @@ snapshots:
balanced-match@2.0.0: {}
base64-arraybuffer@1.0.2: {}
base@0.11.2:
dependencies:
cache-base: 1.0.1
@ -8803,12 +8660,8 @@ snapshots:
node-releases: 2.0.27
update-browserslist-db: 1.2.3(browserslist@4.28.1)
btoa@1.2.1: {}
buffer-from@1.1.2: {}
bwip-js@4.8.0: {}
cac@6.7.14: {}
cache-base@1.0.1:
@ -8864,17 +8717,6 @@ snapshots:
caniuse-lite@1.0.30001761: {}
canvg@3.0.11:
dependencies:
'@babel/runtime': 7.28.4
'@types/raf': 3.4.3
core-js: 3.47.0
raf: 3.4.1
regenerator-runtime: 0.13.11
rgbcolor: 1.0.1
stackblur-canvas: 2.7.0
svg-pathdata: 6.0.3
chalk@1.1.3:
dependencies:
ansi-styles: 2.2.1
@ -9110,10 +8952,6 @@ snapshots:
css-functions-list@3.2.3: {}
css-line-break@2.1.0:
dependencies:
utrie: 1.0.2
css-select@4.3.0:
dependencies:
boolbase: 1.0.0
@ -9394,8 +9232,6 @@ snapshots:
detect-libc@1.0.3:
optional: true
dhtmlx-gantt@9.1.3: {}
diagram-js-direct-editing@3.2.0(diagram-js@14.11.3):
dependencies:
diagram-js: 14.11.3
@ -9486,9 +9322,6 @@ snapshots:
domify@2.0.0: {}
dompurify@2.5.9:
optional: true
dompurify@3.3.1:
optionalDependencies:
'@types/trusted-types': 2.0.7
@ -9597,20 +9430,6 @@ snapshots:
emojis-list@3.0.0: {}
engine.io-client@6.6.4:
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.4.3
engine.io-parser: 5.2.3
ws: 8.18.3
xmlhttprequest-ssl: 2.1.2
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
engine.io-parser@5.2.3: {}
entities@1.1.2: {}
entities@2.2.0: {}
@ -9997,8 +9816,6 @@ snapshots:
lezer-feel: 1.9.0
luxon: 3.7.2
fflate@0.8.2: {}
file-entry-cache@11.1.1:
dependencies:
flat-cache: 6.1.19
@ -10295,12 +10112,6 @@ snapshots:
html-void-elements@2.0.1: {}
html2canvas@1.4.1:
dependencies:
css-line-break: 2.1.0
text-segmentation: 1.0.3
optional: true
htmlparser2@3.10.1:
dependencies:
domelementtype: 1.3.1
@ -10579,8 +10390,6 @@ snapshots:
jiti@2.6.1: {}
jquery@3.7.1: {}
js-base64@2.6.4: {}
js-beautify@1.15.4:
@ -10601,8 +10410,6 @@ snapshots:
dependencies:
argparse: 2.0.1
jsbarcode@3.12.3: {}
jsencrypt@3.5.4: {}
jsesc@3.1.0: {}
@ -10638,18 +10445,6 @@ snapshots:
jsonparse@1.3.1: {}
jspdf@2.5.2:
dependencies:
'@babel/runtime': 7.28.4
atob: 2.1.2
btoa: 1.2.1
fflate: 0.8.2
optionalDependencies:
canvg: 3.0.11
core-js: 3.47.0
dompurify: 2.5.9
html2canvas: 1.4.1
katex@0.16.27:
dependencies:
commander: 8.3.0
@ -11101,8 +10896,6 @@ snapshots:
dependencies:
boolbase: 1.0.0
nzh@1.0.14: {}
object-assign@4.1.1: {}
object-copy@0.1.0:
@ -11254,8 +11047,6 @@ snapshots:
perfect-debounce@1.0.0: {}
performance-now@2.1.0: {}
picocolors@1.1.1: {}
picomatch@2.3.1: {}
@ -11450,10 +11241,6 @@ snapshots:
queue-microtask@1.2.3: {}
raf@3.4.1:
dependencies:
performance-now: 2.1.0
rd@2.0.1:
dependencies:
'@types/node': 10.17.60
@ -11489,8 +11276,6 @@ snapshots:
regenerate@1.4.2: {}
regenerator-runtime@0.13.11: {}
regenerator-runtime@0.14.1: {}
regex-not@1.0.2:
@ -11564,8 +11349,6 @@ snapshots:
rfdc@1.4.1: {}
rgbcolor@1.0.1: {}
rimraf@3.0.2:
dependencies:
glob: 7.2.3
@ -11808,24 +11591,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
socket.io-client@4.8.3:
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.4.3
engine.io-client: 6.6.4
socket.io-parser: 4.2.6
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
socket.io-parser@4.2.6:
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.4.3
transitivePeerDependencies:
- supports-color
sortablejs@1.14.0: {}
sortablejs@1.15.6: {}
@ -11863,8 +11628,6 @@ snapshots:
stable@0.1.8: {}
stackblur-canvas@2.7.0: {}
static-extend@0.1.2:
dependencies:
define-property: 0.2.5
@ -12050,8 +11813,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
svg-pathdata@6.0.3: {}
svg-tags@1.0.0: {}
svg.js@2.7.1: {}
@ -12096,10 +11857,6 @@ snapshots:
text-extensions@2.4.0: {}
text-segmentation@1.0.3:
dependencies:
utrie: 1.0.2
text-table@0.2.0: {}
through@2.3.8: {}
@ -12367,10 +12124,6 @@ snapshots:
util-deprecate@1.0.2: {}
utrie@1.0.2:
dependencies:
base64-arraybuffer: 1.0.2
uuid@10.0.0: {}
vary@1.1.2: {}
@ -12504,23 +12257,6 @@ snapshots:
'@vue/devtools-api': 6.6.4
vue: 3.5.12(typescript@5.3.3)
vue-plugin-hiprint@0.0.60:
dependencies:
'@claviska/jquery-minicolors': 2.3.6(jquery@3.7.1)
'@wtto00/html2canvas': 1.4.3
bwip-js: 4.8.0
canvg: 3.0.11
jquery: 3.7.1
jsbarcode: 3.12.3
jspdf: 2.5.2
lodash: 4.17.21
nzh: 1.0.14
socket.io-client: 4.8.3
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
vue-router@4.4.5(vue@3.5.12(typescript@5.3.3)):
dependencies:
'@vue/devtools-api': 6.6.4
@ -12664,16 +12400,12 @@ snapshots:
imurmurhash: 0.1.4
signal-exit: 4.1.0
ws@8.18.3: {}
xml-js@1.6.11:
dependencies:
sax: 1.4.3
xml-name-validator@4.0.0: {}
xmlhttprequest-ssl@2.1.2: {}
y18n@4.0.3: {}
y18n@5.0.8: {}

@ -52,8 +52,6 @@ export interface AutocodeRuleVO {
id: number // ID
ruleCode: string // 规则编码
ruleName: string // 规则名称
barcodeType?: number | string
barCodeType?: number | string
ruleDesc: string // 描述
maxLength: number // 最大长度
isPadded: string // 是否补齐
@ -104,4 +102,4 @@ getTestCode: async (ruleCode: string) => {
getAutocodePartListByRuleId: async (ruleId) => {
return await request.get({ url: `/erp/autocode-rule/autocode-part/list-by-rule-id?ruleId=` + ruleId })
}
}
}

@ -26,7 +26,6 @@ export interface MoldBrandTreeVO extends MoldBrandVO {
export interface MoldVO {
id: number // ID
code: string // 模具编码
isCode?: boolean
name: string // 模具名称
unitId: number // 单位ID
machineId: number // 机台ID
@ -35,7 +34,6 @@ export interface MoldVO {
status: number // 状态
images: string // 模具图片
qrcodeUrl?: string
templateJson?: string | any
fileUrl?: string
remark: string // 备注
isEnable: boolean // 是否启用
@ -127,9 +125,6 @@ export const MoldBrandApi = {
// 获得模具
getMold: async (id: number) => {
return await request.get({ url: `/erp/mold-brand/mold/get?id=` + id })
},
regenerateCode: async (id: number, code: string) => {
return await request.post({ url: `/erp/mold-brand/regenerate-code?id=${id}&code=${encodeURIComponent(code)}` })
},
// 根据状态获得模具
getMoldListByStatus: async (status: number) => {

@ -5,9 +5,6 @@ export interface ProductVO {
id: number // 产品编号
name: string // 产品名称
barCode: string // 产品条码
isCode?: boolean
qrcodeUrl?: string
templateJson?: string | any
categoryId: number // 产品类型编号
subCategoryId: number // 产品类型子类编号
subCategoryName: string // 产品类型子类名称
@ -21,12 +18,6 @@ export interface ProductVO {
purchasePrice: number // 采购价格,单位:元
salePrice: number // 销售价格,单位:元
minPrice: number // 最低价格,单位:元
deviceIds?: string // 关联设备ID列表
moldIds?: string // 关联模具ID列表
devices?: { id: number; name: string }[] // 关联设备列表
molds?: { id: number; name: string }[] // 关联模具列表
deviceList?: any[]
moldList?: any[]
}
// ERP 产品 API
@ -72,13 +63,6 @@ export const ProductApi = {
return await request.put({ url: `/erp/product/update`, data })
},
// 刷新产品二维码
regenerateCode: async (id: number, code: string) => {
return await request.post({
url: `/erp/product/regenerate-code?id=${id}&code=${encodeURIComponent(code)}`
})
},
// 删除产品
deleteProduct: async (id: number) => {
return await request.delete({ url: `/erp/product/delete?id=` + id })

@ -56,9 +56,4 @@ export const ProductUnitApi = {
getProductUnitListByFlag: async () => {
return await request.get({ url: `/erp/product-unit/flag-list`})
},
// 下载用户导入模板
importUserTemplate: async () => {
return await request.download({ url: '/erp/product-unit/get-import-template' })
}
}

@ -12,33 +12,6 @@ export interface WarehouseVO {
truckagePrice: number // 搬运费,单位:元
status: number // 开启状态
defaultStatus: boolean // 是否默认
warehouseId:number
areaList?: Array<{
id: number
warehouseId: number
areaCode: string
areaName: string
areaSize: number
description: string
status: number
createTime: number
}>
locationList?: Array<{
id: number
warehouseId: number
areaId: number
code: string
name: string
areaSize: number
maxLoadWeight: number
positionX: number
positionY: number
positionZ: number
allowProductMix: boolean
allowBatchMix: boolean
status: number
createTime: number
}>
}
// ERP 仓库 API

@ -1,34 +0,0 @@
import request from '@/config/axios'
export interface WarehouseAreaVO {
id: number
warehouseId: number
areaCode: string
areaName: string
areaSize: number
description: string
status: number
createTime: string
}
export const WarehouseAreaApi = {
getWarehouseAreaPage: async (params: any) => {
return await request.get({ url: `/erp/warehouse-area/page`, params })
},
getWarehouseArea: async (id: number) => {
return await request.get({ url: `/erp/warehouse-area/get?id=` + id })
},
createWarehouseArea: async (data: WarehouseAreaVO) => {
return await request.post({ url: `/erp/warehouse-area/create`, data })
},
updateWarehouseArea: async (data: WarehouseAreaVO) => {
return await request.put({ url: `/erp/warehouse-area/update`, data })
},
deleteWarehouseArea: async (id: number) => {
return await request.delete({ url: `/erp/warehouse-area/delete?id=` + id })
}
}

@ -1,40 +0,0 @@
import request from '@/config/axios'
export interface WarehouseLocationVO {
id: number
warehouseId: number
areaId: number
code: string
name: string
areaSize: number
maxLoadWeight: number
positionX: number
positionY: number
positionZ: number
allowProductMix: boolean
allowBatchMix: boolean
status: number
createTime: string
}
export const WarehouseLocationApi = {
getWarehouseLocationPage: async (params: any) => {
return await request.get({ url: `/erp/warehouse-location/page`, params })
},
getWarehouseLocation: async (id: number) => {
return await request.get({ url: `/erp/warehouse-location/get?id=` + id })
},
createWarehouseLocation: async (data: WarehouseLocationVO) => {
return await request.post({ url: `/erp/warehouse-location/create`, data })
},
updateWarehouseLocation: async (data: WarehouseLocationVO) => {
return await request.put({ url: `/erp/warehouse-location/update`, data })
},
deleteWarehouseLocation: async (id: number) => {
return await request.delete({ url: `/erp/warehouse-location/delete?id=` + id })
}
}

@ -27,7 +27,6 @@ export interface DeviceVO {
certificate?: string // 证书
secretKey?: string // 秘钥
collectionTime?: string | number
images: string,//图片
}
export interface DeviceConnectParams {
@ -100,12 +99,6 @@ export const DeviceApi = {
getDeviceList2ByNoUsed: async (id: number) => {
return await request.get({ url: `/iot/device/noUsedlist2?id=` + id })
},
getAvailableList: async () => {
return await request.get({ url: `/iot/device/available-list` })
},
getAvailableListPage: async (params: any) => {
return await request.get({ url: `/iot/device/available-page` , params})
},
// 新增物联设备
createDevice: async (data: DeviceVO) => {
return await request.post({ url: `/iot/device/create`, data })
@ -151,10 +144,6 @@ export const DeviceApi = {
return await request.get({ url: `/iot/device/singleDevice`, params })
},
getSingleDeviceFrom: async (params: SingleDeviceParams) => {
return await request.get({ url: `/iot/device/singleDeviceFrom`, params })
},
getHistoryRecord: async (params: HistoryRecordParams) => {
return await request.get({
url: `/iot/device/historyRecord`,
@ -163,15 +152,6 @@ export const DeviceApi = {
})
},
getHistoryAnalyse: async (params: HistoryRecordParams) => {
return await request.get({
url: `/iot/device/historyAnalyse`,
params,
paramsSerializer: (p) => qs.stringify(p, { allowDots: true, arrayFormat: 'repeat' })
})
},
devicePointList: async () => {
return await request.get({ url: `/iot/device/devicePointList` })
},
@ -187,10 +167,6 @@ export const DeviceApi = {
getDeviceAttributePage: async (params) => {
return await request.get({ url: `/iot/device/device-attribute/page`, params })
},
// 获得设备属性按类型分租
getDeviceAttributeGroupList: async (params) => {
return await request.get({ url: `/iot/device/device-attribute/groupList`, params })
},
// 获得设备属性列表
getDeviceAttributeList: async (deviceId: number | string) => {
return await request.get({ url: `/iot/device/device-attribute/list?deviceId=` + deviceId })

@ -1,62 +0,0 @@
import request from '@/config/axios'
export interface DeviceOperationOverviewParams {
ids?: string
startTime?: string
endTime?: string
timelinePageNo?: number
timelinePageSize?: number
}
export interface DeviceOperationOverviewMetricVO {
key: string
icon: string
value: number
unit: string
change: number
}
export interface DeviceOperationOverviewHourlyStatusVO {
hour: string
running: number
standby: number
fault: number
offline: number
}
export interface DeviceOperationOverviewSummaryVO {
status: 'running' | 'standby' | 'fault' | 'offline'
percent: number
hours: number
}
export interface DeviceOperationOverviewTimelineSegmentVO {
status: 'running' | 'standby' | 'fault' | 'offline'
startHour: number
endHour: number
}
export interface DeviceOperationOverviewTimelineRowVO {
id: string
name: string
utilizationRate: number
segments: DeviceOperationOverviewTimelineSegmentVO[]
}
export interface DeviceOperationOverviewRespVO {
metrics: DeviceOperationOverviewMetricVO[]
hourlyStatus: DeviceOperationOverviewHourlyStatusVO[]
summary: DeviceOperationOverviewSummaryVO[]
summaryTotalHours: number
timelineRows: DeviceOperationOverviewTimelineRowVO[]
totalDevices: number
}
export const DeviceOperationOverviewApi = {
getRunOverview: async (params: DeviceOperationOverviewParams) => {
return await request.get<DeviceOperationOverviewRespVO>({
url: `/iot/device-operation-record/runOverview`,
params
})
}
}

@ -42,10 +42,6 @@ export const DeviceAttributeTypeApi = {
},
// 获得采集点分类列表
importTemplate: async () => {
return await request.download({ url: `/iot/device-attribute-type/get-import-template` })
},
getDeviceAttributeTypeList: async () => {
return await request.get({ url: `/iot/device-attribute-type/list` })
},

@ -3,28 +3,11 @@ import request from '@/config/axios'
// 报工 VO
export interface BaogongRecordVO {
id: number // id
planId: number // 关联计划 id
planId: number // 关联计划id
num: number // 派工数量
baogongTime: Date // 派工时间
}
// 报工统计 VO
export interface BaogongRecordStatVO {
id: number // ID
taskCode: string // 任务单编码
planCode: string // 计划单编码
employeeId: string // 员工 ID
employeeName: string // 员工姓名
productName: string // 产品名称
productCode: string // 产品编码
baogongNum: number // 报工数量
passNum: number // 合格数量
noPassNum: number // 不合格数量
passRate: number // 合格率 (%)
reason: string // 原因
baogongTime: Date // 报工时间
}
// 报工 API
export const BaogongRecordApi = {
// 查询报工分页
@ -32,11 +15,6 @@ export const BaogongRecordApi = {
return await request.get({ url: `/mes/baogong-record/page`, params })
},
// 查询报工统计分页
getBaogongRecordStatPage: async (params: any) => {
return await request.get({ url: `/mes/baogong-record/stat-page`, params })
},
// 查询报工详情
getBaogongRecord: async (id: number) => {
return await request.get({ url: `/mes/baogong-record/get?id=` + id })
@ -62,13 +40,4 @@ export const BaogongRecordApi = {
return await request.download({ url: `/mes/baogong-record/export-excel`, params })
},
// 导出报工统计 Excel
exportBaogongRecordStat: async (params) => {
return await request.download({ url: `/mes/baogong-record/export-stat-excel`, params })
},
// 导出报工统计 Excel (新)
statExportExcel: async (params) => {
return await request.download({ url: `/mes/baogong-record/stat-export-excel`, params })
},
}

@ -49,9 +49,5 @@ export const BomApi = {
// 获得产品BOM明细列表
getBomDetailListByBomId: async (bomId) => {
return await request.get({ url: `/mes/bom/bom-detail/list-by-bom-id?bomId=` + bomId })
},
getBomByProductId: async (productId: number) => {
return await request.get({ url: `/mes/bom/getByProductId`, params: { productId } })
}
}

@ -3,15 +3,11 @@ import request from '@/config/axios'
export interface CriticalComponentVO {
id: number
code: string
isCode?: boolean
name: string
description?: string
count?: number
remark?: string
qrcodeUrl?: string
templateJson?: string | any
createTime?: string
images?: string
}
export const CriticalComponentApi = {
@ -21,9 +17,6 @@ export const CriticalComponentApi = {
getCriticalComponentList: async () => {
return await request.get({ url: `/mes/critical-component/list`, })
},
getCriticalComponent: async (id: number) => {
return await request.get({ url: `/mes/critical-component/get?id=` + id })
},
createCriticalComponent: async (data: Partial<CriticalComponentVO>) => {
return await request.post({ url: `/mes/critical-component/create`, data })
},
@ -32,12 +25,6 @@ export const CriticalComponentApi = {
return await request.put({ url: `/mes/critical-component/update`, data })
},
regenerateCode: async (id: number, code: string) => {
return await request.post({
url: `/mes/critical-component/regenerate-code?id=${id}&code=${encodeURIComponent(code)}`
})
},
deleteCriticalComponent: async (ids: string) => {
return await request.delete({ url: `/mes/critical-component/delete?ids=` + ids })
},

@ -1,47 +0,0 @@
import request from '@/config/axios'
export interface CapacityReportVO {
id: number
deviceCode: string
deviceName: string
typeName: string
deviceStatus: number
ratedCapacity: number
reportCapacity: number
actualCapacity: number
workshopName: string
}
export interface CapacityReportQuery {
pageNo: number
pageSize: number
deviceCode?: string
deviceName?: string
deviceType?: string
deviceStatus?: string
workshop?: string
ids?: string
}
export const DeviceLedgerApi = {
/**
*
*/
getCapacityReportPage: async (params: CapacityReportQuery) => {
return await request.get({ url: '/mes/device-ledger/capacity-report/page', params })
},
/**
*
*/
exportCapacityReport: async (params: { ids?: string }) => {
return await request.download({ url: '/mes/device-ledger/capacity-report/export-excel', params })
},
/**
*
*/
updateDeviceLedger: async (data: { id: number; deviceStatus: number }) => {
return await request.put({ url: '/mes/device-ledger/update', data })
}
}

@ -6,7 +6,6 @@ import { MoldVO } from '@/api/erp/mold'
export interface DeviceLedgerVO {
id: number // id
deviceCode: string // 设备编号
isCode?: boolean
deviceName: string // 设备名称
deviceStatus: number // 设备状态 (0-正常, 1-停用, 2-维修, 3-报废)
deviceBrand: string // 设备品牌
@ -25,11 +24,6 @@ export interface DeviceLedgerVO {
deviceRemark: string // 设备备注
remark: string // 备注
fileUrl?: string // 附件下载
qrcodeUrl?: string
templateJson?: string | any
isSchedueld?: number
isScheduled?: number
ratedCapacity?: number
componentId?: string // 关键件ids集合
componentList?: CriticalComponentVO[]
beijianId?: string // 备件ids集合
@ -43,7 +37,6 @@ export interface DeviceLedgerVO {
createTime?: string | number | Date
updateTime?: string | number | Date
sort: number // 排序
images?: string // 图片
}
// 设备类型 API
@ -80,10 +73,6 @@ export const DeviceLedgerApi = {
return await request.put({ url: `/mes/device-ledger/update`, data })
},
regenerateCode: async (id: number, code: string) => {
return await request.post({ url: `/mes/device-ledger/regenerate-code?id=${id}&code=${encodeURIComponent(code)}` })
},
// 删除设备类型
deleteDeviceLedger: async (ids: string) => {
return await request.delete({ url: `/mes/device-ledger/delete?ids=` + ids })

@ -11,7 +11,6 @@ export interface DvSubjectVO {
isEnable: string // 是否启用
inspectionMethod: string // 检验方式
valueType: string // 值类型
jobType:string // 作业类型
judgmentCriteria: string // 判定基准
creator?: string // 创建人
createTime?: string | number | Date // 创建时间

@ -42,61 +42,6 @@ export interface EnergyDeviceDataRecordVO {
latestDataTime?: string
}
export interface EnergyOverviewMetricVO {
key: string
label: string
value: string
unit?: string
subLabel?: string
subValue?: string
change?: string
down?: boolean
}
export interface EnergyOverviewTrendVO {
unit?: string
xAxis: string[]
data: string[]
}
export interface EnergyOverviewRegionItemVO {
name: string
value: string
percent: string
}
export interface EnergyOverviewRegionVO {
unit?: string
totalValue: string
items: EnergyOverviewRegionItemVO[]
}
export interface EnergyOverviewRankVO {
id: number
name: string
region: string
value: string
}
export interface EnergyOverviewDetailVO {
id: number
name: string
energyType: string
region: string
value: string
startTime: string
endTime: string
}
export interface EnergyOverviewRespVO {
metrics: EnergyOverviewMetricVO[]
trendChart: EnergyOverviewTrendVO
regionChart: EnergyOverviewRegionVO
rankList: EnergyOverviewRankVO[]
total: number
detailList: EnergyOverviewDetailVO[]
}
// 能源设备 API
export const EnergyDeviceApi = {
// 查询能源设备分页
@ -121,10 +66,6 @@ export const EnergyDeviceApi = {
return await request.get({ url: `/mes/energy-device/queryDataRecords`, params })
},
queryOverviewData: async (params: any) => {
return await request.get({ url: `/mes/energy-device/queryOverviewData`, params })
},
exportQueryDataRecords: async (params: any) => {
return await request.download({ url: `/mes/energy-device/record-export-excel`, params })
},

@ -1,36 +1,6 @@
import request from '@/config/axios'
import {ItemRequisitionVO} from "@/api/mes/itemrequisition";
// 计划记录状态枚举
export enum OperateStatusEnum {
SCHEDULED = '1', // 已排产
PAUSED = '3', // 暂停
PENDING_WAREHOUSE = '4', // 待入库
WAREHOUSED = '5', // 已入库
STARTED = '8' // 已开工
}
// 计划记录状态映射
export const OPERATE_STATUS_MAP: Record<string, string> = {
'1': '已排产',
'3': '暂停',
'4': '待入库',
'5': '已入库',
'8': '已开工'
}
// 计划记录 VO
export interface PlanRecordVO {
id: number // ID
taskId: number // 任务ID
planId: number // 计划ID
operateStatus: string // 操作状态 ('1' | '3' | '4' | '5' | '8')
operateTime: number // 操作时间戳(毫秒)
remark: string // 备注
isEnable: boolean // 是否启用
createTime: number // 创建时间戳
}
// 生产计划 VO
export interface PlanVO {
id: number // ID
@ -56,27 +26,6 @@ export interface PlanVO {
feedingPipelineName: string
wangongNumber: number
passRate: number
passNumber?: number // 合格数量
noPassNumber?: number // 不合格数量
deviceName?: string // 设备名称
planRecordList?: PlanRecordVO[] // 计划记录列表
}
export interface DevicePlanGanttPlanVO {
planId: number
planStartTime: string
planEndTime: string
latestStartTime: string
planNumber: number
productName: string
deviceName: string
}
export interface DevicePlanGanttRespVO {
deviceId: number
deviceName: string
deviceCode: string
plans: DevicePlanGanttPlanVO[]
}
// 生产计划 API
@ -114,9 +63,6 @@ export const PlanApi = {
saveBatchPlan: async (data: any) => {
return await request.post({ url: `/mes/plan/saveBatchPlan`, data })
},
createBatch: async (data:any) => {
return await request.post({ url: `/mes/plan/create-batch`, data })
},
// 派工生产计划
arrangePlan: async (data: ItemRequisitionVO) => {
return await request.put({ url: `/mes/plan/paigong`, data })
@ -170,16 +116,5 @@ export const PlanApi = {
},
getLastSevenDaysCompletedCount: async (params?: any) => {
return await request.get({ url: `/mes/plan/getLastSevenDaysCompletedCount`, params })
},
getGanttByDevice: async (params: { startTime: string; endTime: string }) => {
return await request.get<DevicePlanGanttRespVO[]>({ url: `/mes/plan/gantt-by-device`, params })
},
getPlanPageByTask: async (params: any) => {
return await request.get({ url: `/mes/plan/page-by-task`, params })
},
getProductCapacityPage: async (params: any) => {
return await request.get({ url: `/mes/plan/product-capacity-page`, params })
}
}

@ -1,34 +0,0 @@
import request from '@/config/axios'
export interface PrintTemplateVO {
id: number
templateCode: string
templateName: string
templateType: number
templateBizType: string
templateJson: string
remark: string
isEnable: boolean
createTime: string
}
export const PrintTemplateApi = {
getPrintTemplatePage: async (params: any) => {
return await request.get({ url: `/mes/print-template/page`, params })
},
getPrintTemplate: async (id: number) => {
return await request.get({ url: `/mes/print-template/get?id=` + id })
},
createPrintTemplate: async (data: PrintTemplateVO) => {
return await request.post({ url: `/mes/print-template/create`, data })
},
updatePrintTemplate: async (data: PrintTemplateVO) => {
return await request.put({ url: `/mes/print-template/update`, data })
},
deletePrintTemplate: async (id: number) => {
return await request.delete({ url: `/mes/print-template/delete?id=` + id })
},
exportPrintTemplate: async (params) => {
return await request.download({ url: `/mes/print-template/export-excel`, params })
},
}

@ -6,7 +6,6 @@ export interface TaskVO {
code: string // 编码
orderDate: Date // 下达日期
deliveryDate: Date // 交货日期
isUrgent: string // 是否急单0-否 1-是
status: number // 状态
processInstanceId: string // 流程实例的编号
remark: string // 备注
@ -156,8 +155,4 @@ export const TaskApi = {
generatePlanByTaskDetail: async (params) => {
return await request.get({ url: `/mes/task/generate-plan/byTaskDetail`, params })
},
// 一键排产
oneClickSchedule: async (data: any) => {
return await request.post({ url: `/mes/task/one-click-schedule`, data })
},
}

@ -44,10 +44,6 @@ export const ZjTaskApi = {
return await request.get({ url: `/mes/zj-task/list`, params })
},
getZjTaskPageByTicket: async (params: any) => {
return await request.get({ url: `/mes/zj-task/page-by-ticket`, params })
},
createZjTask: async (data: ZjTaskVO) => {
return await request.post({ url: `/mes/zj-task/create`, data })
},

@ -16,13 +16,12 @@ export interface MenuVO {
visible: boolean
keepAlive: boolean
alwaysShow?: boolean
clientType?: number
createTime: Date
}
// 查询菜单(精简)列表
export const getSimpleMenusList = (clientType?: number) => {
return request.get({ url: '/system/menu/simple-list', params: { clientType } })
export const getSimpleMenusList = () => {
return request.get({ url: '/system/menu/simple-list' })
}
// 查询菜单列表

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 B

@ -9,14 +9,13 @@ const props = defineProps({
modelValue: propTypes.bool.def(false),
title: propTypes.string.def('Dialog'),
fullscreen: propTypes.bool.def(true),
initialFullscreen: propTypes.bool.def(false),
width: propTypes.oneOfType([String, Number]).def('40%'),
scroll: propTypes.bool.def(false),
scroll: propTypes.bool.def(false), // maxHeight
maxHeight: propTypes.oneOfType([String, Number]).def('400px')
})
const getBindValue = computed(() => {
const delArr: string[] = ['fullscreen', 'initialFullscreen', 'title', 'maxHeight', 'appendToBody']
const delArr: string[] = ['fullscreen', 'title', 'maxHeight', 'appendToBody']
const attrs = useAttrs()
const obj = { ...attrs, ...props }
for (const key in obj) {
@ -27,7 +26,7 @@ const getBindValue = computed(() => {
return obj
})
const isFullscreen = ref(props.initialFullscreen)
const isFullscreen = ref(false)
const toggleFull = () => {
isFullscreen.value = !unref(isFullscreen)

@ -4,8 +4,7 @@ import { isHexColor } from '@/utils/color'
import { ElTag } from 'element-plus'
import { DictDataType, getDictOptions } from '@/utils/dict'
import { isArray, isBoolean, isNumber, isString } from '@/utils/is'
import {useLocaleStoreWithOut} from "@/store/modules/locale";
const localeStore = useLocaleStoreWithOut()
export default defineComponent({
name: 'DictTag',
props: {
@ -69,7 +68,6 @@ export default defineComponent({
if (dict.colorType + '' === 'primary' || dict.colorType + '' === 'default') {
dict.colorType = ''
}
const label = localeStore.getCurrentLocale?.lang === 'en' ? (dict.labelEn || dict.label) : dict.label
return (
//
<ElTag
@ -78,7 +76,7 @@ export default defineComponent({
color={dict?.cssClass && isHexColor(dict?.cssClass) ? dict?.cssClass : ''}
disableTransitions={true}
>
{label}
{dict?.label}
</ElTag>
)
}

@ -1,397 +0,0 @@
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle" width="92%" :fullscreen="false" top="4vh">
<div class="hiprint-preview">
<div class="hiprint-toolbar">
<div class="hiprint-paper">
<el-button
v-for="(value, type) in paperTypes"
:key="type"
size="small"
:type="curPaperType === type ? 'primary' : 'default'"
@click="setPaper(type, value)"
>
{{ type }}
</el-button>
<el-popover placement="bottom-start" :width="260" trigger="click" v-model:visible="paperPopVisible">
<template #reference>
<el-button size="small" :type="curPaperType === 'other' ? 'primary' : 'default'">自定义纸张</el-button>
</template>
<div class="paper-pop">
<div class="paper-pop-title">设置纸张宽高(mm)</div>
<div class="paper-pop-form">
<el-input-number v-model="paperWidth" :precision="1" :min="1" controls-position="right" />
<span>x</span>
<el-input-number v-model="paperHeight" :precision="1" :min="1" controls-position="right" />
</div>
<el-button class="mt-8px" size="small" type="primary" @click="setPaperOther"></el-button>
</div>
</el-popover>
</div>
<div class="hiprint-zoom">
<el-button size="small" @click="changeScale(false)">
<Icon icon="ep:zoom-out" />
</el-button>
<div class="zoom-value">{{ (scaleValue * 100).toFixed(0) }}%</div>
<el-button size="small" @click="changeScale(true)">
<Icon icon="ep:zoom-in" />
</el-button>
</div>
<el-button type="primary" size="small" @click="handlePrint">
<Icon icon="ep:printer" class="mr-4px" />
浏览器打印
</el-button>
</div>
<div class="hiprint-body">
<div class="hiprint-left">
<div class="hiprint-title">基础元素</div>
<div :id="dragContainerId" class="hiprint-drag-wrap">
<div v-for="item in baseElements" :key="item.tid" class="ep-draggable-item hiprint-item" :tid="item.tid">
<span>{{ item.label }}</span>
</div>
</div>
</div>
<div class="hiprint-center">
<div :id="designerContainerId"></div>
</div>
<div class="hiprint-right">
<div :id="settingContainerId"></div>
</div>
</div>
</div>
</Dialog>
</template>
<script setup lang="ts">
import { defaultElementTypeProvider, hiprint } from 'vue-plugin-hiprint'
interface HiprintPreviewOpenOptions {
title?: string
printData?: Record<string, any> | Record<string, any>[]
withDefaultQrcodeLayout?: boolean
templateJson?: Record<string, any>
paperSize?: {
width: number
height: number
}
}
const message = useMessage()
const baseElements = [
{ tid: 'defaultModule.text', label: '文本' },
{ tid: 'defaultModule.image', label: '图片' },
{ tid: 'defaultModule.longText', label: '长文' },
{ tid: 'defaultModule.table', label: '表格' },
{ tid: 'defaultModule.html', label: 'HTML' },
{ tid: 'defaultModule.hline', label: '横线' },
{ tid: 'defaultModule.vline', label: '竖线' },
{ tid: 'defaultModule.rect', label: '矩形' },
{ tid: 'defaultModule.oval', label: '圆形' }
]
const dialogVisible = ref(false)
const dialogTitle = ref('打印预览')
const currentPrintData = ref<Record<string, any> | Record<string, any>[]>({})
const withDefaultQrcodeLayout = ref(false)
const currentTemplateJson = ref<Record<string, any> | undefined>(undefined)
const instanceId = `hiprint-preview-${Math.random().toString(36).slice(2)}`
const dragContainerId = `${instanceId}-drag`
const designerContainerId = `${instanceId}-designer`
const settingContainerId = `${instanceId}-setting`
const paperTypes = {
A3: { width: 420, height: 296.6 },
A4: { width: 210, height: 296.6 },
A5: { width: 210, height: 147.6 },
B3: { width: 500, height: 352.6 },
B4: { width: 250, height: 352.6 },
B5: { width: 250, height: 175.6 }
}
const curPaper = ref({
type: 'A4',
width: 210,
height: 296.6
})
const paperPopVisible = ref(false)
const paperWidth = ref(220)
const paperHeight = ref(80)
const curPaperType = computed(() => {
let type = 'other'
for (const [key, value] of Object.entries(paperTypes)) {
if (value.width === curPaper.value.width && value.height === curPaper.value.height) {
type = key
break
}
}
return type
})
const scaleValue = ref(1)
const scaleMax = 5
const scaleMin = 0.5
let hiprintInited = false
let hiprintTemplate: any
const ensureInit = () => {
if (hiprintInited) {
return
}
hiprint.init({
providers: [defaultElementTypeProvider()]
})
hiprintInited = true
}
const buildLeftElement = () => {
const jquery = (window as any).$
if (!jquery) {
message.warning('未检测到 jQuery无法加载拖拽元素')
return
}
hiprint.PrintElementTypeManager.buildByHtml(jquery(`#${dragContainerId} .ep-draggable-item`))
}
const buildDesigner = () => {
const jquery = (window as any).$
if (!jquery) {
message.warning('未检测到 jQuery无法初始化打印设计器')
return
}
jquery(`#${designerContainerId}`).empty()
const template =
currentTemplateJson.value || (withDefaultQrcodeLayout.value ? buildDefaultQrcodeTemplateJson() : undefined)
hiprintTemplate = new hiprint.PrintTemplate({
template,
settingContainer: `#${settingContainerId}`
})
hiprintTemplate.design(`#${designerContainerId}`)
setPaper(curPaperType.value, { width: curPaper.value.width, height: curPaper.value.height })
hiprintTemplate.zoom(scaleValue.value)
}
const buildDefaultQrcodeTemplateJson = () => ({
panels: [
{
index: 0,
name: 1,
width: 80,
height: 60,
paperHeader: 0,
paperFooter: 170.07874015748033,
printElements: [
{
options: {
left: 20,
top: 6,
height: 36,
width: 36,
textType: 'qrcode',
title: '二维码',
field: 'qrcodeContent',
testData: 'XS888888888'
},
printElementType: {
title: '二维码',
type: 'text'
}
},
{
options: {
left: 8,
top: 46,
height: 8,
width: 60,
title: 'ID',
field: 'printId',
testData: 'ID: 1001',
textAlign: 'center',
fontSize: 10
},
printElementType: {
title: '文本',
type: 'text'
}
}
],
paperNumberLeft: 196,
paperNumberTop: 148,
paperNumberDisabled: true,
paperNumberContinue: true,
watermarkOptions: {},
panelLayoutOptions: {}
}
]
})
const setPaper = (type: string, value: { width: number; height: number }) => {
if (!hiprintTemplate) {
return
}
const width = Number(value.width)
const height = Number(value.height)
curPaper.value = { type, width, height }
hiprintTemplate.setPaper(width, height)
}
const setPaperOther = () => {
paperPopVisible.value = false
setPaper('other', { width: Number(paperWidth.value), height: Number(paperHeight.value) })
}
const changeScale = (isZoomIn: boolean) => {
if (!hiprintTemplate) {
return
}
let nextScale = scaleValue.value
if (isZoomIn) {
nextScale += 0.1
if (nextScale > scaleMax) nextScale = scaleMax
} else {
nextScale -= 0.1
if (nextScale < scaleMin) nextScale = scaleMin
}
scaleValue.value = nextScale
hiprintTemplate.zoom(nextScale)
}
const handlePrint = () => {
if (!hiprintTemplate) {
return
}
hiprintTemplate.print(currentPrintData.value, { leftOffset: -1, topOffset: -1 })
}
const resetState = () => {
scaleValue.value = 1
curPaper.value = {
type: 'A4',
width: 210,
height: 296.6
}
paperWidth.value = 220
paperHeight.value = 80
paperPopVisible.value = false
}
const open = async (options: HiprintPreviewOpenOptions = {}) => {
dialogTitle.value = options.title || '打印预览'
currentPrintData.value = options.printData || {}
withDefaultQrcodeLayout.value = !!options.withDefaultQrcodeLayout
currentTemplateJson.value = options.templateJson
resetState()
if (options.paperSize?.width && options.paperSize?.height) {
const width = Number(options.paperSize.width)
const height = Number(options.paperSize.height)
curPaper.value = {
type: 'other',
width,
height
}
paperWidth.value = width
paperHeight.value = height
}
dialogVisible.value = true
await nextTick()
ensureInit()
buildLeftElement()
buildDesigner()
}
defineExpose({ open })
</script>
<style scoped lang="scss">
.hiprint-preview {
width: 100%;
}
.hiprint-toolbar {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
.hiprint-paper {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.hiprint-zoom {
display: flex;
align-items: center;
gap: 4px;
}
.zoom-value {
width: 56px;
text-align: center;
font-size: 13px;
color: var(--el-text-color-regular);
}
.paper-pop-title {
font-size: 14px;
font-weight: 600;
}
.paper-pop-form {
margin-top: 8px;
display: flex;
align-items: center;
gap: 8px;
}
.hiprint-body {
height: 75vh;
display: grid;
grid-template-columns: 220px 1fr 300px;
gap: 12px;
}
.hiprint-left,
.hiprint-center,
.hiprint-right {
background: var(--el-bg-color);
border: 1px solid var(--el-border-color-light);
border-radius: 8px;
overflow: auto;
}
.hiprint-center {
padding: 16px;
}
.hiprint-right {
padding: 12px;
}
.hiprint-title {
padding: 10px 10px 0;
font-weight: 600;
}
.hiprint-drag-wrap {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
padding: 10px;
}
.hiprint-item {
min-height: 56px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
border: 1px solid var(--el-border-color);
background: var(--el-fill-color-light);
color: var(--el-text-color-primary);
cursor: grab;
}
</style>

@ -1,5 +1,4 @@
import Icon from './src/Icon.vue'
import IconSelect from './src/IconSelect.vue'
import AppIconSelect from './src/AppIconSelect.vue'
export { Icon, IconSelect, AppIconSelect }
export { Icon, IconSelect }

@ -1,280 +0,0 @@
<script lang="ts" setup>
import { uniIconsList, uviewIconsList, uniIconsUnicodeMap, uviewIconsUnicodeMap } from './appIconData'
defineOptions({ name: 'AppIconSelect' })
const props = defineProps({
modelValue: {
require: false,
type: String
},
clearable: {
require: false,
type: Boolean
}
})
const emit = defineEmits<{ (e: 'update:modelValue', v: string) }>()
const visible = ref(false)
const inputValue = toRef(props, 'modelValue')
const currentActiveType = ref('uni-icons')
const filterValue = ref('')
const iconDataMap: Record<string, string[]> = {
'uni-icons': uniIconsList,
'uview-plus': uviewIconsList
}
const unicodeMapMap: Record<string, Record<string, string>> = {
'uni-icons': uniIconsUnicodeMap,
'uview-plus': uviewIconsUnicodeMap
}
const fontFamilyMap: Record<string, string> = {
'uni-icons': 'UniIconsFontFamily',
'uview-plus': 'uview-iconfont'
}
const tabsList = [
{ label: 'uni-icons', name: 'uni-icons' },
{ label: 'uview-plus', name: 'uview-plus' }
]
const pageSize = ref(96)
const currentPage = ref(1)
const currentIconList = computed(() => {
return iconDataMap[currentActiveType.value] || []
})
const currentUnicodeMap = computed(() => {
return unicodeMapMap[currentActiveType.value] || {}
})
const currentFontFamily = computed(() => {
return fontFamilyMap[currentActiveType.value] || 'UniIconsFontFamily'
})
const filteredList = computed(() => {
return currentIconList.value.filter((v) =>
v.toLowerCase().includes(filterValue.value.toLowerCase())
)
})
const pageList = computed(() => {
if (currentPage.value === 1) {
return filteredList.value.slice(0, pageSize.value)
}
return filteredList.value.slice(
pageSize.value * (currentPage.value - 1),
pageSize.value * (currentPage.value - 1) + pageSize.value
)
})
const iconCount = computed(() => filteredList.value.length)
function parseIconValue(value: string): { prefix: string; name: string } | null {
if (!value || value.indexOf(':') < 0) return null
const idx = value.indexOf(':')
return {
prefix: value.substring(0, idx),
name: value.substring(idx + 1)
}
}
function getUnicode(name: string): string {
return currentUnicodeMap.value[name] || ''
}
function getSelectedFontFamily(fullValue: string): string {
const parsed = parseIconValue(fullValue)
if (!parsed) return 'UniIconsFontFamily'
return fontFamilyMap[parsed.prefix] || 'UniIconsFontFamily'
}
function getSelectedUnicode(fullValue: string): string {
const parsed = parseIconValue(fullValue)
if (!parsed) return ''
const map = unicodeMapMap[parsed.prefix]
return map ? map[parsed.name] || '' : ''
}
function handleClick({ props }: any) {
currentPage.value = 1
currentActiveType.value = props.name
}
function onChangeIcon(item: string) {
emit('update:modelValue', currentActiveType.value + ':' + item)
visible.value = false
}
function onCurrentChange(page: number) {
currentPage.value = page
}
function clearIcon() {
emit('update:modelValue', '')
visible.value = false
}
watch(
() => props.modelValue,
(val) => {
if (val) {
const parsed = parseIconValue(val)
if (parsed && fontFamilyMap[parsed.prefix]) {
currentActiveType.value = parsed.prefix
}
}
}
)
watch(
() => filterValue.value,
() => {
currentPage.value = 1
}
)
</script>
<template>
<div class="selector">
<ElInput v-model="inputValue" @click="visible = !visible" :clearable="props.clearable" @clear="clearIcon">
<template #append>
<ElPopover
:visible="visible"
:width="355"
popper-class="pure-popper"
trigger="click"
>
<template #reference>
<div
class="h-32px w-40px flex cursor-pointer items-center justify-center"
@click="visible = !visible"
>
<span
v-if="inputValue && getSelectedUnicode(inputValue)"
class="app-icon-font"
:style="{ fontFamily: getSelectedFontFamily(inputValue), fontSize: '18px' }"
>{{ getSelectedUnicode(inputValue) }}</span>
<span v-else class="text-12px color-gray-500">选择</span>
</div>
</template>
<ElInput v-model="filterValue" class="p-2" clearable placeholder="搜索图标" />
<ElDivider border-style="dashed" />
<ElTabs v-model="currentActiveType" @tab-click="handleClick">
<ElTabPane
v-for="(pane, index) in tabsList"
:key="index"
:label="pane.label"
:name="pane.name"
>
<ElDivider border-style="dashed" class="tab-divider" />
<ElScrollbar height="220px">
<ul class="ml-2 flex flex-wrap">
<li
v-for="(item, key) in pageList"
:key="key"
:style="inputValue === currentActiveType + ':' + item ? { borderColor: 'var(--el-color-primary)', color: 'var(--el-color-primary)' } : {}"
:title="item"
class="icon-item mr-2 mt-1 w-1/10 flex cursor-pointer items-center justify-center border border-solid p-2"
@click="onChangeIcon(item)"
>
<span
class="app-icon-font"
:style="{ fontFamily: currentFontFamily }"
>{{ getUnicode(item) }}</span>
</li>
</ul>
</ElScrollbar>
</ElTabPane>
</ElTabs>
<ElDivider border-style="dashed" />
<ElPagination
:current-page="currentPage"
:page-size="pageSize"
:total="iconCount"
background
class="h-10 flex items-center justify-center"
layout="prev, pager, next"
size="small"
@current-change="onCurrentChange"
/>
</ElPopover>
</template>
</ElInput>
</div>
</template>
<style lang="scss">
@font-face {
font-family: 'UniIconsFontFamily';
src: url('@/assets/fonts/uniicons.ttf') format('truetype');
font-display: swap;
}
@font-face {
font-family: 'uview-iconfont';
src: url('@/assets/fonts/uview-icons.ttf') format('truetype');
font-display: swap;
}
</style>
<style lang="scss" scoped>
.app-icon-font {
font-style: normal;
font-size: 20px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.el-divider--horizontal {
margin: 1px auto !important;
}
.tab-divider.el-divider--horizontal {
margin: 0 !important;
}
.icon-item {
&:hover {
color: var(--el-color-primary);
border-color: var(--el-color-primary);
transform: scaleX(1.05);
transition: all 0.4s;
}
}
:deep(.el-tabs__nav-next) {
font-size: 15px;
line-height: 32px;
box-shadow: -5px 0 5px -6px #ccc;
}
:deep(.el-tabs__nav-prev) {
font-size: 15px;
line-height: 32px;
box-shadow: 5px 0 5px -6px #ccc;
}
:deep(.el-input-group__append) {
padding: 0;
}
:deep(.el-tabs__item) {
height: 30px;
font-size: 12px;
font-weight: normal;
line-height: 30px;
}
:deep(.el-tabs__header),
:deep(.el-tabs__nav-wrap) {
position: static;
margin: 0;
}
</style>

@ -1,382 +0,0 @@
const uniIconsUnicodeMap: Record<string, string> = {
'arrow-down': '\ue6be',
'arrow-left': '\ue6bc',
'arrow-right': '\ue6bb',
'arrow-up': '\ue6bd',
'auth': '\ue6ab',
'auth-filled': '\ue6cc',
'back': '\ue6b9',
'bars': '\ue627',
'calendar': '\ue6a0',
'calendar-filled': '\ue6c0',
'camera': '\ue65a',
'camera-filled': '\ue658',
'cart': '\ue631',
'cart-filled': '\ue6d0',
'chat': '\ue65d',
'chat-filled': '\ue659',
'chatboxes': '\ue696',
'chatboxes-filled': '\ue692',
'chatbubble': '\ue697',
'chatbubble-filled': '\ue694',
'checkbox': '\ue62b',
'checkbox-filled': '\ue62c',
'checkmarkempty': '\ue65c',
'circle': '\ue65b',
'circle-filled': '\ue65e',
'clear': '\ue66d',
'close': '\ue673',
'closeempty': '\ue66c',
'cloud-download': '\ue647',
'cloud-download-filled': '\ue646',
'cloud-upload': '\ue645',
'cloud-upload-filled': '\ue648',
'color': '\ue6cf',
'color-filled': '\ue6c9',
'compose': '\ue67f',
'contact': '\ue693',
'contact-filled': '\ue695',
'down': '\ue6b8',
'bottom': '\ue6b8',
'download': '\ue68d',
'download-filled': '\ue681',
'email': '\ue69e',
'email-filled': '\ue69a',
'eye': '\ue651',
'eye-filled': '\ue66a',
'eye-slash': '\ue6b3',
'eye-slash-filled': '\ue6b4',
'fire': '\ue6a1',
'fire-filled': '\ue6c5',
'flag': '\ue65f',
'flag-filled': '\ue660',
'folder-add': '\ue6a9',
'folder-add-filled': '\ue6c8',
'font': '\ue6a3',
'forward': '\ue6ba',
'gear': '\ue664',
'gear-filled': '\ue661',
'gift': '\ue6a4',
'gift-filled': '\ue6c4',
'hand-down': '\ue63d',
'hand-down-filled': '\ue63c',
'hand-up': '\ue63f',
'hand-up-filled': '\ue63e',
'headphones': '\ue630',
'heart': '\ue639',
'heart-filled': '\ue641',
'help': '\ue679',
'help-filled': '\ue674',
'home': '\ue662',
'home-filled': '\ue663',
'image': '\ue670',
'image-filled': '\ue678',
'images': '\ue650',
'images-filled': '\ue64b',
'info': '\ue669',
'info-filled': '\ue649',
'left': '\ue6b7',
'link': '\ue6a5',
'list': '\ue644',
'location': '\ue6ae',
'location-filled': '\ue6af',
'locked': '\ue66b',
'locked-filled': '\ue668',
'loop': '\ue633',
'mail-open': '\ue643',
'mail-open-filled': '\ue63a',
'map': '\ue667',
'map-filled': '\ue666',
'map-pin': '\ue6ad',
'map-pin-ellipse': '\ue6ac',
'medal': '\ue6a2',
'medal-filled': '\ue6c3',
'mic': '\ue671',
'mic-filled': '\ue677',
'micoff': '\ue67e',
'micoff-filled': '\ue6b0',
'minus': '\ue66f',
'minus-filled': '\ue67d',
'more': '\ue64d',
'more-filled': '\ue64e',
'navigate': '\ue66e',
'navigate-filled': '\ue67a',
'notification': '\ue6a6',
'notification-filled': '\ue6c1',
'paperclip': '\ue652',
'paperplane': '\ue672',
'paperplane-filled': '\ue675',
'person': '\ue699',
'person-filled': '\ue69d',
'personadd': '\ue69f',
'personadd-filled': '\ue698',
'phone': '\ue69c',
'phone-filled': '\ue69b',
'plus': '\ue676',
'plus-filled': '\ue6c7',
'plusempty': '\ue67b',
'pulldown': '\ue632',
'pyq': '\ue682',
'qq': '\ue680',
'redo': '\ue64a',
'redo-filled': '\ue655',
'refresh': '\ue657',
'refresh-filled': '\ue656',
'refreshempty': '\ue6bf',
'reload': '\ue6b2',
'right': '\ue6b5',
'scan': '\ue62a',
'search': '\ue654',
'settings': '\ue653',
'settings-filled': '\ue6ce',
'shop': '\ue62f',
'shop-filled': '\ue6cd',
'smallcircle': '\ue67c',
'smallcircle-filled': '\ue665',
'sound': '\ue684',
'sound-filled': '\ue686',
'spinner-cycle': '\ue68a',
'staff': '\ue6a7',
'staff-filled': '\ue6cb',
'star': '\ue688',
'star-filled': '\ue68f',
'starhalf': '\ue683',
'trash': '\ue687',
'trash-filled': '\ue685',
'tune': '\ue6aa',
'tune-filled': '\ue6ca',
'undo': '\ue64f',
'undo-filled': '\ue64c',
'up': '\ue6b6',
'top': '\ue6b6',
'upload': '\ue690',
'upload-filled': '\ue68e',
'videocam': '\ue68c',
'videocam-filled': '\ue689',
'vip': '\ue6a8',
'vip-filled': '\ue6c6',
'wallet': '\ue6b1',
'wallet-filled': '\ue6c2',
'weibo': '\ue68b',
'weixin': '\ue691'
}
const uviewIconsUnicodeMap: Record<string, string> = {
'level': '\ue693',
'column-line': '\ue68e',
'checkbox-mark': '\ue807',
'folder': '\ue7f5',
'movie': '\ue7f6',
'star-fill': '\ue669',
'star': '\ue65f',
'phone-fill': '\ue64f',
'phone': '\ue622',
'apple-fill': '\ue881',
'chrome-circle-fill': '\ue885',
'backspace': '\ue67b',
'attach': '\ue632',
'cut': '\ue948',
'empty-car': '\ue602',
'empty-coupon': '\ue682',
'empty-address': '\ue646',
'empty-favor': '\ue67c',
'empty-permission': '\ue686',
'empty-news': '\ue687',
'empty-search': '\ue664',
'github-circle-fill': '\ue887',
'rmb': '\ue608',
'person-delete-fill': '\ue66a',
'reload': '\ue788',
'order': '\ue68f',
'server-man': '\ue6bc',
'search': '\ue62a',
'fingerprint': '\ue955',
'more-dot-fill': '\ue630',
'scan': '\ue662',
'share-square': '\ue60b',
'map': '\ue61d',
'map-fill': '\ue64e',
'tags': '\ue629',
'tags-fill': '\ue651',
'bookmark-fill': '\ue63b',
'bookmark': '\ue60a',
'eye': '\ue613',
'eye-fill': '\ue641',
'mic': '\ue64a',
'mic-off': '\ue649',
'calendar': '\ue66e',
'calendar-fill': '\ue634',
'trash': '\ue623',
'trash-fill': '\ue658',
'play-left': '\ue66d',
'play-right': '\ue610',
'minus': '\ue618',
'plus': '\ue62d',
'info': '\ue653',
'info-circle': '\ue7d2',
'info-circle-fill': '\ue64b',
'question': '\ue715',
'error': '\ue6d3',
'close': '\ue685',
'checkmark': '\ue6a8',
'android-circle-fill': '\ue67e',
'android-fill': '\ue67d',
'ie': '\ue87b',
'IE-circle-fill': '\ue889',
'google': '\ue87a',
'google-circle-fill': '\ue88a',
'setting-fill': '\ue872',
'setting': '\ue61f',
'minus-square-fill': '\ue855',
'plus-square-fill': '\ue856',
'heart': '\ue7df',
'heart-fill': '\ue851',
'camera': '\ue7d7',
'camera-fill': '\ue870',
'more-circle': '\ue63e',
'more-circle-fill': '\ue645',
'chat': '\ue620',
'chat-fill': '\ue61e',
'bag-fill': '\ue617',
'bag': '\ue619',
'error-circle-fill': '\ue62c',
'error-circle': '\ue624',
'close-circle': '\ue63f',
'close-circle-fill': '\ue637',
'checkmark-circle': '\ue63d',
'checkmark-circle-fill': '\ue635',
'question-circle-fill': '\ue666',
'question-circle': '\ue625',
'share': '\ue631',
'share-fill': '\ue65e',
'shopping-cart': '\ue621',
'shopping-cart-fill': '\ue65d',
'bell': '\ue609',
'bell-fill': '\ue640',
'list': '\ue650',
'list-dot': '\ue616',
'zhihu': '\ue6ba',
'zhihu-circle-fill': '\ue709',
'zhifubao': '\ue6b9',
'zhifubao-circle-fill': '\ue6b8',
'weixin-circle-fill': '\ue6b1',
'weixin-fill': '\ue6b2',
'twitter-circle-fill': '\ue6ab',
'twitter': '\ue6aa',
'taobao-circle-fill': '\ue6a7',
'taobao': '\ue6a6',
'weibo-circle-fill': '\ue6a5',
'weibo': '\ue6a4',
'qq-fill': '\ue6a1',
'qq-circle-fill': '\ue6a0',
'moments-circel-fill': '\ue69a',
'moments': '\ue69b',
'qzone': '\ue695',
'qzone-circle-fill': '\ue696',
'baidu-circle-fill': '\ue680',
'baidu': '\ue681',
'facebook-circle-fill': '\ue68a',
'facebook': '\ue689',
'car': '\ue60c',
'car-fill': '\ue636',
'warning-fill': '\ue64d',
'warning': '\ue694',
'clock-fill': '\ue638',
'clock': '\ue60f',
'edit-pen': '\ue612',
'edit-pen-fill': '\ue66b',
'email': '\ue611',
'email-fill': '\ue642',
'minus-circle': '\ue61b',
'minus-circle-fill': '\ue652',
'plus-circle': '\ue62e',
'plus-circle-fill': '\ue661',
'file-text': '\ue663',
'file-text-fill': '\ue665',
'pushpin': '\ue7e3',
'pushpin-fill': '\ue86e',
'grid': '\ue673',
'grid-fill': '\ue678',
'play-circle': '\ue647',
'play-circle-fill': '\ue655',
'pause-circle-fill': '\ue654',
'pause': '\ue8fa',
'pause-circle': '\ue643',
'eye-off': '\ue648',
'eye-off-outline': '\ue62b',
'gift-fill': '\ue65c',
'gift': '\ue65b',
'rmb-circle-fill': '\ue657',
'rmb-circle': '\ue677',
'kefu-ermai': '\ue656',
'server-fill': '\ue751',
'coupon-fill': '\ue8c4',
'coupon': '\ue8ae',
'integral': '\ue704',
'integral-fill': '\ue703',
'home-fill': '\ue964',
'home': '\ue965',
'hourglass-half-fill': '\ue966',
'hourglass': '\ue967',
'account': '\ue628',
'plus-people-fill': '\ue626',
'minus-people-fill': '\ue615',
'account-fill': '\ue614',
'thumb-down-fill': '\ue726',
'thumb-down': '\ue727',
'thumb-up': '\ue733',
'thumb-up-fill': '\ue72f',
'lock-fill': '\ue979',
'lock-open': '\ue973',
'lock-opened-fill': '\ue974',
'lock': '\ue97a',
'red-packet-fill': '\ue690',
'photo-fill': '\ue98b',
'photo': '\ue98d',
'volume-off-fill': '\ue659',
'volume-off': '\ue644',
'volume-fill': '\ue670',
'volume': '\ue633',
'red-packet': '\ue691',
'download': '\ue63c',
'arrow-up-fill': '\ue6b0',
'arrow-down-fill': '\ue600',
'play-left-fill': '\ue675',
'play-right-fill': '\ue676',
'rewind-left-fill': '\ue679',
'rewind-right-fill': '\ue67a',
'arrow-downward': '\ue604',
'arrow-leftward': '\ue601',
'arrow-rightward': '\ue603',
'arrow-upward': '\ue607',
'arrow-down': '\ue60d',
'arrow-right': '\ue605',
'arrow-left': '\ue60e',
'arrow-up': '\ue606',
'skip-back-left': '\ue674',
'skip-forward-right': '\ue672',
'rewind-right': '\ue66f',
'rewind-left': '\ue671',
'arrow-right-double': '\ue68d',
'arrow-left-double': '\ue68c',
'wifi-off': '\ue668',
'wifi': '\ue667',
'empty-data': '\ue62f',
'empty-history': '\ue684',
'empty-list': '\ue68b',
'empty-page': '\ue627',
'empty-order': '\ue639',
'man': '\ue697',
'woman': '\ue69c',
'man-add': '\ue61c',
'man-add-fill': '\ue64c',
'man-delete': '\ue61a',
'man-delete-fill': '\ue66a',
'zh': '\ue70a',
'en': '\ue692'
}
const uniIconsList = Object.keys(uniIconsUnicodeMap)
const uviewIconsList = Object.keys(uviewIconsUnicodeMap)
export { uniIconsList, uviewIconsList, uniIconsUnicodeMap, uviewIconsUnicodeMap }

@ -1,371 +0,0 @@
<template>
<div class="qrcode-action-card">
<el-image
v-if="imageUrl"
:src="imageUrl"
:preview-src-list="[imageUrl]"
preview-teleported
fit="cover"
class="qrcode-action-card__img"
>
<template #error>
<div class="qrcode-action-card__error">{{ errorText || emptyText }}</div>
</template>
</el-image>
<div v-else class="qrcode-action-card__error">{{ emptyText }}</div>
<div v-if="showActionMask" class="qrcode-action-card__mask">
<el-button v-if="showPreview" circle :disabled="!imageUrl" @click="handlePreview">
<Icon icon="ep:zoom-in" />
</el-button>
<el-button v-if="showPrint" circle :disabled="!imageUrl || printDisabled" @click="handlePrint">
<Icon icon="ep:printer" />
</el-button>
<el-button
v-if="showRefresh"
circle
:loading="refreshLoading"
:disabled="refreshDisabled || refreshLoading"
@click="handleRefresh"
>
<Icon icon="ep:refresh" />
</el-button>
</div>
</div>
<HiprintPreviewDialog ref="hiprintPreviewDialogRef" />
</template>
<script setup lang="ts">
import request from '@/config/axios'
import HiprintPreviewDialog from '@/components/HiprintPreviewDialog/index.vue'
import { createImageViewer } from '@/components/ImageViewer'
import { ElLoading } from 'element-plus'
const { t } = useI18n()
const message = useMessage()
const props = withDefaults(
defineProps<{
imageUrl?: string
printId?: string | number
printTitle?: string
emptyText?: string
refreshUrl?: string
refreshMethod?: 'post' | 'put' | 'get'
refreshDisabled?: boolean
printDisabled?: boolean
refreshConfirmText?: string
errorText?: string
showPreview?: boolean
showPrint?: boolean
showRefresh?: boolean
printAdaptive?: boolean
printPaperWidth?: number
printPaperHeight?: number
printMaxWidth?: number
printMaxHeight?: number
templateJsonUrl?: string
templateJson?: any
printData?: Record<string, any>
}>(),
{
imageUrl: '',
printId: '',
printTitle: '二维码打印预览',
emptyText: '',
refreshUrl: '',
refreshMethod: 'post',
refreshDisabled: false,
printDisabled: false,
refreshConfirmText: '',
errorText: '',
showPreview: true,
showPrint: true,
showRefresh: true,
printAdaptive: true,
printPaperWidth: 80,
printPaperHeight: 80,
printMaxWidth: 180,
printMaxHeight: 120,
templateJsonUrl: '',
templateJson: undefined,
printData: () => ({})
}
)
const emit = defineEmits<{
(e: 'refresh-success', data: any): void
}>()
const hiprintPreviewDialogRef = ref()
const refreshLoading = ref(false)
const showActionMask = computed(() => props.showPreview || props.showPrint || props.showRefresh)
const buildQrcodeTemplateJson = (
qrcodeUrl: string,
printId: string,
imageWidth: number,
imageHeight: number,
paperHeight: number
) => ({
panels: [
{
index: 0,
name: 1,
width: imageWidth,
height: paperHeight,
paperHeader: 0,
paperFooter: 0,
printElements: [
{
options: {
left: 0,
top: 0,
width: imageWidth,
height: imageHeight,
src: qrcodeUrl,
field: 'qrcodeUrl',
title: '二维码图片',
testData: qrcodeUrl
},
printElementType: {
title: '图片',
type: 'image'
}
},
{
options: {
left: 0,
top: imageHeight + 2,
height: 10,
width: imageWidth,
testData: printId,
title: 'ID',
field: 'printId',
textAlign: 'center',
fontSize: 10
},
printElementType: {
title: '文本',
type: 'text'
}
}
],
paperNumberDisabled: true,
paperNumberContinue: true,
watermarkOptions: {},
panelLayoutOptions: {}
}
]
})
const getImageSize = (src: string) =>
new Promise<{ width: number; height: number }>((resolve, reject) => {
const img = new Image()
img.onload = () => {
const width = img.naturalWidth || img.width
const height = img.naturalHeight || img.height
if (!width || !height) {
reject(new Error('invalid image size'))
return
}
resolve({ width, height })
}
img.onerror = () => reject(new Error('image load failed'))
img.src = src
})
const resolvePrintSize = async (src: string) => {
const fallbackWidth = Math.max(20, Number(props.printPaperWidth) || 80)
const fallbackHeight = Math.max(20, Number(props.printPaperHeight) || 80)
if (!props.printAdaptive) {
return { width: fallbackWidth, height: fallbackHeight }
}
try {
const size = await getImageSize(src)
const ratio = size.width / size.height
if (!Number.isFinite(ratio) || ratio <= 0) {
return { width: fallbackWidth, height: fallbackHeight }
}
if (ratio >= 1) {
const height = fallbackHeight
const width = Math.min(Math.max(fallbackWidth, Math.round(height * ratio)), Number(props.printMaxWidth) || 180)
return { width, height }
}
const width = fallbackWidth
const height = Math.min(Math.max(fallbackHeight, Math.round(width / ratio)), Number(props.printMaxHeight) || 120)
return { width, height }
} catch {
return { width: fallbackWidth, height: fallbackHeight }
}
}
const handlePreview = () => {
if (!props.imageUrl) return
createImageViewer({
zIndex: 9999999,
urlList: [props.imageUrl]
})
}
const replaceTemplateValues = (templateJson: any, printData: Record<string, any>) => {
if (!templateJson?.panels) return templateJson
return {
...templateJson,
panels: templateJson.panels.map((panel: any) => ({
...panel,
printElements: panel.printElements?.map((element: any) => {
if (!element?.options?.qid) return element
const qid = element.options.qid
const value = printData[qid]
if (value === undefined || value === null) return element
const newOptions = { ...element.options }
if (qid === 'qrcodeUrl') {
newOptions.src = value
newOptions.testData = value
} else {
newOptions.field = qid
if (newOptions.testData === undefined || newOptions.testData === '') {
newOptions.testData = String(value)
}
}
return {
...element,
options: newOptions
}
}) || []
}))
}
}
const handlePrint = async () => {
if (!props.imageUrl || !props.showPrint) return
let templateJson: any
let printData: Record<string, any>
printData = {
qrcodeUrl: props.imageUrl,
printId: props.printId === undefined || props.printId === null ? '' : String(props.printId),
...props.printData
}
if (props.templateJson) {
templateJson = replaceTemplateValues(props.templateJson, printData)
} else if (props.templateJsonUrl) {
try {
const response = await request.get({ url: props.templateJsonUrl })
templateJson = typeof response.data === 'string' ? JSON.parse(response.data) : response.data
templateJson = replaceTemplateValues(templateJson, printData)
} catch (error) {
console.error('获取打印模板失败', error)
message.error('获取打印模板失败')
return
}
} else {
const printSize = await resolvePrintSize(props.imageUrl)
const imageWidth = printSize.width
const imageHeight = printSize.height
const printId = props.printId === undefined || props.printId === null ? '' : String(props.printId)
const idAreaHeight = printId ? 14 : 0
const paperHeight = imageHeight + idAreaHeight
templateJson = buildQrcodeTemplateJson(props.imageUrl, printId, imageWidth, imageHeight, paperHeight)
}
const paperSize = templateJson?.panels?.[0] ? {
width: templateJson.panels[0].width,
height: templateJson.panels[0].height
} : { width: 80, height: 80 }
hiprintPreviewDialogRef.value?.open({
title: props.printTitle,
printData,
templateJson,
withDefaultQrcodeLayout: false,
paperSize
})
}
const handleRefresh = async () => {
if (!props.refreshUrl || props.refreshDisabled || !props.showRefresh) return
try {
await message.confirm(props.refreshConfirmText || '确认刷新二维码吗?')
} catch {
return
}
refreshLoading.value = true
const loading = ElLoading.service({
lock: true,
text: t('common.loading'),
background: 'rgba(0, 0, 0, 0.3)'
})
try {
let data
if (props.refreshMethod === 'get') {
data = await request.get({ url: props.refreshUrl })
} else if (props.refreshMethod === 'put') {
data = await request.put({ url: props.refreshUrl })
} else {
data = await request.post({ url: props.refreshUrl })
}
emit('refresh-success', data)
message.success(t('common.updateSuccess'))
} finally {
loading.close()
refreshLoading.value = false
}
}
</script>
<style scoped lang="scss">
.qrcode-action-card {
position: relative;
height: 150px;
width: fit-content;
min-width: 150px;
border-radius: 10px;
overflow: hidden;
border: 1px solid var(--el-border-color-lighter);
background: var(--el-fill-color-blank);
}
.qrcode-action-card__img {
height: 150px;
width: auto;
display: block;
}
.qrcode-action-card__error {
height: 150px;
min-width: 150px;
display: flex;
align-items: center;
justify-content: center;
color: var(--el-text-color-secondary);
font-size: 12px;
padding: 0 12px;
}
.qrcode-action-card__mask {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
background: rgba(0, 0, 0, 0.35);
opacity: 0;
transition: opacity 0.2s ease;
}
.qrcode-action-card:hover .qrcode-action-card__mask {
opacity: 1;
}
</style>

@ -17,10 +17,9 @@
/>
</el-select>
</ElDialog>
<div v-else class="custom-hover" @click.stop="toggleTopSearch">
<div v-else class="custom-hover" @click.stop="showTopSearch = !showTopSearch">
<Icon icon="ep:search" />
<el-select
ref="topSelectRef"
@click.stop
filterable
:reserve-keyword="false"
@ -52,8 +51,7 @@ defineProps({
const router = useRouter() //
const showSearch = ref(false) //
const showTopSearch = ref(false) //
const value: Ref = ref('')
const topSelectRef = ref<InstanceType<typeof ElSelect>>()
const value: Ref = ref('') //
const routers = router.getRoutes() //
const options = computed(() => {
@ -88,15 +86,6 @@ function hiddenTopSearch() {
showTopSearch.value = false
}
function toggleTopSearch() {
showTopSearch.value = !showTopSearch.value
if (showTopSearch.value) {
setTimeout(() => {
topSelectRef.value?.focus()
}, 600)
}
}
onMounted(() => {
window.addEventListener('keydown', listenKey)
window.addEventListener('click', hiddenTopSearch)

@ -1,241 +0,0 @@
<template>
<Dialog :title="title" v-model="dialogVisible" :appendToBody="true" width="1080">
<ContentWrap>
<!-- 新增查询表单区域 -->
<div v-if="$slots.header" class="search-container">
<slot name="header"></slot>
</div>
<el-table
ref="tableRef"
v-loading="loading"
:data="list"
:row-key="resolveRowKey"
:show-overflow-tooltip="true"
:stripe="true"
@selection-change="handleSelectionChange"
@row-click="handleRowClick"
>
<el-table-column type="selection" width="55" reserve-selection />
<el-table-column
v-for="column in columns"
:key="column.prop"
:label="column.label"
:prop="column.prop"
:width="column.width"
:min-width="column.minWidth"
:align="column.align || 'center'"
/>
</el-table>
<Pagination
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getList"
/>
</ContentWrap>
<template #footer>
<el-button type="primary" @click="submitSelection"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import type { ElTable } from 'element-plus'
defineOptions({ name: 'TableSelectDialog' })
type TableColumn = {
label: string
prop: string
width?: string | number
minWidth?: string | number
align?: 'left' | 'center' | 'right'
}
const props = withDefaults(
defineProps<{
title: string
columns: TableColumn[]
fetchApi: (params: Record<string, any>) => Promise<{ list: any[]; total: number }>
selectionType?: 'single' | 'multiple'
rowKey?: string
pageSize?: number
initialRows?: any[]
queryParams?: Record<string, any> //
/** ✅ 新增 */
defaultSelectedKeys?: (string | number)[]
}>(),
{
selectionType: 'multiple',
rowKey: 'id',
pageSize: 10,
initialRows: () => [],
queryParams: () => ({}), //
defaultSelectedKeys: () => []
}
)
const emit = defineEmits<{
(e: 'confirm', value: { ids: (number | string)[]; rows: any[] }): void
}>()
const dialogVisible = ref(false)
const loading = ref(false)
const list = ref<any[]>([])
const total = ref(0)
const tableRef = ref<InstanceType<typeof ElTable>>()
const queryParams = reactive({
pageNo: 1,
pageSize: props.pageSize
})
const selectedMap = ref(new Map<number | string, any>())
const syncingSelection = ref(false)
const isSingleSelect = computed(() => props.selectionType === 'single')
const resolveRowKey = (row: Record<string, any>) => row[props.rowKey]
const refreshSelectionOnTable = async () => {
await nextTick()
const table = tableRef.value
if (!table) return
syncingSelection.value = true
try {
table.clearSelection()
const selectedIds = selectedMap.value
list.value.forEach((row) => {
const rowId = resolveRowKey(row)
if (selectedIds.has(rowId)) {
table.toggleRowSelection(row, true)
}
})
} finally {
syncingSelection.value = false
}
}
/**
* 新增根据 defaultSelectedKeys 初始化选中状态
*/
const initDefaultSelection = () => {
if (!props.defaultSelectedKeys.length) return
props.defaultSelectedKeys.forEach((key) => {
selectedMap.value.set(key, { [props.rowKey]: key })
})
}
const getList = async () => {
loading.value = true
try {
const params = getQueryParams()
const data = await props.fetchApi(params)
list.value = data.list || []
total.value = data.total || 0
await refreshSelectionOnTable()
} finally {
loading.value = false
}
}
const handleSelectionChange = (rows: any[]) => {
if (syncingSelection.value) return
if (isSingleSelect.value) {
selectedMap.value.clear()
const latestRow = rows.length ? rows[rows.length - 1] : undefined
if (latestRow) {
selectedMap.value.set(resolveRowKey(latestRow), latestRow)
}
void refreshSelectionOnTable()
return
}
const pageIdSet = new Set(list.value.map((item) => resolveRowKey(item)))
pageIdSet.forEach((id) => {
selectedMap.value.delete(id)
})
rows.forEach((row) => {
selectedMap.value.set(resolveRowKey(row), row)
})
}
const handleRowClick = (row: any) => {
if (!isSingleSelect.value) return
const table = tableRef.value
if (!table) return
syncingSelection.value = true
try {
table.clearSelection()
table.toggleRowSelection(row, true)
} finally {
syncingSelection.value = false
}
selectedMap.value.clear()
selectedMap.value.set(resolveRowKey(row), row)
}
const open = async (rows?: any[]) => {
/* selectedMap.value.clear()
const initialRows = rows || props.initialRows
const nextRows = isSingleSelect.value ? initialRows.slice(0, 1) : initialRows
nextRows.forEach((row) => {
selectedMap.value.set(resolveRowKey(row), row)
})
queryParams.pageNo = 1
queryParams.pageSize = props.pageSize
dialogVisible.value = true
await getList()*/
selectedMap.value.clear()
// 使 defaultSelectedKeys
if (props.defaultSelectedKeys.length) {
initDefaultSelection()
}
// initialRows / rows
const initialRows = rows || props.initialRows
const nextRows = isSingleSelect.value ? initialRows.slice(0, 1) : initialRows
nextRows.forEach((row) => {
selectedMap.value.set(resolveRowKey(row), row)
})
queryParams.pageNo = 1
queryParams.pageSize = props.pageSize
dialogVisible.value = true
await getList()
}
const submitSelection = () => {
const rows = Array.from(selectedMap.value.values())
const ids = rows.map((row) => resolveRowKey(row))
emit('confirm', { ids, rows })
dialogVisible.value = false
}
//
const reload =async () => {
loading.value = true
//
await getList()
}
// 3. queryParams
const getQueryParams = () => {
return {
pageNo: queryParams.pageNo || 1,
pageSize: queryParams.pageSize || 10,
...props.queryParams //
}
}
// 4.
watch(() => props.queryParams, async () => {
await getList()
}, { deep: true })
defineExpose({ open, reload })
</script>
<style scoped>
.search-container {
margin-bottom: 10px;
padding: 0 0px;
}
</style>

@ -202,35 +202,6 @@ $prefix-cls: #{$namespace}-menu;
.#{$elNamespace}-menu-item {
padding-right: 0;
}
&:not(.#{$elNamespace}-menu--collapse) {
.#{$elNamespace}-sub-menu__title,
.#{$elNamespace}-menu-item {
display: flex;
align-items: flex-start;
/* min-height: var(--el-menu-item-height);*/
height: auto;
padding-top: 10px;
padding-bottom: 10px;
white-space: normal;
}
/* .#{$elNamespace}-sub-menu__icon-arrow {
margin-top: 8px;
}*/
.#{$prefix-cls}__title {
display: block;
flex: 1;
min-width: 0;
overflow: visible;
text-overflow: initial;
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
line-height: 1.4;
}
}
}
}
@ -257,10 +228,6 @@ $prefix-cls: #{$namespace}-menu;
}
.#{$prefix-cls}__title {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
/* stylelint-disable-next-line */
max-height: calc(var(--top-tool-height) - 2px) !important;
/* stylelint-disable-next-line */

@ -10,10 +10,14 @@ export const useRenderMenuTitle = () => {
return icon ? (
<>
<Icon icon={meta.icon}></Icon>
<span class="v-menu__title">{t(title as string)}</span>
<span class="v-menu__title overflow-hidden overflow-ellipsis whitespace-nowrap">
{t(title as string)}
</span>
</>
) : (
<span class="v-menu__title">{t(title as string)}</span>
<span class="v-menu__title overflow-hidden overflow-ellipsis whitespace-nowrap">
{t(title as string)}
</span>
)
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -41,8 +41,6 @@ import '@/plugins/tongji' // 百度统计
import Logger from '@/utils/Logger'
import VueDOMPurifyHTML from 'vue-dompurify-html' // 解决v-html 的安全隐患
import { hiPrintPlugin } from 'vue-plugin-hiprint'
import 'vue-plugin-hiprint/dist/print-lock.css'
// 创建实例
const setupAll = async () => {
@ -58,9 +56,6 @@ const setupAll = async () => {
setupFormCreate(app)
app.use(hiPrintPlugin, '$hiprint', false)
hiPrintPlugin.disAutoConnect()
setupRouter(app)
// directives 指令

@ -79,9 +79,7 @@ const whiteList = [
'/auth-redirect',
'/bind',
'/register',
'/oauthLogin/gitee',
'/iot/report/dashboardPage/Dashboard8',
'/iot/report/dashboardPage/Dashboard1'
'/oauthLogin/gitee'
]
// 路由加载前

@ -642,28 +642,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
canTo: true
}
},
{
path: '/iot',
component: Layout,
name: 'IotHiddenPages',
meta: {
hidden: true
},
children: [
{
path: 'runoverview',
component: () => import('@/views/iot/runoverview/index.vue'),
name: 'IotRunOverview',
meta: {
title: t('DataCollection.RunOverview.moduleName'),
hidden: true,
noTagsView: false,
canTo: true,
activeMenu: '/iot/runoverview'
}
}
]
},
{
path: '/:pathMatch(.*)*',
@ -694,28 +672,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
activeMenu: '/mes/plan'
},
component: () => import('@/views/mes/plan/index.vue')
},
{
path: 'production-report/detail/:id',
name: 'MesProductionReportDetail',
meta: {
title: '生产报工详情',
noCache: true,
hidden: true,
activeMenu: '/mes/productionReport'
},
component: () => import('@/views/mes/productionReport/detail/index.vue')
},
{
path: 'device-ledger/detail/:id',
name: 'MesDeviceLedgerDetail',
meta: {
title: '设备台账详情',
noCache: true,
hidden: true,
activeMenu: '/mes/deviceledger'
},
component: () => import('@/views/mes/deviceledger/detail/index.vue')
}
]
}

@ -20,7 +20,3 @@
.icon-user-o:before {
content: '\f2c0';
}
.el-table--small .cell {
padding: 0 2px;
}

@ -35,19 +35,3 @@
border-left-color: var(--el-color-primary);
}
}
/* 全局生效,所有表格的悬停色都会改变 */
.el-table--enable-row-hover .el-table__body tr:hover > td {
background-color: #f0f9eb !important;
}
/* 2. 关键补全:针对展开行内的 hover 状态 */
/* 展开行tr.expanded内部的 td 需要单独设置 */
.el-table__body .el-table__expanded-cell:hover > td {
background-color: #f0f9eb !important;
}
/* 3. 针对展开后触发的 hover-row 类(某些版本生效) */
.el-table__body tr.hover-row > td {
background-color: #f0f9eb !important;
}

@ -1,7 +1,7 @@
:root {
--login-bg-color: #293146;
--left-menu-max-width: 220px;
--left-menu-max-width: 200px;
--left-menu-min-width: 64px;

@ -268,7 +268,6 @@ export enum DICT_TYPE {
MES_ZJ_PRODUCT = "mes_zj_product",
FILE_STATUS = "file_status",
Classification = "classification",
MES_JOB_TYPE = "mes_job_type",
//====iot
IOT_SIEMENS_TYPE = "iot_siemens_type",
IOT_MODBUS_TYPE = "iot_modbus_type",
@ -291,7 +290,7 @@ export enum DICT_TYPE {
IOT_ALERT_LEVEL = "iot_alert_level",
IOT_PROTOCOL = "iot_protocol",
IOT_DEVICE_ATTRIBUTE_UNIT = "iot_device_attribute_unit",
IOT_ALARM_REGISTRATION = "alarm_registration",
IOT_ALARM_REGISTRATION = " alarm_registration",
PRIMARY_FLAG = "primary_flag"
}

@ -1,56 +1,83 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" min-label-width="68px">
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
min-label-width="68px"
>
<el-form-item :label="t('FactoryModeling.AutocodeRule.searchRuleCodeLabel')" prop="ruleCode">
<el-input v-model="queryParams.ruleCode"
:placeholder="t('FactoryModeling.AutocodeRule.searchRuleCodePlaceholder')" clearable
@keyup.enter="handleQuery" class="!w-240px" />
<el-input
v-model="queryParams.ruleCode"
:placeholder="t('FactoryModeling.AutocodeRule.searchRuleCodePlaceholder')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('FactoryModeling.AutocodeRule.searchRuleNameLabel')" prop="ruleName">
<el-input v-model="queryParams.ruleName"
:placeholder="t('FactoryModeling.AutocodeRule.searchRuleNamePlaceholder')" clearable
@keyup.enter="handleQuery" class="!w-240px" />
<el-input
v-model="queryParams.ruleName"
:placeholder="t('FactoryModeling.AutocodeRule.searchRuleNamePlaceholder')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('FactoryModeling.AutocodeRule.searchRuleDescLabel')" prop="ruleDesc">
<el-input v-model="queryParams.ruleDesc"
:placeholder="t('FactoryModeling.AutocodeRule.searchRuleDescPlaceholder')" clearable
@keyup.enter="handleQuery" class="!w-240px" />
<el-input
v-model="queryParams.ruleDesc"
:placeholder="t('FactoryModeling.AutocodeRule.searchRuleDescPlaceholder')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('FactoryModeling.AutocodeRule.searchRemarkLabel')" prop="remark" v-show="showAllFilters">
<el-input v-model="queryParams.remark" :placeholder="t('FactoryModeling.AutocodeRule.searchRemarkPlaceholder')"
clearable @keyup.enter="handleQuery" class="!w-240px" />
<el-form-item :label="t('FactoryModeling.AutocodeRule.searchRemarkLabel')" prop="remark">
<el-input
v-model="queryParams.remark"
:placeholder="t('FactoryModeling.AutocodeRule.searchRemarkPlaceholder')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('FactoryModeling.AutocodeRule.searchIsEnableLabel')" prop="isEnable"
v-show="showAllFilters">
<el-select v-model="queryParams.isEnable"
:placeholder="t('FactoryModeling.AutocodeRule.searchIsEnablePlaceholder')" clearable class="!w-240px">
<el-option v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)" :key="dict.value"
:label="dict.label" :value="dict.value" />
<el-form-item :label="t('FactoryModeling.AutocodeRule.searchIsEnableLabel')" prop="isEnable">
<el-select
v-model="queryParams.isEnable"
:placeholder="t('FactoryModeling.AutocodeRule.searchIsEnablePlaceholder')"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') :
t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> {{ t('FactoryModeling.AutocodeRule.searchButtonText') }}
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" /> {{ t('FactoryModeling.AutocodeRule.resetButtonText') }}
</el-button>
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['erp:autocode-rule:create']">
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('FactoryModeling.AutocodeRule.searchButtonText') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('FactoryModeling.AutocodeRule.resetButtonText') }}</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['erp:autocode-rule:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> {{ t('FactoryModeling.AutocodeRule.addButtonText') }}
</el-button>
<el-button type="success" plain @click="handleExport" :loading="exportLoading"
v-hasPermi="['erp:autocode-rule:export']">
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['erp:autocode-rule:export']"
>
<Icon icon="ep:download" class="mr-5px" /> {{ t('FactoryModeling.AutocodeRule.exportButtonText') }}
</el-button>
</el-form-item>
@ -71,27 +98,17 @@
</template>
</el-table-column>
<!-- <el-table-column label="ID" align="center" prop="id" /> -->
<el-table-column :label="t('FactoryModeling.AutocodeRule.tableRuleCodeColumn')" align="center" prop="ruleCode"
width="260px" sortable />
<el-table-column :label="t('FactoryModeling.AutocodeRule.tableRuleNameColumn')" align="center" prop="ruleName"
width="200px" sortable />
<el-table-column :label="t('FactoryModeling.AutocodeRule.tableBarcodeTypeColumn')" align="center">
<template #default="scope">
<el-tag type="primary">{{ getBarcodeTypeLabel(scope.row.barcodeType) }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="t('FactoryModeling.AutocodeRule.tableRuleCodeColumn')" align="center" prop="ruleCode" sortable/>
<el-table-column :label="t('FactoryModeling.AutocodeRule.tableRuleNameColumn')" align="center" prop="ruleName" sortable/>
<el-table-column :label="t('FactoryModeling.AutocodeRule.tableRuleDescColumn')" align="center" prop="ruleDesc" />
<el-table-column :label="t('FactoryModeling.AutocodeRule.tableMaxLengthColumn')" align="center"
prop="maxLength" />
<el-table-column :label="t('FactoryModeling.AutocodeRule.tableMaxLengthColumn')" align="center" prop="maxLength" />
<el-table-column :label="t('FactoryModeling.AutocodeRule.tableIsPaddedColumn')" align="center" prop="isPadded">
<template #default="scope">
<dict-tag :type="DICT_TYPE.ERP_AUTOCODE_IS_PADDING" :value="scope.row.isPadded" />
</template>
</el-table-column>
<el-table-column :label="t('FactoryModeling.AutocodeRule.tablePaddedCharColumn')" align="center"
prop="paddedChar" />
<el-table-column :label="t('FactoryModeling.AutocodeRule.tablePaddedMethodColumn')" align="center"
prop="paddedMethod">
<el-table-column :label="t('FactoryModeling.AutocodeRule.tablePaddedCharColumn')" align="center" prop="paddedChar" />
<el-table-column :label="t('FactoryModeling.AutocodeRule.tablePaddedMethodColumn')" align="center" prop="paddedMethod">
<template #default="scope">
<dict-tag :type="DICT_TYPE.ERP_AUTOCODE_PADDING_TYPE" :value="scope.row.paddedMethod" />
</template>
@ -103,26 +120,42 @@
</template>
</el-table-column>
<el-table-column :label="t('FactoryModeling.AutocodeRule.tableOperateColumn')" align="center" fixed="right"
width="200">
<el-table-column :label="t('FactoryModeling.AutocodeRule.tableOperateColumn')" align="center" fixed="right" width="200">
<template #default="scope">
<el-button link type="primary" @click="openForm('update', scope.row.id)"
v-hasPermi="['erp:autocode-rule:update']">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['erp:autocode-rule:update']"
>
{{ t('FactoryModeling.AutocodeRule.tableEditAction') }}
</el-button>
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['erp:autocode-rule:delete']">
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['erp:autocode-rule:delete']"
>
{{ t('FactoryModeling.AutocodeRule.tableDeleteAction') }}
</el-button>
<el-button link type="primary" @click="handleTestCode(scope.row.ruleCode)"
v-hasPermi="['erp:autocode-rule:update']">
<el-button
link
type="primary"
@click="handleTestCode(scope.row.ruleCode)"
v-hasPermi="['erp:autocode-rule:update']"
>
{{ t('FactoryModeling.AutocodeRule.tableTestCodeAction') }}
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
@pagination="getList" />
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
@ -162,20 +195,6 @@ const queryParams = reactive({
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
const showAllFilters = ref(false) //
const filterCount = 5 // ruleCoderuleNameruleDescremarkisEnable
/** 切换筛选框展开/折叠 */
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const getBarcodeTypeLabel = (value: any) => {
const str = value === undefined || value === null ? '' : String(value)
if (str === '1') return '条形码'
if (str === '2') return '二维码'
return '-'
}
/** 查询列表 */
const getList = async () => {
@ -217,17 +236,17 @@ const handleDelete = async (id: number) => {
message.success(t('common.delSuccess'))
//
await getList()
} catch { }
} catch {}
}
/** 测试编码按钮操作 */
const handleTestCode = async (ruleCode: string) => {
try {
//
const data = await AutocodeRuleApi.getTestCode(ruleCode)
message.success(data)
} catch { }
} catch {}
}
/** 导出按钮操作 */

@ -20,12 +20,6 @@
:placeholder="t('FactoryModeling.AutocodeRule.dialogRuleNamePlaceholder')"
/>
</el-form-item>
<el-form-item :label="t('FactoryModeling.AutocodeRule.dialogBarcodeTypeLabel')" prop="barcodeType">
<el-radio-group v-model="formData.barcodeType">
<el-radio :label="1">{{ t('FactoryModeling.AutocodeRule.barcodeTypeBarcodeLabel') }}</el-radio>
<el-radio :label="2">{{ t('FactoryModeling.AutocodeRule.barcodeTypeQrcodeLabel') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('FactoryModeling.AutocodeRule.dialogRuleDescLabel')" prop="ruleDesc">
<el-input
v-model="formData.ruleDesc"
@ -116,7 +110,6 @@ const formData = ref({
id: undefined,
ruleCode: undefined,
ruleName: undefined,
barcodeType: 1,
ruleDesc: undefined,
maxLength: undefined,
isPadded: undefined,
@ -146,12 +139,7 @@ const open = async (type: string, id?: number) => {
if (id) {
formLoading.value = true
try {
const data = await AutocodeRuleApi.getAutocodeRule(id)
const barcodeType = Number(data?.barcodeType ?? data?.barCodeType) === 2 ? 2 : 1
formData.value = {
...data,
barcodeType
}
formData.value = await AutocodeRuleApi.getAutocodeRule(id)
} finally {
formLoading.value = false
}
@ -174,12 +162,7 @@ const submitForm = async () => {
//
formLoading.value = true
try {
const barcodeType = Number(formData.value.barcodeType) === 2 ? 2 : 1
const data = {
...formData.value,
barcodeType,
barCodeType: barcodeType
} as unknown as AutocodeRuleVO
const data = formData.value as unknown as AutocodeRuleVO
//
data.autocodeParts = autocodePartFormRef.value.getData()
if (formType.value === 'create') {
@ -203,7 +186,6 @@ const resetForm = () => {
id: undefined,
ruleCode: undefined,
ruleName: undefined,
barcodeType: 1,
ruleDesc: undefined,
maxLength: undefined,
isPadded: undefined,

@ -21,7 +21,6 @@
v-model="row.warehouseId"
clearable
filterable
disabled="true"
:placeholder="t('SparePartsManagement.SpareIn.placeholderWarehouse')"
@change="onChangeWarehouse($event, row)"
>
@ -35,29 +34,6 @@
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('SparePartsManagement.SpareIn.itemProductCode')" min-width="150">
<template #default="{ row, $index }">
<!-- <el-form-item class="mb-0px!">
<el-input disabled v-model="row.productBarCode" />
</el-form-item>-->
<el-form-item :prop="`${$index}.productBarCode`" class="mb-0px!">
<el-select
v-model="row.productBarCode"
clearable
filterable
@change="onChangeProductCode($event, row)"
:placeholder="t('SparePartsManagement.SpareIn.placeholderProduct')"
>
<el-option
v-for="item in productList"
:key="item.barCode"
:label="item.barCode"
:value="item.barCode"
/>
</el-select>
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('SparePartsManagement.SpareIn.itemProductName')" min-width="180">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productId`" :rules="formRules.productId" class="mb-0px!">
@ -85,6 +61,13 @@
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('SparePartsManagement.SpareIn.itemProductCode')" min-width="150">
<template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.productBarCode" />
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('SparePartsManagement.SpareIn.itemUnitName')" min-width="80">
<template #default="{ row }">
<el-form-item class="mb-0px!">
@ -247,7 +230,7 @@ const getSummaries = (param: SummaryMethodProps) => {
const handleAdd = () => {
const row = {
id: undefined,
warehouseId: 10,
warehouseId: defaultWarehouse.value?.id,
productId: undefined,
productUnitName: undefined, //
productBarCode: undefined, //
@ -283,19 +266,6 @@ const onChangeProduct = (productId, row) => {
setStockCount(row)
}
/** 处理产品变更 */
const onChangeProductCode = (productBarCode, row) => {
const product = productList.value.find((item) => item.barCode === productBarCode)
if (product) {
row.productUnitName = product.unitName
row.productBarCode = product.barCode
row.productPrice = product.minPrice
row.productId = product.id
}
//
setStockCount(row)
}
/** 加载库存 */
const setStockCount = async (row) => {
if (!row.productId || !row.warehouseId) {

@ -52,7 +52,7 @@
/>
</el-select>
</el-form-item>
<el-form-item :label="t('SparePartsManagement.SpareIn.warehouse')" prop="warehouseId" v-show="showAllFilters">
<el-form-item :label="t('SparePartsManagement.SpareIn.warehouse')" prop="warehouseId">
<el-select
v-model="queryParams.warehouseId"
clearable
@ -68,7 +68,7 @@
/>
</el-select>
</el-form-item>
<el-form-item :label="t('SparePartsManagement.SpareIn.remark')" prop="remark" v-show="showAllFilters">
<el-form-item :label="t('SparePartsManagement.SpareIn.remark')" prop="remark">
<el-input
v-model="queryParams.remark"
:placeholder="t('SparePartsManagement.SpareIn.placeholderRemark')"
@ -77,12 +77,6 @@
class="!w-240px"
/>
</el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') : t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button>
@ -259,11 +253,6 @@ const queryParams = reactive({
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
const showAllFilters = ref(false)
const filterCount = 5
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const productList = ref<ProductVO[]>([]) //
const warehouseList = ref<WarehouseVO[]>([]) //
const supplierList = ref<SupplierVO[]>([]) //

@ -45,7 +45,7 @@
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('SparePartsManagement.SpareOut.warehouse')" prop="warehouseId" v-show="showAllFilters">
<el-form-item :label="t('SparePartsManagement.SpareOut.warehouse')" prop="warehouseId">
<el-select
v-model="queryParams.warehouseId"
clearable
@ -61,7 +61,7 @@
/>
</el-select>
</el-form-item>
<el-form-item :label="t('SparePartsManagement.SpareOut.remark')" prop="remark" v-show="showAllFilters">
<el-form-item :label="t('SparePartsManagement.SpareOut.remark')" prop="remark">
<el-input
v-model="queryParams.remark"
:placeholder="t('SparePartsManagement.SpareOut.placeholderRemark')"
@ -70,12 +70,6 @@
class="!w-240px"
/>
</el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') : t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button>
@ -254,11 +248,6 @@ const queryParams = reactive({
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
const showAllFilters = ref(false)
const filterCount = 5
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const productList = ref<ProductVO[]>([]) //
const warehouseList = ref<WarehouseVO[]>([]) //

@ -11,24 +11,7 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item :label="t('SparePartsManagement.SpareInfo.code')" prop="barCode">
<el-row :gutter="10" style="width: 100%">
<el-col :xs="24" :sm="18" :md="16" :lg="14" :xl="12">
<el-input
v-model="formData.barCode"
:placeholder="t('SparePartsManagement.SpareInfo.placeholderCode')"
:disabled="Boolean(formData.isCode) || formType === 'update'"
/>
</el-col>
<el-col :xs="24" :sm="6" :md="4" :lg="3" :xl="2">
<div>
<el-switch
v-model="formData.isCode"
:disabled="formType === 'update'"
@change="handleCodeAutoChange"
/>
</div>
</el-col>
</el-row>
<el-input v-model="formData.barCode" :placeholder="t('SparePartsManagement.SpareInfo.placeholderCode')" />
</el-form-item>
</el-col>
<el-col :span="12">
@ -36,11 +19,6 @@
<el-input v-model="formData.name" :placeholder="t('SparePartsManagement.SpareInfo.placeholderName')" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('SparePartsManagement.SpareInfo.standard')" prop="standard">
<el-input v-model="formData.standard" :placeholder="t('SparePartsManagement.SpareInfo.placeholderStandard')" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('SparePartsManagement.SpareInfo.unit')" prop="unitId">
<el-select v-model="formData.unitId" clearable :placeholder="t('SparePartsManagement.SpareInfo.placeholderUnit')" class="w-1/1">
@ -53,11 +31,11 @@
</el-select>
</el-form-item>
</el-col>
<!-- <el-col :span="12">
<el-col :span="12">
<el-form-item :label="t('SparePartsManagement.SpareInfo.standard')" prop="standard">
<el-input v-model="formData.standard" :placeholder="t('SparePartsManagement.SpareInfo.placeholderStandard')" />
</el-form-item>
</el-col>-->
</el-col>
<!-- <el-col :span="12">
<el-form-item :label="t('SparePartsManagement.SpareInfo.expiryDay')" prop="expiryDay">
<el-input-number
@ -104,26 +82,6 @@
</el-radio-group>
</el-form-item>
</el-col>
<el-col v-if="formType === 'update'" :span="24">
<el-form-item :label="t('SparePartsManagement.SpareInfo.qrcode')" prop="qrcodeUrl">
<QrcodeActionCard
:image-url="formData.qrcodeUrl"
:print-id="formData.id"
:print-title="`${formData.name || '备件'}码打印预览`"
:print-paper-width="80"
:print-paper-height="80"
:print-max-width="220"
:empty-text="t('SparePartsManagement.SpareInfo.qrcodeEmpty')"
:error-text="t('SparePartsManagement.SpareInfo.qrcodeLoadError')"
:refresh-url="getQrcodeRefreshUrl()"
:refresh-disabled="!formData.id || !formData.barCode"
refresh-confirm-text="确认刷新该备件二维码吗?"
:template-json="formData.templateJson"
:print-data="buildPrintData()"
@refresh-success="handleQrcodeRefreshSuccess"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item :label="t('SparePartsManagement.SpareInfo.remark')" prop="remark">
<el-input type="textarea" v-model="formData.remark" :placeholder="t('SparePartsManagement.SpareInfo.placeholderRemark')"/>
@ -140,8 +98,8 @@
<script setup lang="ts">
import { ProductApi, ProductVO } from '@/api/erp/product/product'
import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit'
import QrcodeActionCard from '@/components/QrcodeActionCard/index.vue'
import { CommonStatusEnum } from '@/utils/constants'
import { defaultProps, handleTree } from '@/utils/tree'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
/** ERP 产品 表单 */
@ -158,9 +116,6 @@ const formData = ref({
id: undefined,
name: undefined,
barCode: undefined,
isCode: undefined,
qrcodeUrl: undefined,
templateJson: undefined,
categoryId: undefined,
unitId: undefined,
status: undefined,
@ -173,20 +128,9 @@ const formData = ref({
minPrice: undefined,
safetyNumber: undefined
})
const validateBarCode = (_rule, value, callback) => {
if (Boolean(formData.value.isCode)) {
callback()
return
}
if (value === undefined || value === null || String(value).trim() === '') {
callback(new Error(t('SparePartsManagement.SpareInfo.validatorCodeRequired')))
return
}
callback()
}
const formRules = reactive({
name: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorNameRequired'), trigger: 'blur' }],
barCode: [{ validator: validateBarCode, trigger: ['blur', 'change'] }],
barCode: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorCodeRequired'), trigger: 'blur' }],
categoryId: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorCategoryRequired'), trigger: 'blur' }],
unitId: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorUnitRequired'), trigger: 'blur' }],
status: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorStatusRequired'), trigger: 'blur' }]
@ -204,15 +148,7 @@ const open = async (type: string, id?: number) => {
if (id) {
formLoading.value = true
try {
const productData = await ProductApi.getProduct(id)
const templateJson = productData?.templateJson
const parsedTemplateJson = typeof templateJson === 'string'
? JSON.parse(templateJson)
: templateJson
formData.value = {
...productData,
templateJson: parsedTemplateJson
}
formData.value = await ProductApi.getProduct(id)
} finally {
formLoading.value = false
}
@ -222,43 +158,6 @@ const open = async (type: string, id?: number) => {
}
defineExpose({ open }) // open
const handleCodeAutoChange = (value: boolean) => {
if (value) {
formData.value.barCode = undefined
}
formRef.value?.clearValidate('barCode')
}
const getQrcodeRefreshUrl = () => {
if (!formData.value.id || !formData.value.barCode) return ''
return `/erp/product/regenerate-code?id=${formData.value.id}&code=${encodeURIComponent(String(formData.value.barCode))}`
}
const buildPrintData = () => {
return {
id: formData.value.id,
barCode: formData.value.barCode,
name: formData.value.name,
standard: formData.value.standard,
unitId: formData.value.unitId,
safetyNumber: formData.value.safetyNumber,
status: formData.value.status,
remark: formData.value.remark,
qrcodeUrl: formData.value.qrcodeUrl
}
}
const handleQrcodeRefreshSuccess = async (data: any) => {
if (!formData.value.id) return
if (data?.qrcodeUrl) {
formData.value.qrcodeUrl = data.qrcodeUrl
return
}
const productData = await ProductApi.getProduct(formData.value.id)
formData.value.qrcodeUrl = productData?.qrcodeUrl
formData.value.barCode = productData?.barCode ?? formData.value.barCode
}
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
@ -290,9 +189,6 @@ const resetForm = () => {
id: undefined,
name: undefined,
barCode: undefined,
isCode: true,
qrcodeUrl: undefined,
templateJson: undefined,
categoryId: undefined,
unitId: undefined,
status: CommonStatusEnum.ENABLE,
@ -307,4 +203,3 @@ const resetForm = () => {
formRef.value?.resetFields()
}
</script>
<style scoped lang="scss"></style>

@ -11,15 +11,6 @@
:inline="true"
min-label-width="68px"
>
<el-form-item :label="t('SparePartsManagement.SpareInfo.code')" prop="code">
<el-input
v-model="queryParams.code"
:placeholder="t('SparePartsManagement.SpareInfo.placeholderCode')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('SparePartsManagement.SpareInfo.name')" prop="name">
<el-input
v-model="queryParams.name"
@ -29,7 +20,6 @@
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button>
@ -65,10 +55,9 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column :label="t('SparePartsManagement.SpareInfo.code')" align="center" prop="barCode" width="240px" sortable />
<el-table-column :label="t('SparePartsManagement.SpareInfo.code')" align="center" prop="barCode" sortable />
<el-table-column :label="t('SparePartsManagement.SpareInfo.name')" align="left" prop="name" width="220px" sortable />
<el-table-column :label="t('SparePartsManagement.SpareInfo.standard')" align="center" prop="standard" />
<!-- <el-table-column v-if="checkPermi(['erp:component:standard'])" :label="t('SparePartsManagement.SpareInfo.deviceSpec')" align="center" prop="deviceSpec" sortable />-->
<el-table-column v-if="checkPermi(['erp:component:standard'])" :label="t('SparePartsManagement.SpareInfo.standard')" align="center" prop="standard" />
<el-table-column :label="t('SparePartsManagement.SpareInfo.category')" align="center" prop="categoryName" sortable />
<el-table-column :label="t('SparePartsManagement.SpareInfo.unit')" align="center" prop="unitName" sortable />
<el-table-column :label="t('SparePartsManagement.SpareInfo.safetyNumber')" align="center" prop="safetyNumber" />
@ -187,7 +176,6 @@ const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
code: undefined,
categoryId: undefined
})
const queryFormRef = ref() //

@ -52,7 +52,7 @@
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('SparePartsManagement.SpareRecord.createTime')" prop="createTime" v-show="showAllFilters">
<el-form-item :label="t('SparePartsManagement.SpareRecord.createTime')" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
@ -63,12 +63,6 @@
class="!w-220px"
/>
</el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') : t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button>
@ -97,7 +91,7 @@
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column :label="t('SparePartsManagement.SpareRecord.product')" align="left" sortable prop="productName" width="300px"/>
<el-table-column :label="t('SparePartsManagement.SpareRecord.product')" align="left" sortable prop="productName" width="210px"/>
<el-table-column :label="t('SparePartsManagement.SpareRecord.unit')" align="center" prop="unitName" sortable />
<el-table-column :label="t('SparePartsManagement.SpareRecord.warehouse')" align="center" prop="warehouseName" sortable />
<el-table-column :label="t('SparePartsManagement.SpareRecord.bizType')" align="center" prop="bizType" min-width="120" sortable>
@ -179,11 +173,6 @@ const queryParams = reactive({
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
const showAllFilters = ref(false)
const filterCount = 4
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const productList = ref<ProductVO[]>([]) //
const warehouseList = ref<WarehouseVO[]>([]) //

@ -43,12 +43,6 @@
/>
</el-select>
</el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') : t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button>
@ -124,11 +118,6 @@ const queryParams = reactive({
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
const showAllFilters = ref(false)
const filterCount = 2
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const productList = ref<ProductVO[]>([]) //
const warehouseList = ref<WarehouseVO[]>([]) //

@ -4,28 +4,15 @@
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
label-width="100px"
v-loading="formLoading"
>
<el-form-item :label="t('MoldManagement.Mold.code')" prop="code">
<el-row :gutter="10" style="width: 100%">
<el-col :xs="24" :sm="18" :md="16" :lg="14" :xl="12">
<el-input
v-model="formData.code"
:placeholder="t('MoldManagement.Mold.placeholderCode')"
:disabled="formData.isCode == true || formType === 'update'"
/>
</el-col>
<el-col :xs="24" :sm="6" :md="4" :lg="3" :xl="2">
<div>
<el-switch
v-model="formData.isCode"
:disabled="formType === 'update'"
@change="handleCodeAutoChange"
/>
</div>
</el-col>
</el-row>
<el-input
v-model="formData.code"
:placeholder="t('MoldManagement.Mold.placeholderCode')"
:disabled="formType == 'update'"
/>
</el-form-item>
<el-form-item :label="t('MoldManagement.Mold.name')" prop="name">
<el-input
@ -84,24 +71,6 @@
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="formType === 'update'" :label="t('MoldManagement.Mold.qrcode')" prop="qrcodeUrl">
<QrcodeActionCard
:image-url="formData.qrcodeUrl"
:print-id="formData.id"
:print-title="`${formData.name || '模具'}码打印预览`"
:print-paper-width="80"
:print-paper-height="80"
:print-max-width="220"
:empty-text="t('MoldManagement.Mold.qrcodeEmpty')"
:error-text="t('MoldManagement.Mold.qrcodeLoadError')"
:refresh-url="getQrcodeRefreshUrl()"
:refresh-disabled="!formData.id || !formData.code"
refresh-confirm-text="确认刷新该模具二维码吗?"
:template-json="formData.templateJson"
:print-data="buildPrintData()"
@refresh-success="handleQrcodeRefreshSuccess"
/>
</el-form-item>
<el-form-item :label="t('MoldManagement.Mold.images')" prop="images">
<UploadImg v-model="formData.images" />
</el-form-item>
@ -139,7 +108,6 @@
import { getIntDictOptions, DICT_TYPE, getBoolDictOptions } from '@/utils/dict'
import { MoldBrandApi } from '@/api/erp/mold'
import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit'
import QrcodeActionCard from '@/components/QrcodeActionCard/index.vue'
const unitList = ref<ProductUnitVO[]>([]) //
const { t } = useI18n() //
@ -152,7 +120,6 @@ const formType = ref('') // 表单的类型create - 新增update - 修改
const formData = ref({
id: undefined,
code: undefined,
isCode: undefined,
name: undefined,
unitId: undefined,
machineId: undefined,
@ -160,27 +127,14 @@ const formData = ref({
inTime: undefined,
status: undefined,
images: undefined,
qrcodeUrl: undefined,
templateJson: undefined,
remark: undefined,
isEnable: undefined,
fileUrl: '',
brandId: undefined
})
const validateCode = (_rule, value, callback) => {
if (Boolean(formData.value.isCode)) {
callback()
return
}
if (value === undefined || value === null || String(value).trim() === '') {
callback(new Error(t('MoldManagement.Mold.validatorCodeRequired')))
return
}
callback()
}
const formRules = reactive({
code: [
{ validator: validateCode, trigger: ['blur', 'change'] }
{ required: true, message: t('MoldManagement.Mold.validatorCodeRequired'), trigger: 'blur' }
],
name: [
{ required: true, message: t('MoldManagement.Mold.validatorNameRequired'), trigger: 'blur' }
@ -211,15 +165,7 @@ const open = async (type: string, id?: number, brandId: number) => {
if (id) {
formLoading.value = true
try {
const moldData = await MoldBrandApi.getMold(id)
const templateJson = moldData?.templateJson
const parsedTemplateJson = typeof templateJson === 'string'
? JSON.parse(templateJson)
: templateJson
formData.value = {
...moldData,
templateJson: parsedTemplateJson
}
formData.value = await MoldBrandApi.getMold(id)
} finally {
formLoading.value = false
}
@ -230,44 +176,6 @@ const open = async (type: string, id?: number, brandId: number) => {
}
defineExpose({ open }) // open
const handleCodeAutoChange = (value: boolean) => {
if (value) {
formData.value.code = undefined
}
formRef.value?.clearValidate('code')
}
const getQrcodeRefreshUrl = () => {
if (!formData.value.id || !formData.value.code) return ''
return `/erp/mold-brand/regenerate-code?id=${formData.value.id}&code=${encodeURIComponent(String(formData.value.code))}`
}
const buildPrintData = () => {
return {
id: formData.value.id,
code: formData.value.code,
name: formData.value.name,
unitId: formData.value.unitId,
machineId: formData.value.machineId,
useTime: formData.value.useTime,
inTime: formData.value.inTime,
status: formData.value.status,
remark: formData.value.remark,
qrcodeUrl: formData.value.qrcodeUrl
}
}
const handleQrcodeRefreshSuccess = async (data: any) => {
if (!formData.value.id) return
if (data?.qrcodeUrl) {
formData.value.qrcodeUrl = data.qrcodeUrl
return
}
const moldData = await MoldBrandApi.getMold(formData.value.id)
formData.value.qrcodeUrl = moldData?.qrcodeUrl
formData.value.code = moldData?.code ?? formData.value.code
}
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
@ -297,7 +205,6 @@ const resetForm = () => {
formData.value = {
id: undefined,
code: undefined,
isCode: true,
name: undefined,
unitId: undefined,
machineId: undefined,
@ -305,8 +212,6 @@ const resetForm = () => {
inTime: undefined,
status: 3,
images: undefined,
qrcodeUrl: undefined,
templateJson: undefined,
remark: undefined,
isEnable: true,
fileUrl: '',
@ -315,4 +220,3 @@ const resetForm = () => {
formRef.value?.resetFields()
}
</script>
<style scoped lang="scss"></style>

@ -325,6 +325,12 @@ const maintainDateRange = ref<string[] | undefined>(undefined)
const repairExportLoading = ref(false)
const repairDateRange = ref<string[] | undefined>(undefined)
const openQrCode = () => {
const url = detailData.value?.qrcodeUrl
if (!url) return
window.open(String(url), '_blank')
}
const formatDetailDate = (value: any) => {
if (!value) return ''
return formatDate(new Date(value), 'YYYY-MM-DD')
@ -714,7 +720,7 @@ onMounted(async () => {
}
.mold-qrcode-img {
/* width: 150px; */
width: 150px;
height: 150px;
border-radius: 10px;
overflow: hidden;

@ -54,7 +54,7 @@ type="success" plain @click="handleExport" :loading="exportLoading"
</ContentWrap>
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column :label="t('MoldManagement.Mold.code')" align="center" prop="code" sortable width="200px"/>
<el-table-column :label="t('MoldManagement.Mold.code')" align="center" prop="code" sortable />
<el-table-column :label="t('MoldManagement.Mold.name')" align="left" prop="name" sortable />
<el-table-column :label="t('MoldManagement.Mold.useTime')" align="center" prop="useTime" sortable />
<el-table-column :label="t('MoldManagement.Mold.status')" align="center" prop="status" sortable>
@ -241,8 +241,8 @@ const handleDelete = async (ids: number | number[]) => {
//
await message.delConfirm()
//
const idList = Array.isArray(ids) ? ids : [ids]
await Promise.all(idList.map((id) => MoldBrandApi.deleteMold(id)))
const idsParam = buildIdsParam(ids)
await DeviceLedgerApi.deleteDeviceLedger(idsParam)
message.success(t('common.delSuccess'))
selectedIds.value = []
tableRef.value?.clearSelection?.()

@ -11,24 +11,7 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item :label="t('FactoryModeling.ProductInformation.dialogBarCodeLabel')" prop="barCode">
<el-row :gutter="10" style="width: 100%">
<el-col :xs="24" :sm="18" :md="16" :lg="14" :xl="20">
<el-input
v-model="formData.barCode"
:placeholder="t('FactoryModeling.ProductInformation.dialogBarCodePlaceholder')"
:disabled="Boolean(formData.isCode) || formType === 'update'"
/>
</el-col>
<el-col :xs="24" :sm="6" :md="4" :lg="3" :xl="2">
<div>
<el-switch
v-model="formData.isCode"
:disabled="formType === 'update'"
@change="handleCodeAutoChange"
/>
</div>
</el-col>
</el-row>
<el-input v-model="formData.barCode" :placeholder="t('FactoryModeling.ProductInformation.dialogBarCodePlaceholder')" :disabled = "formType === 'update'"/>
</el-form-item>
</el-col>
<el-col :span="12">
@ -87,7 +70,7 @@
/>
</el-form-item>
</el-col> -->
<!-- <el-col :span="12">
<el-col :span="12">
<el-form-item :label="t('FactoryModeling.ProductInformation.dialogPurchasePriceLabel')" prop="purchasePrice">
<el-input-number
v-model="formData.purchasePrice"
@ -119,28 +102,8 @@
class="!w-1/1"
/>
</el-form-item>
</el-col> -->
<el-col v-if="isProductCategory" :span="12">
<el-form-item label="关联设备" prop="devices">
<el-input
:model-value="deviceDisplayText"
placeholder="点击选择设备"
readonly
@click="openDeviceSelectDialog"
/>
</el-form-item>
</el-col>
<el-col v-if="isProductCategory" :span="12">
<el-form-item label="关联模具" prop="molds">
<el-input
:model-value="moldDisplayText"
placeholder="点击选择模具"
readonly
@click="openMoldSelectDialog"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-col :span="12">
<el-form-item :label="t('FactoryModeling.ProductInformation.dialogStatusLabel')" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
@ -153,22 +116,6 @@
</el-radio-group>
</el-form-item>
</el-col>
<el-col v-if="formType === 'update'" :span="24">
<el-form-item :label="t('FactoryModeling.ProductInformation.qrcode')" prop="qrcodeUrl">
<QrcodeActionCard
:image-url="formData.qrcodeUrl"
:print-id="formData.id"
:print-title="`${formData.name || '产品'}二维码打印预览`"
:empty-text="t('FactoryModeling.ProductInformation.qrcodeEmpty')"
:refresh-url="getQrcodeRefreshUrl()"
:refresh-disabled="!formData.id || !formData.barCode"
refresh-confirm-text="确认刷新该产品二维码吗?"
:template-json="formData.templateJson"
:print-data="buildPrintData()"
@refresh-success="handleQrcodeRefreshSuccess"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item :label="t('FactoryModeling.ProductInformation.dialogRemarkLabel')" prop="remark">
<el-input type="textarea" v-model="formData.remark" :placeholder="t('FactoryModeling.ProductInformation.dialogRemarkPlaceholder')" />
@ -181,65 +128,11 @@
<el-button @click="dialogVisible = false">{{ t('common.cancel') }}</el-button>
</template>
</Dialog>
<TableSelectDialog
ref="deviceSelectDialogRef"
title="选择设备"
:columns="deviceColumns"
:fetch-api="fetchDeviceLedgerPage"
row-key="id"
@confirm="handleDeviceSelectConfirm"
:query-params="mergedQueryParams"
>
<!-- 使用 header 插槽插入查询表单 -->
<template #header>
<el-form ref="searchFormRef" :model="searchParams" :inline="true" >
<el-form-item label="设备编号" prop="deviceCode">
<el-input v-model="searchParams.deviceCode" placeholder="请输入编号" clearable />
</el-form-item>
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="searchParams.deviceName" placeholder="请输入名称" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">{{ t('FactoryModeling.ProductInformation.searchButtonText') }}</el-button>
<el-button @click="resetSearch">{{ t('FactoryModeling.ProductInformation.resetButtonText') }}</el-button>
</el-form-item>
</el-form>
</template>
</TableSelectDialog>
<TableSelectDialog
ref="moldSelectDialogRef"
title="选择模具"
:columns="moldColumns"
:fetch-api="MoldBrandApi.getMoldPage"
row-key="id"
@confirm="handleMoldSelectConfirm"
:query-params="mergedMoldQueryParams"
>
<!-- 使用 header 插槽插入查询表单 -->
<template #header>
<el-form ref="searchMoldFormRef" :model="searchMoldParams" :inline="true" >
<el-form-item label="模具编号" prop="code">
<el-input v-model="searchMoldParams.code" placeholder="请输入编号" clearable />
</el-form-item>
<el-form-item label="模具名称" prop="name">
<el-input v-model="searchMoldParams.name" placeholder="请输入名称" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleMoldSearch">{{ t('FactoryModeling.ProductInformation.searchButtonText') }}</el-button>
<el-button @click="resetMoldSearch">{{ t('FactoryModeling.ProductInformation.resetButtonText') }}</el-button>
</el-form-item>
</el-form>
</template>
</TableSelectDialog>
</template>
<script setup lang="ts">
import { ProductApi, ProductVO } from '@/api/erp/product/product'
import { ProductCategoryApi, ProductCategoryVO } from '@/api/erp/product/category'
import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit'
import QrcodeActionCard from '@/components/QrcodeActionCard/index.vue'
import TableSelectDialog from '@/components/TableSelectDialog/TableSelectDialog.vue'
import { DeviceLedgerApi } from '@/api/mes/deviceledger'
import { MoldBrandApi } from '@/api/erp/mold'
import { CommonStatusEnum } from '@/utils/constants'
import { defaultProps, handleTree } from '@/utils/tree'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
@ -254,51 +147,10 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const deviceSelectDialogRef = ref()
const moldSelectDialogRef = ref()
const searchFormRef = ref()
// 1.
const searchParams = reactive({
deviceCode: undefined,
deviceName: undefined,
})
const handleSearch = () => {
//
deviceSelectDialogRef.value?.reload?.()
}
const resetSearch = () => {
searchFormRef.value?.resetFields()
handleSearch()
}
// 1.
const searchMoldParams = reactive({
code: undefined,
name: undefined,
})
const searchMoldFormRef = ref()
const handleMoldSearch = () => {
//
moldSelectDialogRef.value?.reload?.()
}
const resetMoldSearch = () => {
searchMoldFormRef.value?.resetFields()
handleSearch()
}
// 2.
const mergedQueryParams = computed(() => ({ ...searchParams }))
const mergedMoldQueryParams = computed(() => ({ ...searchMoldParams }))
const formData = ref({
id: undefined,
name: undefined,
barCode: undefined,
isCode: undefined,
qrcodeUrl: undefined,
templateJson: undefined,
categoryId: undefined,
unitId: undefined,
status: undefined,
@ -309,177 +161,11 @@ const formData = ref({
purchasePrice: undefined,
salePrice: undefined,
minPrice: undefined,
safetyNumber: undefined,
devices: [] as { id: number; name: string }[],
molds: [] as { id: number; name: string }[]
safetyNumber: undefined
})
const selectedDeviceRows = ref<any[]>([])
const selectedMoldRows = ref<any[]>([])
const findCategoryNameById = (id: number | undefined, tree: any[]): string => {
if (!id || !Array.isArray(tree) || !tree.length) return ''
const queue = [...tree]
while (queue.length) {
const node = queue.shift()
if (!node) continue
if (Number(node.id) === Number(id)) {
return String(node.name || node.label || '')
}
if (Array.isArray(node.children) && node.children.length) {
queue.push(...node.children)
}
}
return ''
}
const selectedCategoryName = computed(() => findCategoryNameById(formData.value.categoryId as any, categoryList.value))
const isProductCategory = computed(() => selectedCategoryName.value === '产品')
const getRelationName = (item: Record<string, any>, nameKeys: string[], fallbackPrefix: string, id: number) => {
for (const key of nameKeys) {
const value = item[key]
if (value !== undefined && value !== null && String(value).trim() !== '') {
return String(value)
}
}
return `${fallbackPrefix}ID:${id}`
}
const normalizeRelationList = (
value: unknown,
fallbackRows: any[] | undefined,
nameKeys: string[],
fallbackPrefix: string
): { id: number; name: string }[] => {
const fallbackMap = new Map<number, string>()
;(fallbackRows || []).forEach((row) => {
const id = Number(row?.id)
if (!Number.isFinite(id)) return
fallbackMap.set(id, getRelationName(row, nameKeys, fallbackPrefix, id))
})
const parseArray = (source: unknown): any[] => {
if (Array.isArray(source)) return source
if (typeof source === 'string') {
const content = source.trim()
if (!content) return []
try {
const parsed = JSON.parse(content.startsWith('[') ? content : `[${content}]`)
return Array.isArray(parsed) ? parsed : []
} catch {
return content.split(',').map((item) => item.trim()).filter(Boolean)
}
}
return []
}
return parseArray(value)
.map((item) => {
if (typeof item === 'object' && item !== null) {
const id = Number((item as any).id)
if (!Number.isFinite(id)) return undefined
return {
id,
name: getRelationName(item as any, nameKeys, fallbackPrefix, id)
}
}
const id = Number(item)
if (!Number.isFinite(id)) return undefined
return {
id,
name: fallbackMap.get(id) || `${fallbackPrefix}ID:${id}`
}
})
.filter((item): item is { id: number; name: string } => Boolean(item))
}
const toDeviceRows = (devices: { id: number; name: string }[]) => {
return devices.map((item) => ({
id: item.id,
deviceName: item.name,
name: item.name
}))
}
const toMoldRows = (molds: { id: number; name: string }[]) => {
return molds.map((item) => ({
id: item.id,
name: item.name
}))
}
const deviceDisplayText = computed(() => {
if (!formData.value.devices.length) return ''
if (!selectedDeviceRows.value.length) return formData.value.devices.map((item) => item.name).join('、')
return selectedDeviceRows.value.map((item) => item.deviceName || item.name || item.code || `ID:${item.id}`).join('、')
})
const moldDisplayText = computed(() => {
if (!formData.value.molds.length) return ''
if (!selectedMoldRows.value.length) return formData.value.molds.map((item) => item.name).join('、')
return selectedMoldRows.value.map((item) => item.name || item.code || `ID:${item.id}`).join('、')
})
const deviceColumns = [
{ label: '设备编号', prop: 'deviceCode', minWidth: 140 },
{ label: '设备名称', prop: 'deviceName', minWidth: 160 },
{ label: '设备型号', prop: 'deviceModel', minWidth: 140 },
{ label: '所属车间', prop: 'workshopName', minWidth: 140 }
]
const moldColumns = [
{ label: '模具编码', prop: 'code', minWidth: 140 },
{ label: '模具名称', prop: 'name', minWidth: 160 },
{ label: '模具型号', prop: 'brandName', minWidth: 140 },
{ label: '状态', prop: 'status', minWidth: 100 }
]
const fetchDeviceLedgerPage = (params: Record<string, any>) => {
return DeviceLedgerApi.getDeviceLedgerPage({
...params,
isScheduled: 1,
deviceStatus: 0
})
}
const openDeviceSelectDialog = () => {
const rows = selectedDeviceRows.value.length
? selectedDeviceRows.value.map((item) => ({ ...item, id: Number(item.id) }))
: toDeviceRows(formData.value.devices)
deviceSelectDialogRef.value?.open(rows)
}
const openMoldSelectDialog = () => {
const rows = selectedMoldRows.value.length
? selectedMoldRows.value.map((item) => ({ ...item, id: Number(item.id) }))
: toMoldRows(formData.value.molds)
moldSelectDialogRef.value?.open(rows)
}
const handleDeviceSelectConfirm = (payload: { ids: (number | string)[]; rows: any[] }) => {
formData.value.devices = payload.rows
.map((item) => {
const id = Number(item.id)
if (!Number.isFinite(id)) return undefined
return {
id,
name: item.deviceName || item.name || item.code || `设备ID:${id}`
}
})
.filter((item): item is { id: number; name: string } => Boolean(item))
selectedDeviceRows.value = payload.rows
}
const handleMoldSelectConfirm = (payload: { ids: (number | string)[]; rows: any[] }) => {
formData.value.molds = payload.rows
.map((item) => {
const id = Number(item.id)
if (!Number.isFinite(id)) return undefined
return {
id,
name: item.name || item.code || `模具ID:${id}`
}
})
.filter((item): item is { id: number; name: string } => Boolean(item))
selectedMoldRows.value = payload.rows
}
const validateBarCode = (_rule, value, callback) => {
if (Boolean(formData.value.isCode)) {
callback()
return
}
if (value === undefined || value === null || String(value).trim() === '') {
callback(new Error(t('FactoryModeling.ProductInformation.validatorBarCodeRequired')))
return
}
callback()
}
const formRules = reactive({
name: [{ required: true, message: t('FactoryModeling.ProductInformation.validatorNameRequired'), trigger: 'blur' }],
barCode: [{ validator: validateBarCode, trigger: ['blur', 'change'] }],
barCode: [{ required: true, message: t('FactoryModeling.ProductInformation.validatorBarCodeRequired'), trigger: 'blur' }],
categoryId: [{ required: true, message: t('FactoryModeling.ProductInformation.validatorCategoryRequired'), trigger: 'blur' }],
unitId: [{ required: true, message: t('FactoryModeling.ProductInformation.validatorUnitRequired'), trigger: 'blur' }],
status: [{ required: true, message: t('FactoryModeling.ProductInformation.validatorStatusRequired'), trigger: 'blur' }]
@ -494,84 +180,23 @@ const open = async (type: string, id?: number) => {
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
const categoryData = await ProductCategoryApi.getProductCategorySimpleList()
categoryList.value = handleTree(categoryData, 'id', 'parentId')
unitList.value = await ProductUnitApi.getProductUnitSimpleList()
//
if (id) {
formLoading.value = true
try {
const productData = await ProductApi.getProduct(id)
const devices = normalizeRelationList(
(productData as any).devices ?? (productData as any).deviceIds,
(productData as any).deviceList,
['name', 'deviceName', 'code'],
'设备'
)
const molds = normalizeRelationList(
(productData as any).molds ?? (productData as any).moldIds,
(productData as any).moldList,
['name', 'code'],
'模具'
)
const templateJson = productData?.templateJson
const parsedTemplateJson = typeof templateJson === 'string'
? JSON.parse(templateJson)
: templateJson
formData.value = {
...formData.value,
...productData,
templateJson: parsedTemplateJson,
devices,
molds
}
selectedDeviceRows.value = toDeviceRows(devices)
selectedMoldRows.value = toMoldRows(molds)
formData.value = await ProductApi.getProduct(id)
} finally {
formLoading.value = false
}
}
//
const categoryData = await ProductCategoryApi.getProductCategorySimpleList()
categoryList.value = handleTree(categoryData, 'id', 'parentId')
//
unitList.value = await ProductUnitApi.getProductUnitSimpleList()
}
defineExpose({ open }) // open
const handleCodeAutoChange = (value: boolean) => {
if (value) {
formData.value.barCode = undefined
}
formRef.value?.clearValidate('barCode')
}
const getQrcodeRefreshUrl = () => {
if (!formData.value.id || !formData.value.barCode) return ''
return `/erp/product/regenerate-code?id=${formData.value.id}&code=${encodeURIComponent(String(formData.value.barCode))}`
}
const buildPrintData = () => {
return {
id: formData.value.id,
name: formData.value.name,
barCode: formData.value.barCode,
standard: formData.value.standard,
remark: formData.value.remark,
qrcodeUrl: formData.value.qrcodeUrl
}
}
const handleQrcodeRefreshSuccess = async (data: any) => {
if (!formData.value.id) return
if (data?.qrcodeUrl) {
formData.value.qrcodeUrl = data.qrcodeUrl
return
}
const productData = await ProductApi.getProduct(formData.value.id)
formData.value.qrcodeUrl = productData?.qrcodeUrl
formData.value.barCode = productData?.barCode ?? formData.value.barCode
}
const buildRelationIdListString = (list: { id: number; name: string }[]) => {
return list.map((item) => Number(item.id)).filter((id) => Number.isFinite(id))
}
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
@ -580,15 +205,7 @@ const submitForm = async () => {
//
formLoading.value = true
try {
const relationDevices = isProductCategory.value ? formData.value.devices : []
const relationMolds = isProductCategory.value ? formData.value.molds : []
const data = {
...formData.value,
deviceIds: buildRelationIdListString(relationDevices),
moldIds: buildRelationIdListString(relationMolds)
} as unknown as ProductVO
delete (data as any).devices
delete (data as any).molds
const data = formData.value as unknown as ProductVO
if (formType.value === 'create') {
await ProductApi.createProduct(data)
message.success(t('common.createSuccess'))
@ -610,8 +227,6 @@ const resetForm = () => {
id: undefined,
name: undefined,
barCode: undefined,
isCode: true,
qrcodeUrl: undefined,
categoryId: undefined,
unitId: undefined,
status: CommonStatusEnum.ENABLE,
@ -621,25 +236,8 @@ const resetForm = () => {
weight: undefined,
purchasePrice: undefined,
salePrice: undefined,
minPrice: undefined,
devices: [],
molds: []
minPrice: undefined
}
selectedDeviceRows.value = []
selectedMoldRows.value = []
formRef.value?.resetFields()
}
watch(
() => [formData.value.categoryId, selectedCategoryName.value, categoryList.value.length],
() => {
if (!formData.value.categoryId || !categoryList.value.length) return
if (selectedCategoryName.value === '产品') return
formData.value.devices = []
formData.value.molds = []
selectedDeviceRows.value = []
selectedMoldRows.value = []
}
)
</script>
<style scoped lang="scss"></style>

@ -1,30 +1,43 @@
<!-- ERP 产品列表 -->
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="auto" @submit.prevent>
<el-form-item :label="t('FactoryModeling.ProductInformation.searchCodeLabel')" prop="barCode">
<el-input v-model="queryParams.barCode"
:placeholder="t('FactoryModeling.ProductInformation.searchCodePlaceholder')" clearable
@keyup.enter="handleQuery" class="!w-240px" />
</el-form-item>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="120px"
>
<el-form-item :label="t('FactoryModeling.ProductInformation.searchNameLabel')" prop="name">
<el-input v-model="queryParams.name"
:placeholder="t('FactoryModeling.ProductInformation.searchNamePlaceholder')" clearable
@keyup.enter="handleQuery" class="!w-240px" />
<el-input
v-model="queryParams.name"
:placeholder="t('FactoryModeling.ProductInformation.searchNamePlaceholder')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.searchButtonText') }}
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.resetButtonText') }}
</el-button>
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['erp:product:create']">
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.searchButtonText') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.resetButtonText') }}</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['erp:product:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.addButtonText') }}
</el-button>
<el-button type="success" plain @click="handleExport" :loading="exportLoading"
v-hasPermi="['erp:product:export']">
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['erp:product:export']"
>
<Icon icon="ep:download" class="mr-5px" /> {{ t('FactoryModeling.ProductInformation.exportButtonText') }}
</el-button>
</el-form-item>
@ -35,7 +48,12 @@
<ContentWrap>
<el-tabs v-model="activeName" @tab-click="handleTabClick">
<!-- 使用 v-for 动态生成 el-tab-pane -->
<el-tab-pane v-for="item in parentList" :key="item.id" :label="item.name" :name="item.id.toString()" />
<el-tab-pane
v-for="item in parentList"
:key="item.id"
:label="item.name"
:name="item.id.toString()"
/>
<!-- <el-tab-pane label="产品" name="2" />
<el-tab-pane label="原料" name="1" />
<el-tab-pane label="备件" name="5" />
@ -45,46 +63,60 @@
<el-tab-pane label="其他" name="0" /> -->
</el-tabs>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column :label="t('FactoryModeling.ProductInformation.tableBarCodeColumn')" align="center" prop="barCode"
sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableNameColumn')" align="left" prop="name"
width="220px" sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableStandardColumn')" align="center"
prop="standard" sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableCategoryColumn')" align="center"
prop="subCategoryName" sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableUnitColumn')" align="center" prop="unitName"
sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableStatusColumn')" align="center" prop="status"
sortable>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableBarCodeColumn')" align="center" prop="barCode" sortable/>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableNameColumn')" align="left" prop="name" width="220px" sortable/>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableStandardColumn')" align="center" prop="standard" sortable/>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableCategoryColumn')" align="center" prop="subCategoryName" sortable/>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableUnitColumn')" align="center" prop="unitName" sortable/>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableStatusColumn')" align="center" prop="status" sortable>
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableCreateTimeColumn')" align="center"
prop="createTime" :formatter="dateFormatter" width="180px" sortable />
<el-table-column
:label="t('FactoryModeling.ProductInformation.tableCreateTimeColumn')"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
sortable
/>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableOperateColumn')" align="center" width="150px">
<template #default="scope">
<!-- <el-button-->
<!-- v-if="scope.row.categoryId ===2"-->
<!-- link-->
<!-- type="primary"-->
<!-- @click="openBomForm('detail', scope.row.id)"-->
<!-- >-->
<!-- BOM-->
<!-- </el-button>-->
<el-button link type="primary" @click="openForm('update', scope.row.id)" v-hasPermi="['erp:product:update']">
<!-- <el-button-->
<!-- v-if="scope.row.categoryId ===2"-->
<!-- link-->
<!-- type="primary"-->
<!-- @click="openBomForm('detail', scope.row.id)"-->
<!-- >-->
<!-- BOM-->
<!-- </el-button>-->
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['erp:product:update']"
>
{{ t('FactoryModeling.ProductInformation.tableEditAction') }}
</el-button>
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['erp:product:delete']">
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['erp:product:delete']"
>
{{ t('FactoryModeling.ProductInformation.tableDeleteAction') }}
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
@pagination="getList" />
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
@ -117,7 +149,6 @@ const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
barCode: undefined,
categoryId: undefined
})
const queryFormRef = ref() //
@ -165,7 +196,7 @@ const handleDelete = async (id: number) => {
message.success(t('common.delSuccess'))
//
await getList()
} catch { }
} catch {}
}
/** 导出按钮操作 */

@ -1,144 +0,0 @@
<template>
<Dialog v-model="dialogVisible" :title="t('FactoryModeling.ProductUnit.import')" width="400">
<el-upload
ref="uploadRef"
v-model:file-list="fileList"
:action="importUrl + '?updateSupport=' + updateSupport"
:auto-upload="false"
:disabled="formLoading"
:headers="uploadHeaders"
:limit="1"
:on-error="submitFormError"
:on-exceed="handleExceed"
:on-success="submitFormSuccess"
accept=".xlsx, .xls"
drag
>
<Icon icon="ep:upload" />
<div class="el-upload__text">
{{ t('FactoryModeling.ProductUnit.importDragText') }}<em>{{ t('FactoryModeling.ProductUnit.importClickText') }}</em>
</div>
<template #tip>
<div class="el-upload__tip text-center">
<!-- <div class="el-upload__tip">
<el-checkbox v-model="updateSupport" />
{{ t('FactoryModeling.ProductUnit.importUpdateSupport') }}
</div>-->
<span>{{ t('FactoryModeling.ProductUnit.importFormatLimit') }}</span>
<el-link
:underline="false"
style="font-size: 12px; vertical-align: baseline"
type="primary"
@click="importTemplate"
>
{{ t('FactoryModeling.ProductUnit.importDownloadTemplate') }}
</el-link>
</div>
</template>
</el-upload>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm">{{ t('common.ok') }}</el-button>
<el-button @click="dialogVisible = false">{{ t('common.cancel') }}</el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import * as UserApi from '@/api/system/user'
import { getAccessToken, getTenantId } from '@/utils/auth'
import download from '@/utils/download'
import {ProductUnitApi} from "@/api/erp/product/unit";
defineOptions({ name: 'ProductUnitImportForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const uploadRef = ref()
const importUrl =
import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/erp/product-unit/import'
const uploadHeaders = ref() // Header
const fileList = ref([]) //
const updateSupport = ref(0) //
/** 打开弹窗 */
const open = () => {
dialogVisible.value = true
updateSupport.value = 0
fileList.value = []
resetForm()
}
defineExpose({ open }) // open
/** 提交表单 */
const submitForm = async () => {
if (fileList.value.length == 0) {
message.error(t('FactoryModeling.ProductUnit.importFileRequired'))
return
}
//
uploadHeaders.value = {
Authorization: 'Bearer ' + getAccessToken(),
'tenantId': getTenantId()
}
formLoading.value = true
uploadRef.value!.submit()
}
/** 文件上传成功 */
const emits = defineEmits(['success'])
const submitFormSuccess = (response: any) => {
if (response.code !== 0) {
message.error(response.msg)
formLoading.value = false
return
}
/* // 拼接提示语
const data = response.data
let text = t('FactoryModeling.ProductUnit.importSuccessTip', {
createCount: data.createUsernames.length,
updateCount: data.updateUsernames.length,
failureCount: Object.keys(data.failureUsernames).length
})
for (let username of data.createUsernames) {
text += '< ' + username + ' >'
}
for (const username of data.updateUsernames) {
text += '< ' + username + ' >'
}
for (const username in data.failureUsernames) {
text += '< ' + username + ': ' + data.failureUsernames[username] + ' >'
}*/
message.alert(response.data)
formLoading.value = false
dialogVisible.value = false
//
emits('success')
}
/** 上传错误提示 */
const submitFormError = (): void => {
message.error(t('FactoryModeling.ProductUnit.importFailed'))
formLoading.value = false
}
/** 重置表单 */
const resetForm = async (): Promise<void> => {
//
formLoading.value = false
await nextTick()
uploadRef.value?.clearFiles()
}
/** 文件数超出提示 */
const handleExceed = (): void => {
message.error(t('FactoryModeling.ProductUnit.importFileLimit'))
}
/** 下载模板操作 */
const importTemplate = async () => {
const res = await ProductUnitApi.importUserTemplate()
download.excel(res, '单位导入模版.xls')
}
</script>

@ -50,14 +50,6 @@
<Icon icon="ep:plus" class="mr-5px" />
{{ t('FactoryModeling.ProductUnit.addButtonText') }}
</el-button>
<el-button
type="warning"
plain
@click="handleImport"
v-hasPermi="['erp:product-unit:export']"
>
<Icon icon="ep:upload" /> {{ t('FactoryModeling.ProductUnit.importButtonText') }}
</el-button>
<el-button
type="success"
plain
@ -137,9 +129,6 @@
<!-- 表单弹窗添加/修改 -->
<ProductUnitForm ref="formRef" @success="getList" />
<!-- 单位导入对话框 -->
<ProductUnitImportForm ref="importFormRef" @success="getList" />
</template>
<script setup lang="ts">
@ -148,7 +137,7 @@ import download from '@/utils/download'
import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit'
import ProductUnitForm from './ProductUnitForm.vue'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import ProductUnitImportForm from './ProductUnitImportForm.vue'
/** ERP 产品单位列表 */
defineOptions({ name: 'ErpProductUnit' })
@ -210,12 +199,6 @@ const handleDelete = async (id: number) => {
} catch {}
}
/** 导入 */
const importFormRef = ref()
const handleImport = () => {
importFormRef.value.open()
}
/** 导出按钮操作 */
const handleExport = async () => {
try {

@ -8,7 +8,7 @@
:inline-message="true"
:disabled="disabled"
>
<el-table :data="formData" size="small" show-summary :summary-method="getSummaries" class="-mt-10px" >
<el-table :data="formData" show-summary :summary-method="getSummaries" class="-mt-10px">
<el-table-column :label="t('common.index')" type="index" align="center" width="60" />
<el-table-column :label="t('ErpStock.Item.warehouse')" min-width="125">
<template #default="{ row, $index }">
@ -34,31 +34,6 @@
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Item.barcode')" min-width="160">
<!-- <template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.productBarCode" />
</el-form-item>
</template>-->
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productBarCode`" class="mb-0px!">
<el-select
v-model="row.productBarCode"
clearable
filterable
@change="onChangeProductCode($event, row)"
:placeholder="t('ErpStock.Item.placeholderBarcode')"
>
<el-option
v-for="item in productList"
:key="item.barCode"
:label="item.barCode"
:value="item.barCode"
/>
</el-select>
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Item.product')" min-width="180">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productId`" :rules="formRules.productId" class="mb-0px!">
@ -83,7 +58,13 @@
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Item.barcode')" min-width="150">
<template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.productBarCode" />
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Item.unit')" min-width="80">
<template #default="{ row }">
<el-form-item class="mb-0px!">
@ -104,7 +85,7 @@
</el-form-item>
</template>
</el-table-column>
<!-- <el-table-column :label="t('ErpStock.Item.price')" fixed="right" min-width="120">
<el-table-column :label="t('ErpStock.Item.price')" fixed="right" min-width="120">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productPrice`" class="mb-0px!">
<el-input-number
@ -123,7 +104,7 @@
<el-input disabled v-model="row.totalPrice" :formatter="erpPriceInputFormatter" />
</el-form-item>
</template>
</el-table-column>-->
</el-table-column>
<el-table-column :label="t('ErpStock.Item.remark')" fixed="right" min-width="150">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.remark`" class="mb-0px!">
@ -269,19 +250,6 @@ const handleDelete = (index) => {
formData.value.splice(index, 1)
}
/** 处理产品变更 */
const onChangeProductCode = (productBarCode, row) => {
const product = productList.value.find((item) => item.barCode === productBarCode)
if (product) {
row.productUnitName = product.unitName
row.productBarCode = product.barCode
row.productPrice = product.minPrice
row.productId = product.id
}
//
setStockCount(row)
}
/** 处理仓库变更 */
const onChangeWarehouse = (warehouseId, row) => {
//

@ -60,7 +60,7 @@
/>
</el-select>
</el-form-item> -->
<el-form-item :label="t('ErpStock.In.warehouse')" prop="warehouseId" v-show="showAllFilters">
<el-form-item :label="t('ErpStock.In.warehouse')" prop="warehouseId">
<el-select
v-model="queryParams.warehouseId"
clearable
@ -76,7 +76,7 @@
/>
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.In.creator')" prop="creator" v-show="showAllFilters">
<el-form-item :label="t('ErpStock.In.creator')" prop="creator">
<el-select
v-model="queryParams.creator"
clearable
@ -92,7 +92,7 @@
/>
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.In.status')" prop="status" v-show="showAllFilters">
<el-form-item :label="t('ErpStock.In.status')" prop="status">
<el-select
v-model="queryParams.status"
:placeholder="t('ErpStock.In.placeholderStatus')"
@ -107,7 +107,7 @@
/>
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.In.remark')" prop="remark" v-show="showAllFilters">
<el-form-item :label="t('ErpStock.In.remark')" prop="remark">
<el-input
v-model="queryParams.remark"
:placeholder="t('ErpStock.In.placeholderRemark')"
@ -116,13 +116,6 @@
class="!w-240px"
/>
</el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') :
t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button>
@ -315,11 +308,6 @@ const queryParams = reactive({
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
const showAllFilters = ref(false)
const filterCount = 7
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const productList = ref<ProductVO[]>([]) //
const warehouseList = ref<WarehouseVO[]>([]) //
const supplierList = ref<SupplierVO[]>([]) //

@ -8,7 +8,7 @@
:inline-message="true"
:disabled="disabled"
>
<el-table :data="formData" size="small" show-summary :summary-method="getSummaries" class="-mt-10px">
<el-table :data="formData" show-summary :summary-method="getSummaries" class="-mt-10px">
<el-table-column :label="t('common.index')" type="index" align="center" width="60" />
<el-table-column :label="t('ErpStock.Item.warehouse')" min-width="125">
<template #default="{ row, $index }">
@ -34,26 +34,6 @@
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Item.barcode')" min-width="160">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productBarCode`" class="mb-0px!">
<el-select
v-model="row.productBarCode"
clearable
filterable
@change="onChangeProductCode($event, row)"
:placeholder="t('ErpStock.Item.placeholderBarcode')"
>
<el-option
v-for="item in productList"
:key="item.barCode"
:label="item.barCode"
:value="item.barCode"
/>
</el-select>
</el-form-item>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.Item.product')" min-width="180">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productId`" :rules="formRules.productId" class="mb-0px!">
@ -78,13 +58,13 @@
</el-form-item>
</template>
</el-table-column>
<!-- <el-table-column :label="t('ErpStock.Item.barcode')" min-width="150">
<el-table-column :label="t('ErpStock.Item.barcode')" min-width="150">
<template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.productBarCode" />
</el-form-item>
</template>
</el-table-column>-->
</el-table-column>
<el-table-column :label="t('ErpStock.Item.unit')" min-width="80">
<template #default="{ row }">
<el-form-item class="mb-0px!">
@ -105,7 +85,7 @@
</el-form-item>
</template>
</el-table-column>
<!-- <el-table-column :label="t('ErpStock.Item.price')" fixed="right" min-width="120">
<el-table-column :label="t('ErpStock.Item.price')" fixed="right" min-width="120">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productPrice`" class="mb-0px!">
<el-input-number
@ -124,7 +104,7 @@
<el-input disabled v-model="row.totalPrice" :formatter="erpPriceInputFormatter" />
</el-form-item>
</template>
</el-table-column>-->
</el-table-column>
<el-table-column :label="t('ErpStock.Item.remark')" fixed="right" min-width="150">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.remark`" class="mb-0px!">
@ -270,19 +250,6 @@ const handleDelete = (index) => {
formData.value.splice(index, 1)
}
/** 处理产品变更 */
const onChangeProductCode = (productBarCode, row) => {
const product = productList.value.find((item) => item.barCode === productBarCode)
if (product) {
row.productUnitName = product.unitName
row.productBarCode = product.barCode
row.productPrice = product.minPrice
row.productId = product.id
}
//
setStockCount(row)
}
/** 处理仓库变更 */
const onChangeWarehouse = (warehouseId, row) => {
//

@ -60,7 +60,7 @@
/>
</el-select>
</el-form-item> -->
<el-form-item :label="t('ErpStock.Out.warehouse')" prop="warehouseId" v-show="showAllFilters">
<el-form-item :label="t('ErpStock.Out.warehouse')" prop="warehouseId">
<el-select
v-model="queryParams.warehouseId"
clearable
@ -76,7 +76,7 @@
/>
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.In.creator')" prop="creator" v-show="showAllFilters">
<el-form-item :label="t('ErpStock.In.creator')" prop="creator">
<el-select
v-model="queryParams.creator"
clearable
@ -92,7 +92,7 @@
/>
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.In.status')" prop="status" v-show="showAllFilters">
<el-form-item :label="t('ErpStock.In.status')" prop="status">
<el-select
v-model="queryParams.status"
:placeholder="t('ErpStock.In.placeholderStatus')"
@ -107,7 +107,7 @@
/>
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.Out.remark')" prop="remark" v-show="showAllFilters">
<el-form-item :label="t('ErpStock.Out.remark')" prop="remark">
<el-input
v-model="queryParams.remark"
:placeholder="t('ErpStock.Out.placeholderRemark')"
@ -116,13 +116,6 @@
class="!w-240px"
/>
</el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') :
t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button>
@ -324,11 +317,6 @@ const queryParams = reactive({
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
const showAllFilters = ref(false)
const filterCount = 7
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const productList = ref<ProductVO[]>([]) //
const warehouseList = ref<WarehouseVO[]>([]) //
const customerList = ref<CustomerVO[]>([]) //
@ -432,7 +420,7 @@ onMounted(async () => {
// TODO
// TODO
/** tab 切换 */
let activeName = '领料出库'
let activeName = '产品出库'
const handleTabClick = (tab: TabsPaneContext) => {
queryParams.outType = tab.paneName
handleQuery()

@ -57,7 +57,7 @@
/>
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.Record.bizNo')" prop="bizNo" v-show="showAllFilters">
<el-form-item :label="t('ErpStock.Record.bizNo')" prop="bizNo">
<el-input
v-model="queryParams.bizNo"
:placeholder="t('ErpStock.Record.placeholderBizNo')"
@ -66,7 +66,7 @@
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('common.createTime')" prop="createTime" v-show="showAllFilters">
<el-form-item :label="t('common.createTime')" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
@ -77,13 +77,6 @@
class="!w-220px"
/>
</el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') :
t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button>
@ -207,11 +200,6 @@ const queryParams = reactive({
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
const showAllFilters = ref(false)
const filterCount = 5
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const productList = ref<ProductVO[]>([]) //
const warehouseList = ref<WarehouseVO[]>([]) //
const categoryTabs = ref<ProductCategoryVO[]>([])

@ -33,13 +33,6 @@
/>
</el-select>
</el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') :
t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button>
@ -124,13 +117,6 @@
>
{{ t('action.edit') }}
</el-button>
<el-button
link
type="info"
@click="openDetail(scope.row.id)"
>
{{ t('action.detail') }}
</el-button>
<el-button
link
type="danger"
@ -153,77 +139,6 @@
<!-- 表单弹窗添加/修改 -->
<WarehouseForm ref="formRef" @success="getList" />
<!-- 详情弹窗 -->
<Dialog :title="t('action.detail')" v-model="detailDialogVisible" width="1200px">
<div v-if="detailData">
<el-descriptions :column="2" border>
<el-descriptions-item :label="t('ErpStock.Warehouse.name')">{{ detailData.name }}</el-descriptions-item>
<el-descriptions-item :label="t('ErpStock.Warehouse.address')">{{ detailData.address }}</el-descriptions-item>
<el-descriptions-item :label="t('ErpStock.Warehouse.principal')">{{ detailData.principal }}</el-descriptions-item>
<el-descriptions-item :label="t('ErpStock.Warehouse.remark')">{{ detailData.remark || '-' }}</el-descriptions-item>
<el-descriptions-item :label="t('ErpStock.Warehouse.sort')">{{ detailData.sort }}</el-descriptions-item>
<el-descriptions-item :label="t('ErpStock.Warehouse.status')">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="detailData.status" />
</el-descriptions-item>
</el-descriptions>
<el-tabs v-model="activeTab" class="mt-4">
<el-tab-pane :label="t('ErpStock.WarehouseArea.areaName')" name="area">
<el-table :data="detailData.areaList || []" style="width: 100%">
<el-table-column :label="t('ErpStock.WarehouseArea.areaCode')" prop="areaCode" />
<el-table-column :label="t('ErpStock.WarehouseArea.areaName')" prop="areaName" />
<el-table-column :label="t('ErpStock.WarehouseArea.areaSize')" prop="areaSize" />
<el-table-column :label="t('ErpStock.WarehouseArea.description')" prop="description" />
<el-table-column :label="t('ErpStock.WarehouseArea.status')" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.WarehouseArea.createTime')" prop="createTime" :formatter="(row) => new Date(row.createTime).toLocaleString()" />
</el-table>
</el-tab-pane>
<el-tab-pane :label="t('ErpStock.WarehouseLocation.name')" name="location">
<el-table :data="detailData.locationList || []" style="width: 100%">
<el-table-column :label="t('ErpStock.WarehouseArea.areaName')">
<template #default="scope">
{{ getAreaName(scope.row.areaId) }}
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.WarehouseLocation.code')" prop="code" />
<el-table-column :label="t('ErpStock.WarehouseLocation.name')" prop="name" />
<el-table-column :label="t('ErpStock.WarehouseLocation.areaSize')" prop="areaSize" />
<el-table-column :label="t('ErpStock.WarehouseLocation.maxLoadWeight')" prop="maxLoadWeight" />
<el-table-column :label="t('ErpStock.WarehouseLocation.position')">
<template #default="scope">
{{ scope.row.positionX }}, {{ scope.row.positionY }}, {{ scope.row.positionZ }}
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.WarehouseLocation.allowProductMix')">
<template #default="scope">
<el-tag :type="scope.row.allowProductMix ? 'success' : 'danger'" size="small">
{{ scope.row.allowProductMix ? t('common.yes') : t('common.no') }}
</el-tag>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.WarehouseLocation.allowBatchMix')">
<template #default="scope">
<el-tag :type="scope.row.allowBatchMix ? 'success' : 'danger'" size="small">
{{ scope.row.allowBatchMix ? t('common.yes') : t('common.no') }}
</el-tag>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.WarehouseLocation.status')" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.WarehouseLocation.createTime')" prop="createTime" :formatter="(row) => new Date(row.createTime).toLocaleString()" />
</el-table>
</el-tab-pane>
</el-tabs>
</div>
</Dialog>
</template>
<script setup lang="ts">
@ -233,7 +148,6 @@ import download from '@/utils/download'
import { WarehouseApi, WarehouseVO } from '@/api/erp/stock/warehouse'
import WarehouseForm from './WarehouseForm.vue'
import { erpPriceTableColumnFormatter } from '@/utils'
import { Dialog } from '@/components/Dialog'
/** ERP 仓库列表 */
defineOptions({ name: 'ErpWarehouse' })
@ -252,14 +166,6 @@ const queryParams = reactive({
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
const showAllFilters = ref(false)
const filterCount = 2
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const detailDialogVisible = ref(false) //
const detailData = ref<WarehouseVO | null>(null) //
const activeTab = ref('area') // tab
/** 查询列表 */
const getList = async () => {
@ -341,22 +247,6 @@ const handleExport = async () => {
}
}
/** 打开详情弹窗 */
const openDetail = async (id: number) => {
try {
detailData.value = await WarehouseApi.getWarehouse(id)
detailDialogVisible.value = true
} catch (error) {
message.error(t('common.requestError'))
}
}
/** 根据库区ID获取库区名称 */
const getAreaName = (areaId: number) => {
if (!detailData.value?.areaList) return '-'
return detailData.value.areaList.find((area) => area.id === areaId)?.areaName || '-'
}
/** 选中操作 */
const selectionList = ref<WarehouseVO[]>([])
const handleSelectionChange = (rows: WarehouseVO[]) => {

@ -1,148 +0,0 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item :label="t('ErpStock.WarehouseArea.warehouseId')" prop="warehouseId">
<el-select
v-model="formData.warehouseId"
:placeholder="t('ErpStock.WarehouseArea.placeholderWarehouseId')"
class="!w-1/1"
>
<el-option
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseArea.areaCode')" prop="areaCode">
<el-row :gutter="10" class="!w-full">
<el-col :xs="24" :sm="18" :md="16" :lg="14" :xl="12">
<el-input v-model="formData.areaCode" :placeholder="t('ErpStock.WarehouseArea.placeholderAreaCode')" :disabled="formData.isCode == true || formType === 'update'" />
</el-col>
<el-col :xs="24" :sm="6" :md="4" :lg="3" :xl="2">
<div>
<el-switch v-model="formData.isCode" :disabled="formType === 'update'" />
</div>
</el-col>
</el-row>
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseArea.areaName')" prop="areaName">
<el-input v-model="formData.areaName" :placeholder="t('ErpStock.WarehouseArea.placeholderAreaName')" />
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseArea.areaSize')" prop="areaSize">
<el-input-number
v-model="formData.areaSize"
:placeholder="t('ErpStock.WarehouseArea.placeholderAreaSize')"
:min="0"
:precision="2"
class="!w-1/1"
/>
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseArea.description')" prop="description">
<el-input type="textarea" v-model="formData.description" :placeholder="t('ErpStock.WarehouseArea.placeholderDescription')" />
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseArea.status')" prop="status">
<el-switch v-model="formData.status" :active-value="CommonStatusEnum.ENABLE" :inactive-value="CommonStatusEnum.DISABLE" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">{{ t('common.ok') }}</el-button>
<el-button @click="dialogVisible = false">{{ t('common.cancel') }}</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { WarehouseAreaApi, WarehouseAreaVO } from '@/api/erp/stock/warehousearea'
import { WarehouseApi } from '@/api/erp/stock/warehouse'
import { CommonStatusEnum } from '@/utils/constants'
defineOptions({ name: 'WarehouseAreaForm' })
const { t } = useI18n()
const message = useMessage()
const dialogVisible = ref(false)
const dialogTitle = ref('')
const formLoading = ref(false)
const formType = ref('')
const formData = ref({
id: undefined,
warehouseId: undefined,
areaCode: undefined,
areaName: undefined,
areaSize: undefined,
description: undefined,
status: undefined,
isCode: true
})
const formRules = reactive({
warehouseId: [{ required: true, message: t('ErpStock.WarehouseArea.validatorWarehouseIdRequired'), trigger: 'blur' }],
areaName: [{ required: true, message: t('ErpStock.WarehouseArea.validatorAreaNameRequired'), trigger: 'blur' }],
status: [{ required: true, message: t('ErpStock.WarehouseArea.validatorStatusRequired'), trigger: 'blur' }]
})
const formRef = ref()
const warehouseList = ref<any[]>([])
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
warehouseList.value = await WarehouseApi.getWarehouseSimpleList()
if (id) {
formLoading.value = true
try {
formData.value = await WarehouseAreaApi.getWarehouseArea(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open })
const emit = defineEmits(['success'])
const submitForm = async () => {
await formRef.value.validate()
formLoading.value = true
try {
const data = { ...formData.value } as unknown as WarehouseAreaVO
if (data.isCode) {
data.areaCode = undefined
}
if (formType.value === 'create') {
await WarehouseAreaApi.createWarehouseArea(data)
message.success(t('common.createSuccess'))
} else {
await WarehouseAreaApi.updateWarehouseArea(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
emit('success')
} finally {
formLoading.value = false
}
}
const resetForm = () => {
formData.value = {
id: undefined,
warehouseId: undefined,
areaCode: undefined,
areaName: undefined,
areaSize: undefined,
description: undefined,
status: CommonStatusEnum.ENABLE,
isCode: true
}
formRef.value?.resetFields()
}
</script>

@ -1,213 +0,0 @@
<template>
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="auto"
>
<el-form-item :label="t('ErpStock.WarehouseArea.warehouseId')" prop="warehouseId">
<el-select
v-model="queryParams.warehouseId"
:placeholder="t('ErpStock.WarehouseArea.placeholderWarehouseId')"
clearable
class="!w-240px"
>
<el-option
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseArea.areaCode')" prop="areaCode">
<el-input
v-model="queryParams.areaCode"
:placeholder="t('ErpStock.WarehouseArea.placeholderAreaCode')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseArea.areaName')" prop="areaName">
<el-input
v-model="queryParams.areaName"
:placeholder="t('ErpStock.WarehouseArea.placeholderAreaName')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseArea.status')" prop="status" v-show="showAllFilters">
<el-select
v-model="queryParams.status"
:placeholder="t('ErpStock.WarehouseArea.placeholderStatus')"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') :
t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['erp:warehouse-area:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> {{ t('action.add') }}
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<ContentWrap>
<el-table
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
row-key="id"
>
<el-table-column :label="t('ErpStock.WarehouseArea.warehouseId')" align="center" prop="warehouseId" sortable>
<template #default="scope">
{{ getWarehouseName(scope.row.warehouseId) }}
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.WarehouseArea.areaCode')" align="center" prop="areaCode" sortable />
<el-table-column :label="t('ErpStock.WarehouseArea.areaName')" align="center" prop="areaName" sortable />
<el-table-column :label="t('ErpStock.WarehouseArea.areaSize')" align="center" prop="areaSize" />
<el-table-column :label="t('ErpStock.WarehouseArea.description')" align="center" prop="description" />
<el-table-column :label="t('ErpStock.WarehouseArea.status')" align="center" prop="status" sortable>
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.WarehouseArea.createTime')" align="center" prop="createTime" :formatter="dateFormatter" width="180px" sortable />
<el-table-column :label="t('common.operate')" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['erp:warehouse-area:update']"
>
{{ t('action.edit') }}
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['erp:warehouse-area:delete']"
>
{{ t('action.del') }}
</el-button>
</template>
</el-table-column>
</el-table>
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<WarehouseAreaForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { WarehouseAreaApi, WarehouseAreaVO } from '@/api/erp/stock/warehousearea'
import { WarehouseApi } from '@/api/erp/stock/warehouse'
import WarehouseAreaForm from './WarehouseAreaForm.vue'
defineOptions({ name: 'ErpWarehouseArea' })
const message = useMessage()
const { t } = useI18n()
const loading = ref(true)
const list = ref<WarehouseAreaVO[]>([])
const total = ref(0)
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
warehouseId: undefined,
areaCode: undefined,
areaName: undefined,
status: undefined
})
const queryFormRef = ref()
const showAllFilters = ref(false)
const filterCount = 4
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const warehouseList = ref<any[]>([])
const getWarehouseName = (warehouseId: number) => {
return warehouseList.value.find((w) => w.id === warehouseId)?.name ?? '-'
}
const getList = async () => {
loading.value = true
try {
const data = await WarehouseAreaApi.getWarehouseAreaPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
const getWarehouseList = async () => {
warehouseList.value = await WarehouseApi.getWarehouseSimpleList()
}
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
const handleDelete = async (id: number) => {
try {
await message.delConfirm()
await WarehouseAreaApi.deleteWarehouseArea(id)
message.success(t('common.delSuccess'))
await getList()
} catch {}
}
onMounted(() => {
getWarehouseList()
getList()
})
</script>

@ -1,227 +0,0 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
v-loading="formLoading"
>
<el-form-item :label="t('ErpStock.WarehouseLocation.warehouseId')" prop="warehouseId">
<el-select
v-model="formData.warehouseId"
:placeholder="t('ErpStock.WarehouseLocation.placeholderWarehouseId')"
class="!w-1/1"
@change="handleWarehouseChange"
>
<el-option
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseLocation.areaId')" prop="areaId">
<el-select
v-model="formData.areaId"
:placeholder="t('ErpStock.WarehouseLocation.placeholderAreaId')"
class="!w-1/1"
>
<el-option
v-for="item in filteredAreaList"
:key="item.id"
:label="item.areaName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseLocation.code')" prop="code">
<el-row :gutter="10" class="!w-full">
<el-col :xs="24" :sm="18" :md="16" :lg="14" :xl="12">
<el-input v-model="formData.code" :placeholder="t('ErpStock.WarehouseLocation.placeholderCode')" :disabled="formData.isCode == true || formType === 'update'" />
</el-col>
<el-col :xs="24" :sm="6" :md="4" :lg="3" :xl="2">
<div>
<el-switch v-model="formData.isCode" :disabled="formType === 'update'" />
</div>
</el-col>
</el-row>
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseLocation.name')" prop="name">
<el-input v-model="formData.name" :placeholder="t('ErpStock.WarehouseLocation.placeholderName')" />
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseLocation.areaSize')" prop="areaSize">
<el-input-number
v-model="formData.areaSize"
:placeholder="t('ErpStock.WarehouseLocation.placeholderAreaSize')"
:min="0"
:precision="2"
class="!w-1/1"
/>
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseLocation.maxLoadWeight')" prop="maxLoadWeight">
<el-input-number
v-model="formData.maxLoadWeight"
:placeholder="t('ErpStock.WarehouseLocation.placeholderMaxLoadWeight')"
:min="0"
:precision="2"
class="!w-1/1"
/>
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseLocation.positionX')" prop="positionX">
<el-input-number
v-model="formData.positionX"
:placeholder="t('ErpStock.WarehouseLocation.placeholderPositionX')"
:precision="0"
class="!w-1/1"
/>
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseLocation.positionY')" prop="positionY">
<el-input-number
v-model="formData.positionY"
:placeholder="t('ErpStock.WarehouseLocation.placeholderPositionY')"
:precision="0"
class="!w-1/1"
/>
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseLocation.positionZ')" prop="positionZ">
<el-input-number
v-model="formData.positionZ"
:placeholder="t('ErpStock.WarehouseLocation.placeholderPositionZ')"
:precision="0"
class="!w-1/1"
/>
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseLocation.allowProductMix')" prop="allowProductMix">
<el-switch v-model="formData.allowProductMix" />
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseLocation.allowBatchMix')" prop="allowBatchMix">
<el-switch v-model="formData.allowBatchMix" />
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseLocation.status')" prop="status">
<el-switch v-model="formData.status" :active-value="CommonStatusEnum.ENABLE" :inactive-value="CommonStatusEnum.DISABLE" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">{{ t('common.ok') }}</el-button>
<el-button @click="dialogVisible = false">{{ t('common.cancel') }}</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { WarehouseLocationApi, WarehouseLocationVO } from '@/api/erp/stock/warehouselocation'
import { WarehouseApi } from '@/api/erp/stock/warehouse'
import { WarehouseAreaApi } from '@/api/erp/stock/warehousearea'
import { CommonStatusEnum } from '@/utils/constants'
defineOptions({ name: 'WarehouseLocationForm' })
const { t } = useI18n()
const message = useMessage()
const dialogVisible = ref(false)
const dialogTitle = ref('')
const formLoading = ref(false)
const formType = ref('')
const formData = ref({
id: undefined,
warehouseId: undefined,
areaId: undefined,
code: undefined,
name: undefined,
areaSize: undefined,
maxLoadWeight: undefined,
positionX: undefined,
positionY: undefined,
positionZ: undefined,
allowProductMix: false,
allowBatchMix: false,
status: undefined,
isCode: true
})
const formRules = reactive({
warehouseId: [{ required: true, message: t('ErpStock.WarehouseLocation.validatorWarehouseIdRequired'), trigger: 'blur' }],
areaId: [{ required: true, message: t('ErpStock.WarehouseLocation.validatorAreaIdRequired'), trigger: 'blur' }],
name: [{ required: true, message: t('ErpStock.WarehouseLocation.validatorNameRequired'), trigger: 'blur' }],
allowProductMix: [{ required: true, message: t('ErpStock.WarehouseLocation.validatorAllowProductMixRequired'), trigger: 'blur' }],
allowBatchMix: [{ required: true, message: t('ErpStock.WarehouseLocation.validatorAllowBatchMixRequired'), trigger: 'blur' }],
status: [{ required: true, message: t('ErpStock.WarehouseLocation.validatorStatusRequired'), trigger: 'blur' }]
})
const formRef = ref()
const warehouseList = ref<any[]>([])
const areaList = ref<any[]>([])
const filteredAreaList = computed(() => {
if (!formData.value.warehouseId) return areaList.value
return areaList.value.filter((item) => item.warehouseId === formData.value.warehouseId)
})
const handleWarehouseChange = () => {
formData.value.areaId = undefined
}
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
warehouseList.value = await WarehouseApi.getWarehouseSimpleList()
const areaData = await WarehouseAreaApi.getWarehouseAreaPage({ pageNo: 1, pageSize: 100 })
areaList.value = areaData.list ?? []
if (id) {
formLoading.value = true
try {
formData.value = await WarehouseLocationApi.getWarehouseLocation(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open })
const emit = defineEmits(['success'])
const submitForm = async () => {
await formRef.value.validate()
formLoading.value = true
try {
const data = { ...formData.value } as unknown as WarehouseLocationVO
if (data.isCode) {
data.code = undefined
}
if (formType.value === 'create') {
await WarehouseLocationApi.createWarehouseLocation(data)
message.success(t('common.createSuccess'))
} else {
await WarehouseLocationApi.updateWarehouseLocation(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
emit('success')
} finally {
formLoading.value = false
}
}
const resetForm = () => {
formData.value = {
id: undefined,
warehouseId: undefined,
areaId: undefined,
code: undefined,
name: undefined,
areaSize: undefined,
maxLoadWeight: undefined,
positionX: undefined,
positionY: undefined,
positionZ: undefined,
allowProductMix: false,
allowBatchMix: false,
status: CommonStatusEnum.ENABLE,
isCode: true
}
formRef.value?.resetFields()
}
</script>

@ -1,275 +0,0 @@
<template>
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="auto"
>
<el-form-item :label="t('ErpStock.WarehouseLocation.warehouseId')" prop="warehouseId">
<el-select
v-model="queryParams.warehouseId"
:placeholder="t('ErpStock.WarehouseLocation.placeholderWarehouseId')"
clearable
class="!w-240px"
@change="handleWarehouseChange"
>
<el-option
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseLocation.areaId')" prop="areaId">
<el-select
v-model="queryParams.areaId"
:placeholder="t('ErpStock.WarehouseLocation.placeholderAreaId')"
clearable
class="!w-240px"
>
<el-option
v-for="item in filteredAreaList"
:key="item.id"
:label="item.areaName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseLocation.code')" prop="code">
<el-input
v-model="queryParams.code"
:placeholder="t('ErpStock.WarehouseLocation.placeholderCode')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseLocation.name')" prop="name" v-show="showAllFilters">
<el-input
v-model="queryParams.name"
:placeholder="t('ErpStock.WarehouseLocation.placeholderName')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('ErpStock.WarehouseLocation.status')" prop="status" v-show="showAllFilters">
<el-select
v-model="queryParams.status"
:placeholder="t('ErpStock.WarehouseLocation.placeholderStatus')"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') :
t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['erp:warehouse-location:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> {{ t('action.add') }}
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<ContentWrap>
<el-table
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
row-key="id"
>
<el-table-column :label="t('ErpStock.WarehouseLocation.warehouseId')" align="center" prop="warehouseId" sortable>
<template #default="scope">
{{ getWarehouseName(scope.row.warehouseId) }}
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.WarehouseLocation.areaId')" align="center" prop="areaId" sortable>
<template #default="scope">
{{ getAreaName(scope.row.areaId) }}
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.WarehouseLocation.code')" align="center" prop="code" sortable />
<el-table-column :label="t('ErpStock.WarehouseLocation.name')" align="center" prop="name" sortable />
<el-table-column :label="t('ErpStock.WarehouseLocation.areaSize')" align="center" prop="areaSize" />
<el-table-column :label="t('ErpStock.WarehouseLocation.maxLoadWeight')" align="center" prop="maxLoadWeight" />
<el-table-column :label="t('ErpStock.WarehouseLocation.position')" align="center">
<template #default="scope">
{{ scope.row.positionX ?? '-' }}, {{ scope.row.positionY ?? '-' }}, {{ scope.row.positionZ ?? '-' }}
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.WarehouseLocation.allowProductMix')" align="center" prop="allowProductMix">
<template #default="scope">
<el-tag :type="scope.row.allowProductMix ? 'success' : 'danger'" size="small">
{{ scope.row.allowProductMix ? t('common.yes') : t('common.no') }}
</el-tag>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.WarehouseLocation.allowBatchMix')" align="center" prop="allowBatchMix">
<template #default="scope">
<el-tag :type="scope.row.allowBatchMix ? 'success' : 'danger'" size="small">
{{ scope.row.allowBatchMix ? t('common.yes') : t('common.no') }}
</el-tag>
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.WarehouseLocation.status')" align="center" prop="status" sortable>
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column :label="t('ErpStock.WarehouseLocation.createTime')" align="center" prop="createTime" :formatter="dateFormatter" width="180px" sortable />
<el-table-column :label="t('common.operate')" align="center" width="150px">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['erp:warehouse-location:update']"
>
{{ t('action.edit') }}
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['erp:warehouse-location:delete']"
>
{{ t('action.del') }}
</el-button>
</template>
</el-table-column>
</el-table>
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<WarehouseLocationForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { WarehouseLocationApi, WarehouseLocationVO } from '@/api/erp/stock/warehouselocation'
import { WarehouseApi } from '@/api/erp/stock/warehouse'
import { WarehouseAreaApi } from '@/api/erp/stock/warehousearea'
import WarehouseLocationForm from './WarehouseLocationForm.vue'
defineOptions({ name: 'ErpWarehouseLocation' })
const message = useMessage()
const { t } = useI18n()
const loading = ref(true)
const list = ref<WarehouseLocationVO[]>([])
const total = ref(0)
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
warehouseId: undefined,
areaId: undefined,
code: undefined,
name: undefined,
status: undefined
})
const queryFormRef = ref()
const showAllFilters = ref(false)
const filterCount = 5
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const warehouseList = ref<any[]>([])
const areaList = ref<any[]>([])
const filteredAreaList = computed(() => {
if (!queryParams.warehouseId) return areaList.value
return areaList.value.filter((item) => item.warehouseId === queryParams.warehouseId)
})
const getWarehouseName = (warehouseId: number) => {
return warehouseList.value.find((w) => w.id === warehouseId)?.name ?? '-'
}
const getAreaName = (areaId: number) => {
return areaList.value.find((a) => a.id === areaId)?.areaName ?? '-'
}
const getList = async () => {
loading.value = true
try {
const data = await WarehouseLocationApi.getWarehouseLocationPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
const getWarehouseList = async () => {
warehouseList.value = await WarehouseApi.getWarehouseSimpleList()
}
const getAreaList = async () => {
const data = await WarehouseAreaApi.getWarehouseAreaPage({ pageNo: 1, pageSize: 100 })
areaList.value = data.list ?? []
}
const handleWarehouseChange = () => {
queryParams.areaId = undefined
}
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
const handleDelete = async (id: number) => {
try {
await message.delConfirm()
await WarehouseLocationApi.deleteWarehouseLocation(id)
message.success(t('common.delSuccess'))
await getList()
} catch {}
}
onMounted(() => {
getWarehouseList()
getAreaList()
getList()
})
</script>

@ -43,9 +43,6 @@
<el-option v-for="item in modelList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item :label="t('EquipmentManagement.EquipmentLedger.images')" prop="images">
<UploadImg style="height: 100px" v-model="formData.images" />
</el-form-item>
</template>
<!-- <el-form-item label="设备类型" prop="deviceType">
<el-select v-model="formData.deviceType" placeholder="请选择设备类型">
@ -137,7 +134,6 @@ const formData = ref({
username: undefined,
password: undefined,
topic: undefined,
images: undefined,
})
const formRules = reactive({
create: {
@ -192,7 +188,7 @@ const submitForm = async () => {
//
formLoading.value = true
try {
const { id, deviceCode, deviceName, deviceModelId, sampleCycle, remark, isEnable, topic,images } = formData.value as any
const { id, deviceCode, deviceName, deviceModelId, sampleCycle, remark, isEnable, topic } = formData.value as any
if (formType.value === 'create') {
const data: Partial<DeviceVO> = {
@ -201,17 +197,16 @@ const submitForm = async () => {
deviceModelId,
sampleCycle,
remark,
isEnable: false,
images
isEnable: false
}
await DeviceApi.createDevice(data as DeviceVO)
message.success(t('common.createSuccess'))
} else if (formType.value === 'update') {
const data: any = { id, deviceCode, deviceName, deviceModelId, sampleCycle, isEnable,images}
const data: any = { id, deviceCode, deviceName, deviceModelId, sampleCycle, isEnable }
await DeviceApi.updateDevice(data)
message.success(t('common.updateSuccess'))
} else {
const data: any = { id, deviceCode, deviceName, deviceModelId, isEnable, topic,images}
const data: any = { id, deviceCode, deviceName, deviceModelId, isEnable, topic }
await DeviceApi.updateDevice(data)
message.success(t('common.updateSuccess'))
}
@ -251,17 +246,3 @@ const resetForm = () => {
/** 初始化 **/
</script>
<style scoped>
:deep(.upload-box[data-v-57417a1d] .upload .el-upload .el-upload-dragger ){
display: flex;
align-items: center;
justify-content: center;
width: 70%;
height: 70%;
padding: 0;
overflow: hidden;
background-color: transparent;
border: 1px dashed var(--el-border-color-darker);
border-radius: var(--57417a1d-borderradius);
}
</style>

File diff suppressed because it is too large Load Diff

@ -1,49 +1,83 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px" v-loading="formLoading">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
v-loading="formLoading"
>
<el-form-item :label="t('DataCollection.Device.attributeCode')" prop="attributeCode">
<el-input
v-model="formData.attributeCode" :placeholder="t('DataCollection.Device.placeholderAttributeCode')"
@input="handleAttributeCodeInput" :disabled="formType === 'update'" />
v-model="formData.attributeCode"
:placeholder="t('DataCollection.Device.placeholderAttributeCode')"
@input="handleAttributeCodeInput"
:disabled = "formType === 'update'"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.attributeName')" prop="attributeName">
<el-input v-model="formData.attributeName" :placeholder="t('DataCollection.Device.placeholderAttributeName')" />
<el-input
v-model="formData.attributeName"
:placeholder="t('DataCollection.Device.placeholderAttributeName')"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.attributeType')" prop="attributeType">
<el-select
v-model="formData.attributeType" clearable filterable
:placeholder="t('DataCollection.Device.placeholderAttributeType')" @change="handleAttributeTypeChange">
v-model="formData.attributeType"
clearable
filterable
:placeholder="t('DataCollection.Device.placeholderAttributeType')"
@change="handleAttributeTypeChange"
>
<el-option v-for="item in typeList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.dataType')" prop="dataType">
<el-select
v-model="formData.dataType" :placeholder="t('DataCollection.Device.placeholderDataType')"
:disabled="formType === 'update'">
v-model="formData.dataType"
:placeholder="t('DataCollection.Device.placeholderDataType')"
>
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.IOT_DEVICE_DATA_TYPE)" :key="dict.value"
:label="dict.label" :value="dict.value" />
v-for="dict in getStrDictOptions(DICT_TYPE.IOT_DEVICE_DATA_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.address')" prop="address">
<el-input v-model="formData.address" :placeholder="t('DataCollection.Device.placeholderAddress')" />
<el-input
v-model="formData.address"
:placeholder="t('DataCollection.Device.placeholderAddress')"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.dataUnit')" prop="dataUnit">
<el-select
v-model="formData.dataUnit" clearable
:placeholder="t('DataCollection.DeviceModel.placeholderDataUnit')" class="w-1/1">
<el-option v-for="unit in unitList" :key="unit.id" :label="unit.name" :value="unit.id" />
</el-select>
<el-select v-model="formData.dataUnit" clearable :placeholder="t('DataCollection.DeviceModel.placeholderDataUnit')" class="w-1/1">
<el-option
v-for="unit in unitList"
:key="unit.id"
:label="unit.name"
:value="unit.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.ratio')" prop="ratio">
<el-input-number
v-model="formData.ratio" :placeholder="t('DataCollection.Device.placeholderRatio')" :min="0.00"
:decision="2" :step="0.01" class="!w-full" :disabled="!ratioEnabled" />
v-model="formData.ratio"
:placeholder="t('DataCollection.Device.placeholderRatio')"
:min="0.00"
:decision="2"
:step="0.01"
class="!w-full"
:disabled = "!ratioEnabled"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.remark')" prop="remark">
<el-input
v-model="formData.remark" :placeholder="t('DataCollection.Device.placeholderRemark')"
type="textarea" />
v-model="formData.remark"
:placeholder="t('DataCollection.Device.placeholderRemark')"
type="textarea"
/>
</el-form-item>
</el-form>
<template #footer>
@ -123,8 +157,7 @@ watch(
)
const handleAttributeCodeInput = (val: string) => {
const sanitized = (val || '').replace(/[^A-Za-z0-9_]/g, '')
formData.value.attributeCode = sanitized.replace(/^[0-9]+/, '')
formData.value.attributeCode = val?.replace(/[\u4e00-\u9fa5]/g, '')
}
@ -148,10 +181,6 @@ const formRules = reactive({
callback(new Error(t('DataCollection.Device.attributeValidatorCodeNoChinese')))
return
}
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
callback(new Error('仅支持英文字母、数字和下划线,且必须以字母或下划线开头'))
return
}
callback()
},
trigger: ['blur', 'change']
@ -227,7 +256,7 @@ const open = async (type: string, id?: number, deviceId: number) => {
try {
formData.value = await DeviceApi.getDeviceAttribute(id)
if (!(formData.value as any)?.deviceId) {
; (formData.value as any).deviceId = deviceId
;(formData.value as any).deviceId = deviceId
}
const currentType = (formData.value as any)?.attributeType
@ -240,8 +269,8 @@ const open = async (type: string, id?: number, deviceId: number) => {
item.code === currentType
)
if (matched) {
; (formData.value as any).attributeType = matched.id
; (formData.value as any).typeName = matched.name
;(formData.value as any).attributeType = matched.id
;(formData.value as any).typeName = matched.name
}
}
} finally {

@ -1,905 +0,0 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1200px" :show-close="false">
<template v-if="attributeDeviceId">
<div class="mb-10px flex items-center justify-between text-sm text-gray-500">
<div>
{{ t('DataCollection.Device.currentDeviceLabel') }}<span class="font-medium text-gray-700">{{ attributeDeviceName || '-' }}</span>
</div>
<div>
<el-button type="primary" link @click="handleShowDeviceAlarmHistory">
{{ t('DataCollection.Device.alarmHistoryTitle') }}
</el-button>
</div>
</div>
<el-tabs v-model="deviceTabActive" :onclick="changeClick">
<el-tab-pane :label="deviceAttributeTabLabel" name="deviceAttribute">
<DeviceAttributeList :device-id="attributeDeviceId" />
</el-tab-pane>
<el-tab-pane :label="deviceRuleTabLabel" name="deviceRule">
<el-form
class="-mb-15px"
:model="ruleQueryParams"
ref="ruleQueryFormRef"
:inline="true"
label-width="120px"
>
<el-form-item :label="t('DataCollection.DeviceModel.ruleIdentifier')" prop="identifier">
<el-input
v-model="ruleQueryParams.identifier"
:placeholder="t('DataCollection.DeviceModel.ruleSearchIdentifierPlaceholder')"
clearable
@keyup.enter="handleRuleQuery"
class="!w-200px"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.DeviceModel.ruleFieldName')" prop="fieldName">
<el-input
v-model="ruleQueryParams.fieldName"
:placeholder="t('DataCollection.DeviceModel.ruleSearchFieldNamePlaceholder')"
clearable
@keyup.enter="handleRuleQuery"
class="!w-200px"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.DeviceModel.ruleDefaultValue')" prop="defaultValue">
<el-input
v-model="ruleQueryParams.defaultValue"
:placeholder="t('DataCollection.DeviceModel.ruleSearchDefaultValuePlaceholder')"
clearable
@keyup.enter="handleRuleQuery"
class="!w-200px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleRuleQuery">
<Icon icon="ep:search" class="mr-5px" /> {{ t('DataCollection.DeviceModel.ruleSearch') }}
</el-button>
<el-button @click="resetRuleQuery">
<Icon icon="ep:refresh" class="mr-5px" /> {{ t('DataCollection.DeviceModel.ruleReset') }}
</el-button>
</el-form-item>
</el-form>
<div class="mb-10px text-right">
<el-button type="primary" @click="openCreateRuleForm">
{{ t('DataCollection.DeviceModel.ruleCreateButton') }}
</el-button>
</div>
<el-table
v-loading="ruleLoading"
:data="ruleList"
:stripe="true"
:show-overflow-tooltip="true"
row-key="id"
>
<el-table-column :label="t('DataCollection.DeviceModel.ruleIdentifier')" align="center" prop="identifier" />
<el-table-column :label="t('DataCollection.DeviceModel.ruleFieldName')" align="center" prop="fieldName" sortable />
<el-table-column :label="t('DataCollection.DeviceModel.ruleFieldRule')" align="center" prop="fieldRule" />
<el-table-column :label="t('DataCollection.DeviceModel.ruleDefaultValue')" align="center" prop="defaultValue" />
<el-table-column
:label="t('DataCollection.DeviceModel.ruleCreateTime')"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
sortable
/>
<el-table-column :label="t('DataCollection.DeviceModel.ruleOperate')" align="center" width="160px">
<template #default="scope">
<el-button link type="primary" @click="openRuleForm(scope.row)">
{{ t('DataCollection.DeviceModel.ruleEditRuleButton') }}
</el-button>
<el-button
v-if="(scope.row.identifier || '').toString().toUpperCase() === 'ALARM'"
link
type="danger"
@click="handleRuleDelete(scope.row.id)"
>
{{ t('DataCollection.DeviceModel.ruleDeleteRuleButton') }}
</el-button>
</template>
</el-table-column>
</el-table>
<Pagination
:total="ruleTotal"
v-model:page="ruleQueryParams.pageNo"
v-model:limit="ruleQueryParams.pageSize"
@pagination="getRuleList"
/>
<el-dialog v-model="ruleDialogVisible" :title="t('DataCollection.DeviceModel.ruleDialogTitle')" width="880px" draggable>
<el-form :model="ruleForm" ref="ruleFormRef" label-width="120px">
<el-form-item :label="t('DataCollection.DeviceModel.ruleDialogIdentifier')">
<el-input v-model="ruleForm.identifier" disabled />
</el-form-item>
<el-form-item :label="t('DataCollection.DeviceModel.ruleDialogFieldName')">
<el-input v-model="ruleForm.fieldName" disabled />
</el-form-item>
<el-form-item :label="t('DataCollection.DeviceModel.ruleDialogDefaultValue')">
<el-input v-model="ruleForm.defaultValue" disabled />
</el-form-item>
<el-form-item
v-if="(ruleForm.identifier || '').toString().toUpperCase() === 'ALARM'"
:label="t('DataCollection.DeviceModel.ruleDialogAlarmLevel')"
>
<el-select
v-model="ruleForm.alarmLevel"
:placeholder="t('DataCollection.DeviceModel.ruleDialogAlarmLevelPlaceholder')"
>
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.IOT_ALARM_REGISTRATION)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<div class="flex flex-col w-full">
<div class="border border-gray-200 dark:border-gray-600 rounded-md py-12px">
<el-form-item :label="t('DataCollection.DeviceModel.ruleDialogFieldRule')">
<el-select
v-model="ruleForm.fieldRule"
:placeholder="t('DataCollection.DeviceModel.ruleDialogFieldRulePlaceholder')"
class="!w-240px"
:disabled="!currentRuleOptions.length"
>
<el-option
v-for="item in currentRuleOptions"
:key="item.value"
:label="item.label"
:value="item.value"
:disabled="isRuleDisabled(item.value, ruleForm.fieldRule)"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('DataCollection.DeviceModel.ruleDialogRule')">
<div class="flex items-center gap-8px">
<el-select
v-model="ruleForm.ruleAttributeId"
:placeholder="t('DataCollection.DeviceModel.ruleDialogRuleAttributePlaceholder')"
class="!w-240px"
>
<el-option
v-for="item in ruleAttributeOptions"
:key="item.id"
:label="item.attributeName || item.attributeCode"
:value="item.id"
/>
</el-select>
<el-select
v-model="ruleForm.ruleOperator"
:placeholder="t('DataCollection.DeviceModel.ruleDialogRuleOperatorPlaceholder')"
class="!w-160px"
>
<el-option
v-for="item in ruleOperatorOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input
v-if="ruleForm.ruleOperator !== 'TRUE' && ruleForm.ruleOperator !== 'FALSE'"
v-model="ruleForm.ruleValue"
:placeholder="t('DataCollection.DeviceModel.ruleDialogRuleValuePlaceholder')"
class="!w-200px"
/>
</div>
</el-form-item>
</div>
<div
v-for="(item, index) in extraPointRules"
:key="index"
class="border border-gray-200 dark:border-gray-600 rounded-md px-16px py-12px"
>
<el-form-item :label="t('DataCollection.DeviceModel.ruleDialogFieldRule')">
<el-select
v-model="item.rule"
:placeholder="t('DataCollection.DeviceModel.ruleDialogFieldRulePlaceholder')"
class="!w-240px"
>
<el-option
v-for="opt in currentRuleOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
:disabled="isRuleDisabled(opt.value, item.rule)"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('DataCollection.DeviceModel.ruleDialogRule')">
<div class="flex items-center gap-8px">
<el-select
v-model="item.id"
:placeholder="t('DataCollection.DeviceModel.ruleDialogRuleAttributePlaceholder')"
class="!w-240px"
>
<el-option
v-for="attr in ruleAttributeOptions"
:key="attr.id"
:label="attr.attributeName || attr.attributeCode"
:value="attr.id"
/>
</el-select>
<el-select
v-model="item.operator"
:placeholder="t('DataCollection.DeviceModel.ruleDialogRuleOperatorPlaceholder')"
class="!w-160px"
>
<el-option
v-for="opt in ruleOperatorOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
<el-input
v-if="item.operator !== 'TRUE' && item.operator !== 'FALSE'"
v-model="item.operatorRule"
:placeholder="t('DataCollection.DeviceModel.ruleDialogRuleValuePlaceholder')"
class="!w-200px"
/>
<el-button type="danger" link @click="handleRemovePointRule(index)">
{{ t('DataCollection.DeviceModel.ruleDeleteRuleButton') }}
</el-button>
</div>
</el-form-item>
</div>
<el-form-item v-if="isRunningIdentifier" label=" ">
<el-button type="primary" link @click="handleAddPointRule">
+ {{ t('DataCollection.DeviceModel.ruleCreateButton') }}
</el-button>
</el-form-item>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="ruleDialogVisible = false">{{ t('DataCollection.DeviceModel.dialogCancel') }}</el-button>
<el-button type="primary" :loading="ruleFormLoading" @click="handleRuleSubmit">{{ t('DataCollection.DeviceModel.dialogOk') }}</el-button>
</span>
</template>
</el-dialog>
<el-dialog v-model="createRuleDialogVisible" :title="t('DataCollection.DeviceModel.ruleCreateButton')" width="520px" draggable>
<el-form :model="createRuleForm" ref="createRuleFormRef" label-width="120px">
<el-form-item :label="t('DataCollection.DeviceModel.ruleDialogIdentifier')">
<el-input v-model="createRuleForm.identifier" disabled />
</el-form-item>
<el-form-item :label="t('DataCollection.DeviceModel.ruleDialogFieldName')">
<el-input
v-model="createRuleForm.fieldName"
:placeholder="t('DataCollection.DeviceModel.ruleSearchFieldNamePlaceholder')"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.DeviceModel.ruleDialogDefaultValue')">
<el-input v-model="createRuleForm.defaultValue" disabled />
</el-form-item>
<el-form-item :label="t('DataCollection.DeviceModel.ruleDialogAlarmLevel')">
<el-select
v-model="createRuleForm.alarmLevel"
:placeholder="t('DataCollection.DeviceModel.ruleDialogAlarmLevelPlaceholder')"
>
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.IOT_ALARM_REGISTRATION)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="createRuleDialogVisible = false">{{ t('DataCollection.DeviceModel.dialogCancel') }}</el-button>
<el-button type="primary" :loading="createRuleFormLoading" @click="handleCreateRuleSubmit">
{{ t('DataCollection.DeviceModel.dialogOk') }}
</el-button>
</span>
</template>
</el-dialog>
</el-tab-pane>
</el-tabs>
</template>
<el-empty v-else :description="t('DataCollection.Device.emptyDescription')" />
</Dialog>
<Dialog :title="t('DataCollection.Device.alarmHistoryTitle')" v-model="deviceAlarmDialogVisible" width="1200px">
<el-form
class="-mb-15px"
:model="deviceAlarmQueryParams"
ref="deviceAlarmQueryFormRef"
:inline="true"
label-width="80px"
>
<el-form-item :label="t('DataCollection.Device.alarmRuleName')" prop="ruleId">
<el-select
v-model="deviceAlarmQueryParams.ruleId"
:loading="deviceAlarmRuleLoading"
clearable
filterable
class="!w-220px"
:placeholder="t('DataCollection.DeviceModel.ruleSearchFieldNamePlaceholder')"
>
<el-option
v-for="item in deviceAlarmRuleOptions"
:key="item.id"
:label="item.fieldName || item.ruleName || item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.alarmPointName')" prop="modelId">
<el-select
v-model="deviceAlarmQueryParams.modelId"
:loading="deviceAlarmPointLoading"
clearable
filterable
class="!w-220px"
:placeholder="t('DataCollection.Device.placeholderAttributeName')"
>
<el-option
v-for="item in deviceAlarmPointOptions"
:key="item.id"
:label="item.attributeName || item.attributeCode || item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.alarmTime')" prop="createTime">
<el-date-picker
v-model="deviceAlarmQueryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
:start-placeholder="t('DataCollection.HistoryData.dialogCollectionTimeStartPlaceholder')"
:end-placeholder="t('DataCollection.HistoryData.dialogCollectionTimeEndPlaceholder')"
class="!w-260px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleDeviceAlarmQuery">
<Icon icon="ep:search" class="mr-5px" />
{{ t('DataCollection.Device.search') }}
</el-button>
<el-button @click="resetDeviceAlarmQuery">
<Icon icon="ep:refresh" class="mr-5px" />
{{ t('DataCollection.Device.reset') }}
</el-button>
</el-form-item>
</el-form>
<el-table
:data="deviceAlarmList"
v-loading="deviceAlarmLoading"
:stripe="true"
:show-overflow-tooltip="true"
:max-height="700"
class="mt-10px"
>
<el-table-column :label="t('DataCollection.Device.alarmRuleName')" align="center" prop="ruleName" sortable />
<el-table-column :label="t('DataCollection.Device.alarmPointName')" align="center" prop="modelName" sortable />
<el-table-column :label="t('DataCollection.Device.alarmPointValue')" align="center" prop="addressValue" />
<el-table-column :label="t('DataCollection.Device.alarmLevel')" align="center" prop="alarmLevel">
<template #default="scope">
<dict-tag :type="DICT_TYPE.IOT_ALARM_REGISTRATION" :value="scope.row.alarmLevel" />
</template>
</el-table-column>
<el-table-column
:label="t('DataCollection.Device.alarmTime')"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
sortable />
</el-table>
<div class="mt-10px pb-10px flex justify-end">
<Pagination
:total="deviceAlarmTotal"
v-model:page="deviceAlarmQueryParams.pageNo"
v-model:limit="deviceAlarmQueryParams.pageSize"
@pagination="getDeviceAlarmList"
/>
</div>
</Dialog>
</template>
<script setup lang="ts">
import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import request from '@/config/axios'
import DeviceForm from './DeviceForm.vue'
import DeviceAttributeList from './components/DeviceAttributeList.vue'
/** 物联设备 表单 */
defineOptions({ name: 'DeviceForm' })
const attributeDeviceId = ref<number | undefined>(undefined)
const attributeDeviceName = ref('')
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (id?: number,deviceName?: string) => {
dialogVisible.value = true
attributeDeviceId.value= id
attributeDeviceName.value = deviceName
}
defineExpose({ open }) // open
const deviceAlarmDialogVisible = ref(false)
const deviceAlarmLoading = ref(false)
const deviceAlarmList = ref<any[]>([])
const deviceAlarmTotal = ref(0)
const deviceTabActive = ref('deviceAttribute')
const deviceAlarmQueryFormRef = ref()
const deviceAlarmRuleOptions = ref<any[]>([])
const deviceAlarmPointOptions = ref<any[]>([])
const deviceAlarmRuleLoading = ref(false)
const deviceAlarmPointLoading = ref(false)
const ruleLoading = ref(false)
const ruleTotal = ref(0)
const ruleList = ref<DevicePointRuleVO[]>([])
const ruleQueryFormRef = ref()
const createRuleDialogVisible = ref(false)
const createRuleFormLoading = ref(false)
const createRuleFormRef = ref()
const deviceAttributeTabLabel = computed(() => {
return t('DataCollection.Device.deviceAttributeTabLabel')
})
const deviceRuleTabLabel = computed(() => {
return t('DataCollection.Device.deviceRuleTabLabel')
})
const ruleOperatorOptions = computed(() => getStrDictOptions('czsb_rules_conditions'))
const isRunningIdentifier = computed(() => {
return (ruleForm.identifier || '').toString().toUpperCase() === 'RUNNING'
})
const isRuleDisabled = (value: string, selfRule?: string | number) => {
if (!value) return false
const v = String(value)
const self = selfRule != null ? String(selfRule) : undefined
if (self === v) return false
if (ruleForm.fieldRule && String(ruleForm.fieldRule) === v && self !== v) return true
if (
extraPointRules.value.some(
(item) => item.rule != null && String(item.rule) === v && String(item.rule) !== self
)
) {
return true
}
return false
}
const ruleDialogVisible = ref(false)
const ruleFormLoading = ref(false)
const ruleFormRef = ref()
const ruleForm = reactive<Partial<DevicePointRuleVO>>({
id: undefined,
identifier: '',
fieldName: '',
fieldRule: '',
defaultValue: '',
deviceId: undefined,
ruleAttributeId: undefined,
ruleOperator: undefined,
ruleValue: undefined,
alarmLevel: '',
})
const createRuleForm = reactive({
identifier: 'ALARM',
fieldName: '',
defaultValue: '报警',
alarmLevel: '',
})
const deviceAlarmQueryParams = reactive({
pageNo: 1,
pageSize: 10,
deviceId: undefined as number | undefined,
modelId: undefined as number | undefined,
rule: undefined as string | undefined,
alarmLevel: undefined as string | undefined,
addressValue: undefined as string | undefined,
createTime: [] as string[],
ruleId: undefined as number | undefined,
})
const handleRuleSubmit = async () => {
if (!ruleForm.id) return
try {
ruleFormLoading.value = true
const pointRulesVOList: any[] = []
if (ruleForm.ruleAttributeId && ruleForm.ruleOperator) {
const attr = ruleAttributeOptions.value.find(
(item) => item.id === ruleForm.ruleAttributeId
)
if (attr && attr.attributeCode) {
pointRulesVOList.push({
code: attr.attributeCode,
rule: ruleForm.fieldRule,
operator: ruleForm.ruleOperator,
operatorRule: ruleForm.ruleValue,
})
}
}
extraPointRules.value.forEach((item) => {
if (!item.id || !item.operator) return
const attr = ruleAttributeOptions.value.find((opt) => opt.id === item.id)
if (!attr || !attr.attributeCode) return
pointRulesVOList.push({
code: attr.attributeCode,
rule: item.rule ?? ruleForm.fieldRule,
operator: item.operator,
operatorRule: item.operatorRule,
})
})
const payload = {
id: ruleForm.id,
identifier: ruleForm.identifier,
fieldName: ruleForm.fieldName,
fieldRule: ruleForm.fieldRule,
defaultValue: ruleForm.defaultValue,
deviceId: ruleForm.deviceId ?? attributeDeviceId.value,
alarmLevel: ruleForm.alarmLevel,
pointRulesVOList,
}
await request.put({ url: '/iot/device-point-rules/update', data: payload })
message.success(t('common.updateSuccess'))
ruleDialogVisible.value = false
await getRuleList()
} finally {
ruleFormLoading.value = false
}
}
interface DevicePointRuleVO {
id: number
identifier: string
fieldName: string
fieldRule: string
defaultValue: string
deviceId: number
createTime?: string | number | Date
ruleAttributeId?: number
ruleOperator?: string
ruleValue?: string | number
}
const ruleQueryParams = reactive({
pageNo: 1,
pageSize: 10,
identifier: undefined as string | undefined,
fieldName: undefined as string | undefined,
fieldRule: undefined as string | undefined,
defaultValue: undefined as string | undefined,
alarmLevel: undefined as string | undefined,
deviceId: undefined as number | undefined,
})
const handleDeviceAlarmQuery = () => {
if (!attributeDeviceId.value) return
deviceAlarmQueryParams.pageNo = 1
getDeviceAlarmList()
}
const resetDeviceAlarmQuery = () => {
if (!attributeDeviceId.value) return
deviceAlarmQueryFormRef.value?.resetFields?.()
deviceAlarmQueryParams.pageNo = 1
getDeviceAlarmList()
}
const changeClick = () => {
deviceTabActive.value==='deviceRule' ? getRuleList() : getDeviceAlarmList()
}
const openCreateRuleForm = () => {
if (!attributeDeviceId.value) {
message.error('请先选择设备')
return
}
createRuleForm.identifier = 'ALARM'
createRuleForm.fieldName = '报警'
createRuleForm.defaultValue = '报警'
createRuleForm.alarmLevel = ''
createRuleDialogVisible.value = true
}
const handleCreateRuleSubmit = async () => {
if (!attributeDeviceId.value) return
if (!createRuleForm.fieldName) {
message.warning('请输入名称')
return
}
try {
createRuleFormLoading.value = true
const payload = {
identifier: createRuleForm.identifier,
fieldName: createRuleForm.fieldName,
defaultValue: createRuleForm.defaultValue,
alarmLevel: createRuleForm.alarmLevel,
deviceId: attributeDeviceId.value,
}
await request.post({ url: '/iot/device-point-rules/create', data: payload })
message.success('新增成功')
createRuleDialogVisible.value = false
await getRuleList()
} finally {
createRuleFormLoading.value = false
}
}
const handleRuleDelete = async (id: number) => {
if (!id) return
try {
await message.delConfirm()
await request.delete({ url: '/iot/device-point-rules/delete?id=' + id })
message.success(t('common.delSuccess'))
await getRuleList()
} catch { }
}
const handleAddPointRule = () => {
if (!isRunningIdentifier.value) return
const allOptions = currentRuleOptions.value || []
const used = new Set<string>()
if (ruleForm.fieldRule) used.add(String(ruleForm.fieldRule))
extraPointRules.value.forEach((item) => {
if (item.rule != null) used.add(String(item.rule))
})
const next = allOptions.find((opt) => !used.has(String(opt.value)))
if (!next) {
message.warning('已没有可用的点位规则可选')
return
}
extraPointRules.value.push({
rule: next.value as any,
id: undefined,
operator: undefined,
operatorRule: undefined,
})
}
const handleRemovePointRule = (index: number) => {
if (index < 0 || index >= extraPointRules.value.length) return
extraPointRules.value.splice(index, 1)
}
const resetRuleQuery = () => {
if (!attributeDeviceId.value) return
ruleQueryFormRef.value?.resetFields?.()
handleRuleQuery()
}
const handleRuleQuery = () => {
if (!attributeDeviceId.value) return
ruleQueryParams.pageNo = 1
getRuleList()
}
const getRuleList = async () => {
if (!attributeDeviceId.value) return
ruleLoading.value = true
try {
const params = {
...ruleQueryParams,
deviceId: attributeDeviceId.value,
}
const data = await request.get({ url: '/iot/device-point-rules/page', params })
const listData = Array.isArray((data as any)?.list) ? (data as any).list : (Array.isArray(data) ? data : [])
const totalData = (data as any)?.total ?? listData.length
ruleList.value = listData as DevicePointRuleVO[]
ruleTotal.value = totalData
} finally {
ruleLoading.value = false
}
}
const handleShowDeviceAlarmHistory = async () => {
if (!attributeDeviceId.value) {
message.error(t('DataCollection.Device.messageSelectDeviceRequired'))
return
}
deviceAlarmQueryParams.pageNo = 1
deviceAlarmQueryParams.pageSize = 10
deviceAlarmQueryParams.modelId = undefined
deviceAlarmQueryParams.rule = undefined
deviceAlarmQueryParams.alarmLevel = undefined
deviceAlarmQueryParams.addressValue = undefined
deviceAlarmQueryParams.createTime = []
deviceAlarmQueryParams.ruleId = undefined
deviceAlarmDialogVisible.value = true
await Promise.all([loadDeviceAlarmRuleOptions(), loadDeviceAlarmPointOptions()])
await getDeviceAlarmList()
}
const getDeviceAlarmList = async () => {
if (!attributeDeviceId.value) return
deviceAlarmLoading.value = true
try {
deviceAlarmQueryParams.deviceId = attributeDeviceId.value
const params = {
pageNo: deviceAlarmQueryParams.pageNo,
pageSize: deviceAlarmQueryParams.pageSize,
deviceId: deviceAlarmQueryParams.deviceId,
modelId: deviceAlarmQueryParams.modelId,
rule: deviceAlarmQueryParams.rule,
alarmLevel: deviceAlarmQueryParams.alarmLevel,
addressValue: deviceAlarmQueryParams.addressValue,
createTime: deviceAlarmQueryParams.createTime,
ruleId: deviceAlarmQueryParams.ruleId,
}
const data = await request.get({
url: '/iot/device-warinning-record/page',
params,
})
const listData = Array.isArray((data as any)?.list)
? (data as any).list
: Array.isArray(data)
? data
: []
const totalData = (data as any)?.total ?? listData.length
deviceAlarmList.value = listData as any[]
deviceAlarmTotal.value = totalData
} finally {
deviceAlarmLoading.value = false
}
}
const loadDeviceAlarmRuleOptions = async () => {
if (!attributeDeviceId.value) return
deviceAlarmRuleLoading.value = true
try {
const res = await request.get({
url: '/iot/device-point-rules/getList',
params: { id: attributeDeviceId.value },
})
const data = (res as any)?.data ?? res
const listData = Array.isArray((data as any)?.list)
? (data as any).list
: Array.isArray(data)
? data
: []
deviceAlarmRuleOptions.value = listData as any[]
} finally {
deviceAlarmRuleLoading.value = false
}
}
const loadDeviceAlarmPointOptions = async () => {
if (!attributeDeviceId.value) return
deviceAlarmPointLoading.value = true
try {
const res = await request.get({
url: '/iot/device/device-attribute/list',
params: { deviceId: attributeDeviceId.value },
})
const data = (res as any)?.data ?? res
const listData = Array.isArray((data as any)?.list)
? (data as any).list
: Array.isArray(data)
? data
: []
deviceAlarmPointOptions.value = listData as any[]
} finally {
deviceAlarmPointLoading.value = false
}
}
const ruleAttributeOptions = ref<any[]>([])
const loadRuleAttributeOptions = async (deviceId: number) => {
try {
const res = await request.get({ url: '/iot/device-contact-model/list', params: { id: deviceId } })
const data = Array.isArray(res) ? res : (res as any)?.list ?? []
ruleAttributeOptions.value = data as any[]
} catch {
ruleAttributeOptions.value = []
}
}
const extraPointRules = ref<
Array<{
id?: number
rule?: string
operator?: string
operatorRule?: string | number
}>
>([])
const currentRuleOptions = computed(() => {
const id = (ruleForm.identifier || '').toString().toUpperCase()
if (id === 'RUNNING') return runningRuleOptions
if (id === 'ALARM') return alarmRuleOptions
return []
})
const runningRuleOptions = [
{ value: '1', label: '运行' },
{ value: '2', label: '待机中(不运行、没故障)' },
{ value: '3', label: '故障中(故障且待机)' },
// { value: '4', label: '' },
]
const alarmRuleOptions = [{ value: '5', label: '报警' }]
const openRuleForm = async (row: DevicePointRuleVO & { pointRulesVOList?: any[] }) => {
const deviceId = row.deviceId || attributeDeviceId.value
if (!deviceId) {
message.error(t('DataCollection.Device.messageDeviceInfoMissingForRules'))
return
}
await loadRuleAttributeOptions(deviceId)
ruleForm.id = row.id
ruleForm.identifier = row.identifier
ruleForm.fieldName = row.fieldName
ruleForm.defaultValue = row.defaultValue
ruleForm.deviceId = row.deviceId
ruleForm.alarmLevel = (row as any).alarmLevel
extraPointRules.value = []
const list = Array.isArray(row.pointRulesVOList) ? row.pointRulesVOList : []
if (list.length) {
const first = list[0] as any
const firstRule = first.rule ?? row.fieldRule
const firstAttrId = (() => {
if (first && first.code != null) {
const target = ruleAttributeOptions.value.find(
(item) => item.attributeCode === first.code
)
return target?.id
}
if (first && first.id != null) return first.id
return undefined
})()
ruleForm.fieldRule = firstRule as any
ruleForm.ruleAttributeId = firstAttrId as any
ruleForm.ruleOperator = first.operator as any
ruleForm.ruleValue = first.operatorRule as any
extraPointRules.value = list.slice(1).map((item: any) => ({
id: (() => {
if (item && item.code != null) {
const target = ruleAttributeOptions.value.find(
(opt) => opt.attributeCode === item.code
)
return target?.id
}
if (item && item.id != null) return item.id
return undefined
})(),
rule: item.rule,
operator: item.operator,
operatorRule: item.operatorRule,
}))
} else {
ruleForm.fieldRule = row.fieldRule
ruleForm.ruleAttributeId = row.ruleAttributeId
ruleForm.ruleOperator = row.ruleOperator
ruleForm.ruleValue = row.ruleValue as any
}
const options = currentRuleOptions.value
if (options.length && !options.some((item) => item.value === ruleForm.fieldRule)) {
ruleForm.fieldRule = options[0].value
}
ruleDialogVisible.value = true
}
/** 初始化 **/
</script>

File diff suppressed because it is too large Load Diff

@ -1,112 +0,0 @@
<template>
<Dialog v-model="dialogVisible" title="导入采集点分类" width="400">
<el-upload
ref="uploadRef"
v-model:file-list="fileList"
:action="importUrl + '?updateSupport=' + updateSupport"
:auto-upload="false"
:disabled="formLoading"
:headers="uploadHeaders"
:limit="1"
:on-error="submitFormError"
:on-exceed="handleExceed"
:on-success="submitFormSuccess"
accept=".xlsx, .xls"
drag
>
<Icon icon="ep:upload" />
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip text-center">
<span>仅允许导入 xlsxlsx 格式文件</span>
<el-link
:underline="false"
style="font-size: 12px; vertical-align: baseline"
type="primary"
@click="importTemplate"
>
下载导入模板
</el-link>
</div>
</template>
</el-upload>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm">{{ t('common.ok') }}</el-button>
<el-button @click="dialogVisible = false">{{ t('common.cancel') }}</el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import { getAccessToken, getTenantId } from '@/utils/auth'
import download from '@/utils/download'
import { DeviceAttributeTypeApi } from '@/api/iot/deviceattributetype'
defineOptions({ name: 'DeviceAttributeTypeImportForm' })
const { t } = useI18n()
const message = useMessage()
const dialogVisible = ref(false)
const formLoading = ref(false)
const uploadRef = ref()
const importUrl =
import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/iot/device-attribute-type/import'
const uploadHeaders = ref()
const fileList = ref([])
const updateSupport = ref(0)
const open = () => {
dialogVisible.value = true
updateSupport.value = 0
fileList.value = []
resetForm()
}
defineExpose({ open })
const submitForm = async () => {
if (fileList.value.length === 0) {
message.error('请选择导入文件')
return
}
uploadHeaders.value = {
Authorization: 'Bearer ' + getAccessToken(),
tenantId: getTenantId()
}
formLoading.value = true
uploadRef.value!.submit()
}
const emits = defineEmits(['success'])
const submitFormSuccess = (response: any) => {
if (response.code !== 0) {
message.error(response.msg)
formLoading.value = false
return
}
message.alert(response.data)
formLoading.value = false
dialogVisible.value = false
emits('success')
}
const submitFormError = (): void => {
message.error('导入失败')
formLoading.value = false
}
const resetForm = async (): Promise<void> => {
formLoading.value = false
await nextTick()
uploadRef.value?.clearFiles()
}
const handleExceed = (): void => {
message.error('最多只能上传一个文件')
}
const importTemplate = async () => {
const res = await DeviceAttributeTypeApi.importTemplate()
download.excel(res, '采集点分类导入模板.xls')
}
</script>

@ -64,14 +64,6 @@
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('DataCollection.DeviceAttributeType.batchDelete') }}
</el-button>
<el-button
type="warning"
plain
@click="handleImport"
v-hasPermi="['iot:device-attribute-type:create']"
>
<Icon icon="ep:upload" class="mr-5px" /> 导入
</el-button>
<el-button
type="success"
plain
@ -141,7 +133,6 @@
<!-- 表单弹窗添加/修改 -->
<DeviceAttributeTypeForm ref="formRef" @success="getList" />
<DeviceAttributeTypeImportForm ref="importFormRef" @success="getList" />
</template>
<script setup lang="ts">
@ -149,7 +140,6 @@ import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { DeviceAttributeTypeApi, DeviceAttributeTypeVO } from '@/api/iot/deviceattributetype'
import DeviceAttributeTypeForm from './DeviceAttributeTypeForm.vue'
import DeviceAttributeTypeImportForm from './DeviceAttributeTypeImportForm.vue'
/** 采集点分类 列表 */
defineOptions({ name: 'DeviceAttributeType' })
@ -236,12 +226,6 @@ const handleBatchDelete = async () => {
}
/** 导出按钮操作 */
/** 导入 */
const importFormRef = ref()
const handleImport = () => {
importFormRef.value.open()
}
const handleExport = async () => {
try {
await message.exportConfirm()

File diff suppressed because it is too large Load Diff

@ -34,7 +34,7 @@
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.HistoryData.searchLineNameLabel')" prop="lineName" v-show="showAllFilters">
<el-form-item :label="t('DataCollection.HistoryData.searchLineNameLabel')" prop="lineName">
<el-input
v-model="queryParams.lineName"
:placeholder="t('DataCollection.HistoryData.searchLineNamePlaceholder')"
@ -43,12 +43,6 @@
class="!w-240px"
/>
</el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('DataCollection.HistoryData.collapseText') : t('DataCollection.HistoryData.expandText') }}
</el-button>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" />
@ -114,15 +108,12 @@
:label="t('DataCollection.HistoryData.tableOperateColumn')"
align="center"
fixed="right"
width="300px"
width="150px"
>
<template #default="scope">
<el-button link type="primary" @click="handleSingleView(scope.row)">
{{ t('DataCollection.HistoryData.tableActionHistoryLabel') }}
</el-button>
<el-button link type="primary" @click="handleSingleAnalyseView(scope.row)">
{{ t('DataCollection.HistoryData.tableActionHistoryAnalyseLabel') }}
</el-button>
</template>
</el-table-column>
</el-table>
@ -146,10 +137,9 @@ import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { DeviceApi, LineDeviceVO, LineDevicePageParams } from '@/api/iot/device'
import HistorySingleDeviceDialog from './HistorySingleDeviceDialog.vue'
import {useRouter} from "vue-router";
//
const router = useRouter()
defineOptions({ name: 'HistoryData' })
const message = useMessage()
const { t } = useI18n()
@ -157,11 +147,6 @@ const loading = ref(true)
const list = ref<LineDeviceVO[]>([])
const total = ref(0)
const exportLoading = ref(false)
const showAllFilters = ref(false)
const filterCount = 4
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
@ -240,27 +225,6 @@ const handleSingleView = (row: LineDeviceVO) => {
singleDialogVisible.value = true
}
const handleSingleAnalyseView = (row: LineDeviceVO) => {
const deviceId = (row as any)?.deviceId ?? row?.id
if (deviceId === undefined || deviceId === null || deviceId === '') {
return
}
singleDeviceId.value = deviceId
singleDeviceName.value = row.deviceName || ''
router.push({
path: '/iot/historySingleAnalyse',
query: {
id: deviceId,
name: singleDeviceName.value,
deviceCode: row.deviceCode,
lineNode: row.lineNode,
lineName: row.lineName,
collectionTime: row.collectionTime
}
});
}
onMounted(() => {
getList()
})

@ -7,15 +7,15 @@
</div>
<ContentWrap v-loading="loading">
<div v-if="sections.length" class="single-device-dialog__table-grid">
<!-- <div v-for="section in sections" :key="section.key" class="single-device-dialog__section">
<div v-for="section in sections" :key="section.key" class="single-device-dialog__section">
<div class="single-device-dialog__section-title">
{{ section.title }}
</div>-->
<!-- <el-empty
v-if="!section.length"
</div>
<el-empty
v-if="!section.columns.length"
:description="t('DataCollection.RealTimeMonitoring.emptyDescription')"
/>-->
<!-- <el-table
/>
<el-table
v-else :data="section.rows" :border="true" :header-cell-style="headerCellStyle"
:cell-style="bodyCellStyle" size="small">
<el-table-column
@ -25,22 +25,8 @@ v-for="col in section.columns" :key="col.prop" :prop="col.prop" :label="col.labe
<span>{{ formatCell(scope.row[col.prop]) }}</span>
</template>
</el-table-column>
</el-table>-->
<div v-for="section in sections" :key="section.id" class="form-section">
<h3 v-if="section.title" class="section-title">{{ section.title }}</h3>
<div class="form-grid">
<div
v-for="item in section.items"
:key="item.prop"
class="form-item"
>
<div class="form-label">{{ item.label }}:</div>
<div class="form-value">{{ formatCellTwo(item.value) }}</div>
</div>
</div>
</div>
<!-- </div>-->
</el-table>
</div>
</div>
<el-empty
v-else
@ -63,14 +49,10 @@ type SectionColumn = {
type SectionRow = Record<string, string | number | null>
type Section = {
id: number
title?: string
items: FormItem[]
}
interface FormItem {
prop: string
label: string
value: any
key: string
title: string
columns: SectionColumn[]
rows: SectionRow[]
}
const { t } = useI18n()
@ -101,44 +83,8 @@ const dialogTitle = computed(() => {
const loading = ref(false)
const deviceName = computed(() => props.deviceName)
const sections = ref<Section[]>([
{
id: 1,
title: '工艺参数监控',
items: [
{ prop: 'addressValue', label: '系统运行', value: 1, unit: '℃' },
{ prop: 'attributeName', label: '烘干湿度', value: 2, unit: '%' },
{ prop: 'attributeName', label: '输送速度', value: 3, unit: 'm/min' },
{ prop: 'attributeName', label: '回风温度', value: 4, unit: '℃' },
{ prop: 'attributeName', label: '排风温度', value: 5, unit: '℃' },
{ prop: 'attributeName', label: '呼叫状态', value: 6 },
{ prop: 'attributeName', label: '在线状态', value: 7 },
{ prop: 'attributeName', label: '运行状态', value: 3 },
{ prop: 'attributeName', label: '主缸速度', value: 0, unit: '%' },
{ prop: 'attributeName', label: '提布辊速度', value: 0 },
{ prop: 'fanSpeed', label: '风机速度', value: '暂无数据' },
{ prop: 'mainLevel', label: '主缸水位', value: 1, unit: '%' },
{ prop: 'mainWater', label: '主缸水量', value: 145, unit: 'L' },
{ prop: 'setTemp', label: '设定温度', value: 0.0, unit: '℃' },
{ prop: 'actualTemp', label: '实际温度', value: 0.0, unit: '℃' },
],
},
])
const formatValue = (item: FormItem) => {
let value = item.value
if (value === null || value === undefined) return '--'
if (typeof value === 'number') {
value = Number.isInteger(value) ? value.toString() : value.toFixed(2)
}
if (item.unit) {
value += ` ${item.unit}`
}
const sections = ref<Section[]>([])
return value
}
const toGroupMap = (value: any): Record<string, any[]> => {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return {}
@ -190,14 +136,6 @@ const formatCell = (value: string | number | null | undefined) => {
return value
}
const formatCellTwo = (value: any) => {
//
if (typeof value === 'number') {
return value.toFixed(2)
}
return value
}
const buildSectionsFromGroups = (groups: Record<string, any[]>): Section[] => {
const result: Section[] = []
for (const [key, list] of Object.entries(groups)) {
@ -212,12 +150,12 @@ const buildSectionsFromGroups = (groups: Record<string, any[]>): Section[] => {
row[prop] = item?.addressValue ?? '-'
})
}
/* result.push({
result.push({
key,
title: key,
columns,
rows: columns.length ? [row] : []
})*/
})
}
return result
}
@ -229,16 +167,15 @@ const fetchList = async () => {
}
loading.value = true
try {
const res: any = await DeviceApi.getSingleDeviceFrom({ deviceId: props.deviceId })
/* const groups = Array.isArray(res)
const res: any = await DeviceApi.getSingleDevice({ deviceId: props.deviceId })
const groups = Array.isArray(res)
? { [t('DataCollection.RealTimeMonitoring.defaultGroupName')]: res }
: {
...toGroupMap(res?.data),
...toGroupMap(res?.data?.data),
...toGroupMap(res)
}
//sections.value = buildSectionsFromGroups(groups)*/
sections.value = res
sections.value = buildSectionsFromGroups(groups)
} finally {
loading.value = false
}
@ -285,84 +222,4 @@ watch(
.single-device-dialog__section :deep(.el-table__inner-wrapper) {
border-radius: 0;
}
/* 表单容器 */
.form-section {
margin-bottom: 24px;
}
/* 标题样式 */
.section-title {
font-size: 13px;
font-weight: bold;
margin-bottom: 12px;
color: var(--el-text-color-primary);
}
/* 三列网格容器 */
.form-grid {
display: grid;
grid-template-columns: repeat(4, 1fr); /* 三列等宽 */
/*gap: 16px;*/ /* 列间距和行间距 */
/* padding: 8px;*/
background-color: #fff;
/* border: 1px solid #dcdfe6; !* 外边框 *!*/
border-radius: 4px;
min-height: 35px;
}
/* 表单项:标签 + 值 */
.form-item {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
min-height: 35px;
/* padding: 8px 0;*/
border: 1px solid #dcdfe6;
/* border-right: 1px solid #dcdfe6; !* *!
border-bottom: 1px solid #dcdfe6; !* 下边框 *!*/
}
/* 标签样式 */
.form-label {
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
color: #666;
height: 100%;
min-height: 35px;
width: 150px; /* 固定标签宽度,避免错位 */
background-color: #f5f7fa; /* 背景色 */
}
/* 值样式 */
.form-value {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
padding: 6px 12px;
background: white;
min-height: 35px;
box-sizing: border-box;
overflow: hidden;
cursor: default;
transition: all 0.2s;
}
/* 响应式:小屏幕下改为两列或单列 */
@media (max-width: 768px) {
.form-grid {
grid-template-columns: repeat(2, 1fr); /* 平板改为两列 */
}
}
@media (max-width: 480px) {
.form-grid {
grid-template-columns: 1fr; /* 手机改为单列 */
}
}
</style>

@ -34,7 +34,7 @@
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.RealTimeMonitoring.searchLineNameLabel')" prop="lineName" v-show="showAllFilters">
<el-form-item :label="t('DataCollection.RealTimeMonitoring.searchLineNameLabel')" prop="lineName">
<el-input
v-model="queryParams.lineName"
:placeholder="t('DataCollection.RealTimeMonitoring.searchLineNamePlaceholder')"
@ -43,12 +43,6 @@
class="!w-240px"
/>
</el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('DataCollection.RealTimeMonitoring.collapseText') : t('DataCollection.RealTimeMonitoring.expandText') }}
</el-button>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" />
@ -162,11 +156,6 @@ const loading = ref(true)
const list = ref<LineDeviceVO[]>([])
const total = ref(0)
const exportLoading = ref(false)
const showAllFilters = ref(false)
const filterCount = 4
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const queryParams = reactive({
pageNo: 1,
pageSize: 10,

@ -1,277 +0,0 @@
<template>
<ContentWrap class="timeline-panel">
<div class="timeline-panel__header">
<div class="timeline-panel__title">{{ t('DataCollection.RunOverview.timelineTitle') }}</div>
<div class="timeline-panel__actions">
<el-select model-value="" class="!w-140px">
<el-option value="" :label="t('DataCollection.RunOverview.deviceFilterAll')" />
</el-select>
<el-select model-value="expand" class="!w-120px">
<el-option value="expand" :label="t('DataCollection.RunOverview.expandAllText')" />
</el-select>
<el-button class="timeline-panel__icon-btn">
<Icon icon="ep:data-analysis" />
</el-button>
<el-button class="timeline-panel__icon-btn">
<Icon icon="ep:download" />
</el-button>
</div>
</div>
<div class="timeline-legend">
<div v-for="item in legendItems" :key="item.status" class="timeline-legend__item">
<span class="timeline-legend__dot" :style="{ background: item.color }"></span>
<span>{{ item.label }}</span>
</div>
</div>
<div class="timeline-table">
<div class="timeline-table__head">
<div class="timeline-table__meta">
<div class="timeline-table__device">{{ t('DataCollection.RunOverview.tableDeviceNameColumn') }}</div>
<div class="timeline-table__rate">{{ t('DataCollection.RunOverview.tableUtilizationRateColumn') }}</div>
</div>
<div class="timeline-scale">
<div v-for="hour in hourTicks" :key="hour" class="timeline-scale__tick">
{{ hour }}
</div>
</div>
</div>
<div v-for="row in timelineRows" :key="row.id" class="timeline-table__row">
<div class="timeline-table__meta">
<div class="timeline-table__device">{{ row.name }}</div>
<div class="timeline-table__rate">{{ row.utilizationRate.toFixed(2) }}%</div>
</div>
<div class="timeline-track">
<div v-for="hour in 24" :key="hour" class="timeline-track__hour"></div>
<div
v-for="(segment, index) in row.segments"
:key="`${row.id}-${index}`"
class="timeline-segment"
:style="{
left: `${(segment.startHour / 24) * 100}%`,
width: `${((segment.endHour - segment.startHour) / 24) * 100}%`,
background: statusColors[segment.status]
}"
></div>
</div>
</div>
</div>
<div class="timeline-panel__footer">
<div class="timeline-panel__summary">
{{ t('DataCollection.RunOverview.statisticsTimeText', { start: statisticsStart, end: statisticsEnd }) }}
</div>
<div class="timeline-panel__pager">
<span>{{ t('DataCollection.RunOverview.totalDevicesText', { total }) }}</span>
<el-select :model-value="pageSize" class="!w-100px" @update:model-value="emit('update:pageSize', Number($event))">
<el-option :value="10" :label="`10${t('DataCollection.RunOverview.pageUnit')}`" />
<el-option :value="20" :label="`20${t('DataCollection.RunOverview.pageUnit')}`" />
</el-select>
<el-pagination
layout="prev, pager, next"
:page-size="pageSize"
:current-page="pageNo"
:total="total"
small
@current-change="emit('update:pageNo', $event)"
/>
</div>
</div>
</ContentWrap>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { DeviceTimelineRow, RunStatus } from './types'
const props = defineProps<{
rows: DeviceTimelineRow[]
total: number
pageNo: number
pageSize: number
statisticsStart: string
statisticsEnd: string
}>()
const emit = defineEmits<{
(e: 'update:pageNo', value: number): void
(e: 'update:pageSize', value: number): void
}>()
const { t } = useI18n()
const statusColors: Record<RunStatus, string> = {
running: '#67c35b',
standby: '#f5c243',
fault: '#ff6b6b',
offline: '#d5dae3'
}
const legendItems = computed(() => [
{ status: 'running' as const, color: statusColors.running, label: t('DataCollection.RunOverview.legend.running') },
{ status: 'standby' as const, color: statusColors.standby, label: t('DataCollection.RunOverview.legend.standby') },
{ status: 'fault' as const, color: statusColors.fault, label: t('DataCollection.RunOverview.legend.fault') },
{ status: 'offline' as const, color: statusColors.offline, label: t('DataCollection.RunOverview.legend.offline') }
])
const hourTicks = Array.from({ length: 24 }, (_, index) => `${String(index).padStart(2, '0')}:00`)
const timelineRows = computed(() =>
props.rows.map((row) => ({
...row,
segments: (row.segments || []).filter(
(segment) =>
!!statusColors[segment.status] &&
Number.isFinite(segment.startHour) &&
Number.isFinite(segment.endHour) &&
segment.endHour > segment.startHour
)
}))
)
</script>
<style scoped lang="scss">
.timeline-panel__header,
.timeline-panel__footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.timeline-panel__title {
color: #101828;
font-size: 16px;
font-weight: 700;
}
.timeline-panel__actions,
.timeline-panel__pager {
display: flex;
align-items: center;
gap: 10px;
}
.timeline-panel__icon-btn {
width: 36px;
padding: 0;
}
.timeline-legend {
display: flex;
justify-content: center;
gap: 32px;
margin: 10px 0 18px;
}
.timeline-legend__item {
display: flex;
align-items: center;
gap: 8px;
color: #475467;
font-size: 13px;
}
.timeline-legend__dot {
width: 18px;
height: 10px;
border-radius: 999px;
}
.timeline-table {
overflow-x: auto;
}
.timeline-table__head,
.timeline-table__row {
display: grid;
grid-template-columns: 210px minmax(720px, 1fr);
gap: 16px;
}
.timeline-table__head {
margin-bottom: 12px;
}
.timeline-table__row {
align-items: center;
margin-bottom: 10px;
}
.timeline-table__meta {
display: grid;
grid-template-columns: 1fr 70px;
gap: 12px;
align-items: center;
}
.timeline-table__device,
.timeline-table__rate {
color: #344054;
font-size: 13px;
}
.timeline-table__head .timeline-table__device,
.timeline-table__head .timeline-table__rate {
color: #475467;
font-weight: 600;
}
.timeline-scale,
.timeline-track {
position: relative;
display: grid;
grid-template-columns: repeat(24, 1fr);
}
.timeline-scale {
color: #667085;
font-size: 12px;
}
.timeline-scale__tick {
position: relative;
}
.timeline-scale__tick::after {
position: absolute;
top: 18px;
right: 0;
width: 1px;
height: 12px;
background: #e4e7ec;
content: '';
}
.timeline-track {
height: 20px;
border-radius: 999px;
overflow: hidden;
}
.timeline-track__hour {
border-right: 1px solid #edf1f7;
}
.timeline-segment {
position: absolute;
top: 0;
bottom: 0;
border-radius: 999px;
}
.timeline-panel__summary {
color: #667085;
font-size: 13px;
}
@media (max-width: 1100px) {
.timeline-panel__header,
.timeline-panel__footer {
flex-direction: column;
align-items: flex-start;
}
}
</style>

@ -1,139 +0,0 @@
<template>
<ContentWrap class="run-overview-filter">
<el-form :model="modelValue" inline label-width="auto">
<el-form-item :label="t('DataCollection.RunOverview.groupLabel')">
<el-tree-select
:model-value="modelValue.groupId"
:data="groupOptions"
:props="treeProps"
check-strictly
clearable
default-expand-all
class="!w-220px"
:placeholder="t('DataCollection.RunOverview.groupPlaceholder')"
@update:model-value="handleGroupChange"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.RunOverview.deviceLabel')">
<el-select
:model-value="modelValue.deviceId"
multiple
collapse-tags
collapse-tags-tooltip
clearable
:placeholder="t('DataCollection.RunOverview.devicePlaceholder')"
class="!w-240px"
@update:model-value="updateField('deviceId', $event || [])"
>
<el-option v-for="item in deviceOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item :label="t('DataCollection.RunOverview.timeRangeLabel')">
<el-date-picker
:model-value="modelValue.timeRange"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
:start-placeholder="t('DataCollection.RunOverview.timeRangeStartPlaceholder')"
:end-placeholder="t('DataCollection.RunOverview.timeRangeEndPlaceholder')"
class="!w-360px"
@update:model-value="updateField('timeRange', $event)"
/>
</el-form-item>
<el-form-item class="run-overview-filter__actions">
<el-button
v-for="item in quickRanges"
:key="item.value"
:type="modelValue.quickRange === item.value ? 'primary' : 'default'"
@click="emit('quick-range-change', item.value)"
>
{{ item.label }}
</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="emit('query')">
<Icon icon="ep:search" class="mr-5px" />
{{ t('DataCollection.RunOverview.searchButtonText') }}
</el-button>
<el-button @click="emit('reset')">
<Icon icon="ep:refresh" class="mr-5px" />
{{ t('DataCollection.RunOverview.resetButtonText') }}
</el-button>
<!-- <el-button type="success" plain @click="emit('export')">
<Icon icon="ep:download" class="mr-5px" />
{{ t('DataCollection.RunOverview.exportButtonText') }}
</el-button>-->
</el-form-item>
</el-form>
</ContentWrap>
</template>
<script setup lang="ts">
import type {
OrganizationTreeOption,
OverviewOption,
QuickRangeKey,
RunOverviewQueryParams
} from './types'
const props = defineProps<{
modelValue: RunOverviewQueryParams
groupOptions: OrganizationTreeOption[]
deviceOptions: OverviewOption[]
quickRanges: Array<{ label: string; value: QuickRangeKey }>
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: RunOverviewQueryParams): void
(e: 'query'): void
(e: 'reset'): void
(e: 'export'): void
(e: 'quick-range-change', value: QuickRangeKey): void
}>()
const { t } = useI18n()
const treeProps = {
value: 'id',
label: 'name',
children: 'children'
}
const updateField = <K extends keyof RunOverviewQueryParams>(key: K, value: RunOverviewQueryParams[K]) => {
emit('update:modelValue', {
...props.modelValue,
[key]: value
})
}
const handleGroupChange = (value: string | number | undefined) => {
const nextGroupId = value == null ? '' : String(value)
emit('update:modelValue', {
...props.modelValue,
groupId: nextGroupId,
deviceId: props.modelValue.deviceId
})
}
</script>
<style scoped lang="scss">
.run-overview-filter {
:deep(.el-form) {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px 0;
}
:deep(.el-form-item) {
margin-bottom: 10px;
}
:deep(.el-form-item__label) {
min-width: 84px;
color: #475467;
}
}
.run-overview-filter__actions :deep(.el-button + .el-button) {
margin-left: 8px;
}
</style>

@ -1,120 +0,0 @@
<template>
<div class="metric-grid">
<div v-for="item in metrics" :key="item.key" class="metric-card">
<div class="metric-card__icon" :style="{ background: itemColors[item.key]?.bg, color: itemColors[item.key]?.fg }">
<Icon :icon="item.icon" />
</div>
<div class="metric-card__content">
<div class="metric-card__title">{{ t(`DataCollection.RunOverview.metrics.${item.key}`) }}</div>
<div class="metric-card__value">
{{ item.value.toFixed(2) }}<span>{{ item.unit }}</span>
</div>
<div class="metric-card__compare">
<span>{{ t('DataCollection.RunOverview.compareLabel') }}</span>
<span :class="item.change >= 0 ? 'is-up' : 'is-down'">
{{ item.change >= 0 ? '↑' : '↓' }} {{ Math.abs(item.change).toFixed(2) }}%
</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { RunOverviewMetric } from './types'
defineProps<{
metrics: RunOverviewMetric[]
}>()
const { t } = useI18n()
const itemColors: Record<string, { bg: string; fg: string }> = {
utilizationRate: { bg: 'rgba(61, 132, 255, 0.12)', fg: '#3d84ff' },
powerOnRate: { bg: 'rgba(86, 196, 94, 0.12)', fg: '#56c45e' },
faultRate: { bg: 'rgba(255, 164, 54, 0.12)', fg: '#ffa436' },
standbyRate: { bg: 'rgba(156, 108, 255, 0.12)', fg: '#9c6cff' }
}
</script>
<style scoped lang="scss">
.metric-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 16px;
margin-bottom: 16px;
}
.metric-card {
display: flex;
align-items: center;
min-height: 104px;
padding: 22px 24px;
background: #fff;
border: 1px solid #edf1f7;
border-radius: 14px;
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.04);
}
.metric-card__icon {
display: flex;
align-items: center;
justify-content: center;
width: 52px;
height: 52px;
margin-right: 18px;
font-size: 28px;
border-radius: 50%;
}
.metric-card__content {
min-width: 0;
}
.metric-card__title {
margin-bottom: 6px;
color: #475467;
font-size: 14px;
font-weight: 600;
}
.metric-card__value {
color: #101828;
font-size: 20px;
font-weight: 700;
line-height: 1.2;
span {
margin-left: 2px;
font-size: 18px;
}
}
.metric-card__compare {
display: flex;
gap: 8px;
margin-top: 10px;
color: #667085;
font-size: 13px;
}
.is-up {
color: #f04438;
}
.is-down {
color: #12b76a;
}
@media (max-width: 1200px) {
.metric-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 768px) {
.metric-grid {
grid-template-columns: 1fr;
}
}
</style>

@ -1,271 +0,0 @@
<template>
<ContentWrap class="chart-panel">
<div class="chart-panel__header">
<div class="chart-panel__title">
{{ t('DataCollection.RunOverview.statusDistributionTitle') }}
</div>
<div class="chart-panel__actions">
<el-select model-value="hour" class="!w-120px">
<el-option value="hour" :label="t('DataCollection.RunOverview.granularityHour')" />
</el-select>
<el-button class="chart-panel__icon-btn">
<Icon icon="ep:data-analysis" />
</el-button>
<el-button class="chart-panel__icon-btn">
<Icon icon="ep:download" />
</el-button>
</div>
</div>
<div class="chart-panel__body">
<div class="chart-panel__main">
<Echart :options="barOption" height="320px" />
</div>
<div class="chart-panel__side">
<div class="chart-panel__side-title">{{ t('DataCollection.RunOverview.summaryTitle') }}</div>
<div class="chart-panel__summary-layout">
<!-- <div class="chart-panel__pie">
<Echart :options="pieOption" height="280px" />
</div>&ndash;&gt;-->
<Echart :options="pieOption" height="280px" />
<div class="chart-panel__legend">
<div v-for="item in summary" :key="item.status" class="chart-panel__legend-item">
<span class="dot" :style="{ background: statusColors[item.status] }"></span>
<span class="label">{{ statusLabelMap[item.status] }}</span>
<span class="value">{{ item.percent.toFixed(2) }}% ({{ item.hours.toFixed(2) }}h)</span>
</div>
</div>
</div>
</div>
</div>
</ContentWrap>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { EChartsOption } from 'echarts'
import { Echart as EchartChart } from '@/components/Echart'
import type { HourlyStatusItem, RunStatus, StatusSummaryItem } from './types'
const Echart = EchartChart
const props = defineProps<{
hourlyStatus: HourlyStatusItem[]
summary: StatusSummaryItem[]
summaryTotalHours?: number
}>()
const { t } = useI18n()
const statusColors: Record<RunStatus, string> = {
running: '#67c35b',
standby: '#f5c243',
fault: '#ff6b6b',
offline: '#cfd4df'
}
const statusLabelMap = computed<Record<RunStatus, string>>(() => ({
running: t('DataCollection.RunOverview.legend.running'),
standby: t('DataCollection.RunOverview.legend.standby'),
fault: t('DataCollection.RunOverview.legend.fault'),
offline: t('DataCollection.RunOverview.legend.offline')
}))
const barOption = computed<EChartsOption>(() => ({
color: [
statusColors.running,
statusColors.standby,
statusColors.fault,
statusColors.offline
],
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: {
top: 8,
itemWidth: 14,
itemHeight: 8,
textStyle: { color: '#667085', fontSize: 12 }
},
grid: { left: 48, right: 18, top: 46, bottom: 36 },
xAxis: {
type: 'category',
data: props.hourlyStatus.map((item) => item.hour),
axisTick: { show: false },
axisLine: { lineStyle: { color: '#d0d5dd' } },
axisLabel: { color: '#667085', fontSize: 11 }
},
yAxis: {
type: 'value',
min: 0,
max: 100,
interval: 20,
axisLabel: { color: '#667085', formatter: '{value}%' },
splitLine: { lineStyle: { color: '#eef2f6' } }
},
series: [
{
name: statusLabelMap.value.running,
type: 'bar',
stack: 'status',
barWidth: 18,
data: props.hourlyStatus.map((item) => item.running)
},
{
name: statusLabelMap.value.standby,
type: 'bar',
stack: 'status',
barWidth: 18,
data: props.hourlyStatus.map((item) => item.standby)
},
{
name: statusLabelMap.value.fault,
type: 'bar',
stack: 'status',
barWidth: 18,
data: props.hourlyStatus.map((item) => item.fault)
},
{
name: statusLabelMap.value.offline,
type: 'bar',
stack: 'status',
barWidth: 18,
data: props.hourlyStatus.map((item) => item.offline)
}
]
}))
const pieOption = computed<EChartsOption>(() => ({
color: props.summary.map((item) => statusColors[item.status]),
tooltip: { trigger: 'item', formatter: '{b}: {c}%' },
graphic: [
{
type: 'text',
left: 'center',
top: '42%',
style: {
text: `${t('DataCollection.RunOverview.totalTimeLabel')}\n${(props.summaryTotalHours ?? 0).toFixed(2)} h`,
textAlign: 'center',
fill: '#101828',
fontSize: 14,
fontWeight: 600,
lineHeight: 24
}
}
],
series: [
{
name: t('DataCollection.RunOverview.summaryTitle'),
type: 'pie',
radius: ['55%', '76%'],
center: ['50%', '50%'],
label: { show: false },
data: props.summary.map((item) => ({
name: statusLabelMap.value[item.status],
value: item.percent
}))
}
]
}))
</script>
<style scoped lang="scss">
.chart-panel {
margin-bottom: 16px;
}
.chart-panel__header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.chart-panel__title,
.chart-panel__side-title {
color: #101828;
font-size: 16px;
font-weight: 700;
}
.chart-panel__actions {
display: flex;
gap: 10px;
align-items: center;
}
.chart-panel__icon-btn {
width: 36px;
padding: 0;
}
.chart-panel__body {
display: grid;
grid-template-columns: minmax(0, 1.7fr) minmax(280px, 0.8fr);
gap: 12px;
}
.chart-panel__side {
padding: 8px 12px 0;
}
.chart-panel__summary-layout {
display: grid;
grid-template-columns: minmax(180px, 220px) minmax(180px, 1fr);
align-items: center;
gap: 8px 16px;
min-height: 280px;
}
.chart-panel__pie {
display: flex;
align-items: center;
justify-content: center;
}
.chart-panel__legend {
display: flex;
flex-direction: column;
gap: 14px;
justify-content: center;
}
.chart-panel__legend-item {
display: grid;
grid-template-columns: 12px 52px 1fr;
gap: 10px;
align-items: center;
color: #344054;
font-size: 13px;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
}
.label {
color: #667085;
}
.value {
color: #101828;
font-weight: 600;
text-align: left;
}
@media (max-width: 1100px) {
.chart-panel__body {
grid-template-columns: 1fr;
}
.chart-panel__summary-layout {
grid-template-columns: 1fr;
justify-items: center;
}
.chart-panel__legend {
width: 100%;
max-width: 320px;
}
}
</style>

@ -1,75 +0,0 @@
export type RunStatus = 'running' | 'standby' | 'fault' | 'offline'
export type QuickRangeKey = 'today' | 'yesterday' | 'last7Days' | 'last30Days'
export interface OverviewOption {
label: string
value: string
}
export interface OrganizationFilterItem {
id: number | string
name: string
parentId?: number | string | null
dvId?: number | string | null
machineName?: string
}
export interface OrganizationTreeOption {
id: number | string
name: string
parentId?: number | string | null
dvId?: number | string | null
children?: OrganizationTreeOption[]
}
export interface RunOverviewQueryParams {
groupId: string
deviceId: string[]
quickRange: QuickRangeKey
timeRange: [string, string]
}
export interface RunOverviewMetric {
key: string
icon: string
value: number
unit: string
change: number
}
export interface HourlyStatusItem {
hour: string
running: number
standby: number
fault: number
offline: number
}
export interface StatusSummaryItem {
status: RunStatus
percent: number
hours: number
}
export interface TimelineSegment {
status: RunStatus
startHour: number
endHour: number
}
export interface DeviceTimelineRow {
id: string
name: string
utilizationRate: number
segments: TimelineSegment[]
}
export interface RunOverviewData {
metrics: RunOverviewMetric[]
hourlyStatus: HourlyStatusItem[]
summary: StatusSummaryItem[]
summaryTotalHours: number
timelineRows: DeviceTimelineRow[]
totalDevices: number
}

@ -1,312 +0,0 @@
<template>
<div ref="fullscreenTargetRef" class="run-overview-page" :class="{ 'is-fullscreen': isFullscreen }">
<div class="run-overview-page__floating-tools">
<el-tooltip
:content="
isFullscreen
? t('DataCollection.RunOverview.exitFullscreen')
: t('DataCollection.RunOverview.enterFullscreen')
"
>
<button class="run-overview-page__screenfull" type="button" @click="toggleFullscreen">
<Icon :icon="isFullscreen ? 'zmdi:fullscreen-exit' : 'zmdi:fullscreen'" color="#344054" />
</button>
</el-tooltip>
</div>
<OverviewFilterBar
v-model="queryParams"
:group-options="groupOptions"
:device-options="deviceOptions"
:quick-ranges="quickRanges"
@quick-range-change="handleQuickRangeChange"
@query="handleQuery"
@reset="resetQuery"
/>
<OverviewMetricCards :metrics="overviewData.metrics" />
<StatusDistributionChart
:hourly-status="overviewData.hourlyStatus"
:summary="overviewData.summary"
:summary-total-hours="overviewData.summaryTotalHours"
/>
<OperationTimelineChart
:rows="overviewData.timelineRows"
:total="overviewData.totalDevices"
:page-no="pageNo"
:page-size="pageSize"
:statistics-start="queryParams.timeRange[0]"
:statistics-end="queryParams.timeRange[1]"
@update:page-no="pageNo = $event"
@update:page-size="handlePageSizeChange"
/>
</div>
</template>
<script setup lang="ts">
import dayjs from 'dayjs'
import { useFullscreen } from '@vueuse/core'
import { DeviceOperationOverviewApi } from '@/api/iot/deviceOperationOverview'
import { OrganizationApi } from '@/api/mes/organization'
import { handleTree } from '@/utils/tree'
import OverviewFilterBar from './components/OverviewFilterBar.vue'
import OverviewMetricCards from './components/OverviewMetricCards.vue'
import OperationTimelineChart from './components/OperationTimelineChart.vue'
import StatusDistributionChart from './components/StatusDistributionChart.vue'
import { buildDefaultQueryParams } from './mock'
import type {
OrganizationFilterItem,
OrganizationTreeOption,
OverviewOption,
QuickRangeKey,
RunOverviewData,
RunOverviewQueryParams
} from './components/types'
defineOptions({ name: 'IotRunOverview' })
const { t } = useI18n()
const message = useMessage()
const fullscreenTargetRef = ref()
const { isFullscreen, toggle: toggleFullscreen } = useFullscreen(fullscreenTargetRef)
const organizationList = ref<OrganizationFilterItem[]>([])
const groupOptions = ref<OrganizationTreeOption[]>([])
const createDateRangeByQuickKey = (key: QuickRangeKey): [string, string] => {
const format = 'YYYY-MM-DD HH:mm:ss'
if (key === 'today') return [dayjs().startOf('day').format(format), dayjs().endOf('day').format(format)]
if (key === 'yesterday') {
return [
dayjs().subtract(1, 'day').startOf('day').format(format),
dayjs().subtract(1, 'day').endOf('day').format(format)
]
}
if (key === 'last7Days') return [dayjs().subtract(6, 'day').startOf('day').format(format), dayjs().endOf('day').format(format)]
if (key === 'last30Days') {
return [dayjs().subtract(29, 'day').startOf('day').format(format), dayjs().endOf('day').format(format)]
}
return [dayjs().startOf('day').format(format), dayjs().endOf('day').format(format)]
}
const quickRanges = computed(() => [
{ label: t('DataCollection.RunOverview.quickRange.today'), value: 'today' as const },
{ label: t('DataCollection.RunOverview.quickRange.yesterday'), value: 'yesterday' as const },
{ label: t('DataCollection.RunOverview.quickRange.last7Days'), value: 'last7Days' as const },
{ label: t('DataCollection.RunOverview.quickRange.last30Days'), value: 'last30Days' as const },
/* { label: t('DataCollection.RunOverview.quickRange.custom'), value: 'custom' as const }*/
])
const queryParams = ref<RunOverviewQueryParams>(buildDefaultQueryParams())
const pageNo = ref(1)
const pageSize = ref(10)
const organizationMap = computed(() => {
return organizationList.value.reduce<Record<string, OrganizationFilterItem>>((acc, item) => {
acc[String(item.id)] = item
return acc
}, {})
})
const childrenByParentId = computed(() => {
return organizationList.value.reduce<Record<string, OrganizationFilterItem[]>>((acc, item) => {
const parentKey = String(item.parentId ?? 0)
if (!acc[parentKey]) acc[parentKey] = []
acc[parentKey].push(item)
return acc
}, {})
})
const collectDeviceOptionsByGroup = (groupId?: string) => {
const deviceMap = new Map<string, OverviewOption>()
const pushDevice = (node?: OrganizationFilterItem) => {
if (!node?.dvId || !node.machineName?.trim()) return
const deviceId = String(node.dvId)
if (!deviceMap.has(deviceId)) {
deviceMap.set(deviceId, {
label: node.machineName.trim(),
value: deviceId
})
}
}
if (!groupId) {
organizationList.value.forEach(pushDevice)
return Array.from(deviceMap.values())
}
const queue = [groupId]
while (queue.length > 0) {
const currentId = queue.shift() as string
pushDevice(organizationMap.value[currentId])
const children = childrenByParentId.value[currentId] || []
children.forEach((child) => queue.push(String(child.id)))
}
return Array.from(deviceMap.values())
}
const deviceOptions = computed(() => collectDeviceOptionsByGroup(queryParams.value.groupId))
const overviewData = ref<RunOverviewData>({
metrics: [],
hourlyStatus: [],
summary: [],
summaryTotalHours: 0,
timelineRows: [],
totalDevices: 0
})
const currentDeviceIds = computed(() =>
queryParams.value.deviceId.length > 0 ? queryParams.value.deviceId : deviceOptions.value.map((item) => item.value)
)
const buildOverviewRequestParams = () => ({
ids: currentDeviceIds.value.join(','),
startTime: queryParams.value.timeRange[0],
endTime: queryParams.value.timeRange[1],
timelinePageNo: pageNo.value,
timelinePageSize: pageSize.value
})
const refreshData = async () => {
const response = await DeviceOperationOverviewApi.getRunOverview(buildOverviewRequestParams())
overviewData.value = {
metrics: response?.metrics || [],
hourlyStatus: response?.hourlyStatus || [],
summary: response?.summary || [],
summaryTotalHours: response?.summaryTotalHours || 0,
timelineRows: response?.timelineRows || [],
totalDevices: response?.totalDevices || 0
}
const maxPage = Math.max(1, Math.ceil(overviewData.value.totalDevices / pageSize.value))
if (pageNo.value > maxPage) pageNo.value = maxPage
}
const resetToFirstPageAndRefresh = () => {
if (pageNo.value !== 1) {
pageNo.value = 1
return
}
void refreshData()
}
const handleQuickRangeChange = (key: QuickRangeKey) => {
queryParams.value = {
...queryParams.value,
quickRange: key,
timeRange: createDateRangeByQuickKey(key)
}
pageNo.value = 1
}
const handleQuery = () => {
resetToFirstPageAndRefresh()
}
const resetQuery = () => {
queryParams.value = buildDefaultQueryParams()
pageSize.value = 10
resetToFirstPageAndRefresh()
}
const handleExport = () => {
message.success(t('DataCollection.RunOverview.exportPlaceholderMessage'))
}
const handlePageSizeChange = (size: number) => {
pageSize.value = size
pageNo.value = 1
void refreshData()
}
watch(pageNo, () => {
void refreshData()
})
const getOrganizationOptions = async () => {
const data = await OrganizationApi.getOrganizationList()
organizationList.value = Array.isArray(data)
? data.map((item) => ({
id: String(item.id),
name: item.name,
parentId: item.parentId != null ? String(item.parentId) : item.parentId,
dvId: item.dvId,
machineName: item.machineName
}))
: []
groupOptions.value = handleTree(
organizationList.value.map((item) => ({ ...item })),
'id',
'parentId'
) as OrganizationTreeOption[]
}
watch(
deviceOptions,
(options) => {
if (queryParams.value.deviceId.length === 0) return
const validDeviceIds = queryParams.value.deviceId.filter((deviceId) =>
options.some((item) => item.value === deviceId)
)
if (validDeviceIds.length !== queryParams.value.deviceId.length) {
queryParams.value = {
...queryParams.value,
deviceId: validDeviceIds
}
}
},
{ immediate: true }
)
onMounted(async () => {
await getOrganizationOptions()
await refreshData()
})
</script>
<style scoped lang="scss">
.run-overview-page {
position: relative;
min-width: 0;
//padding-top: 8px;
}
.run-overview-page.is-fullscreen {
height: 100%;
padding: 16px;
overflow: auto;
background: #f5f7fa;
}
.run-overview-page__floating-tools {
position: absolute;
top: 8px;
right: 12px;
z-index: 20;
display: flex;
align-items: center;
gap: 8px;
}
.run-overview-page__screenfull {
display: inline-flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
padding: 0;
background: #fff;
border: 1px solid #e4e7ec;
border-radius: 8px;
box-shadow: 0 4px 14px rgba(15, 23, 42, 0.08);
cursor: pointer;
transition: all 0.2s ease;
}
.run-overview-page__screenfull:hover {
background: #f8fbff;
border-color: #bfd3ff;
box-shadow: 0 8px 18px rgba(61, 132, 255, 0.12);
}
</style>

@ -1,15 +0,0 @@
import dayjs from 'dayjs'
import type { RunOverviewQueryParams } from './components/types'
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'
export const buildDefaultQueryParams = (): RunOverviewQueryParams => {
const start = dayjs().startOf('day')
const end = dayjs().endOf('day')
return {
groupId: '',
deviceId: [],
quickRange: 'today',
timeRange: [start.format(DATE_TIME_FORMAT), end.format(DATE_TIME_FORMAT)]
}
}

@ -1,37 +1,64 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1200px" :close-on-click-modal="false">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px" v-loading="formLoading">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
v-loading="formLoading"
>
<el-form-item :label="t('FactoryModeling.ProductBOM.dialogCodeLabel')" prop="code">
<el-input v-model="formData.code" :placeholder="t('FactoryModeling.ProductBOM.dialogCodePlaceholder')"
:disabled="formType === 'update'" />
<el-input v-model="formData.code" :placeholder="t('FactoryModeling.ProductBOM.dialogCodePlaceholder')" :disabled = "formType === 'update'"/>
</el-form-item>
<el-form-item :label="t('FactoryModeling.ProductBOM.dialogVersionLabel')" prop="version">
<el-input v-model="formData.version" :placeholder="t('FactoryModeling.ProductBOM.dialogVersionPlaceholder')" />
</el-form-item>
<el-form-item :label="t('FactoryModeling.ProductBOM.dialogProductLabel')" prop="productId">
<el-select v-model="formData.productId" clearable filterable
:placeholder="t('FactoryModeling.ProductBOM.dialogProductPlaceholder')" class="!w-1/1">
<el-option v-for="item in productList" :key="item.id" :label="item.name" :value="item.id" />
<el-select
v-model="formData.productId"
clearable
filterable
:placeholder="t('FactoryModeling.ProductBOM.dialogProductPlaceholder')"
class="!w-1/1"
>
<el-option
v-for="item in productList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('FactoryModeling.ProductBOM.dialogUnitLabel')" prop="unitId">
<el-select v-model="formData.unitId" clearable
:placeholder="t('FactoryModeling.ProductBOM.dialogUnitPlaceholder')" class="w-1/1">
<el-option v-for="unit in unitList" :key="unit.id" :label="unit.name" :value="unit.id" />
</el-select>
<el-select v-model="formData.unitId" clearable :placeholder="t('FactoryModeling.ProductBOM.dialogUnitPlaceholder')" class="w-1/1">
<el-option
v-for="unit in unitList"
:key="unit.id"
:label="unit.name"
:value="unit.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('FactoryModeling.ProductBOM.dialogYieldRateLabel')" prop="yieldRate">
<el-input-number v-model="formData.yieldRate" :min="0" :max="100" :precision="2" class="!w-1/1"
:placeholder="t('FactoryModeling.ProductBOM.dialogYieldRatePlaceholder')" />
<el-input-number
v-model="formData.yieldRate"
:min="0"
:max="100"
:precision="2"
class="!w-1/1"
:placeholder="t('FactoryModeling.ProductBOM.dialogYieldRatePlaceholder')"
/>
</el-form-item>
<el-form-item :label="t('FactoryModeling.ProductBOM.dialogRemarkLabel')" prop="remark">
<el-input type="textarea" v-model="formData.remark"
:placeholder="t('FactoryModeling.ProductBOM.dialogRemarkPlaceholder')" />
<el-input type="textarea" v-model="formData.remark" :placeholder="t('FactoryModeling.ProductBOM.dialogRemarkPlaceholder')" />
</el-form-item>
<el-form-item :label="t('FactoryModeling.ProductBOM.dialogEnableLabel')" prop="isEnable">
<el-radio-group v-model="formData.isEnable">
<el-radio v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)" :key="dict.value"
:label="dict.value">
<el-radio
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
@ -107,7 +134,7 @@ const open = async (type: string, id?: number) => {
formLoading.value = false
}
}
if (type === 'detail') {
if(type==='detail'){
}
//
@ -139,7 +166,6 @@ const submitForm = async () => {
await BomApi.createBom(data)
message.success(t('common.createSuccess'))
} else {
data.bomDetails.forEach(item => delete item.createTime)
await BomApi.updateBom(data)
message.success(t('common.updateSuccess'))
}
@ -158,7 +184,7 @@ const resetForm = () => {
code: undefined,
version: '1.0',
productId: undefined,
unitId: undefined,
unitId: 5,
yieldRate: 100,
remark: undefined,
isEnable: true

@ -17,7 +17,6 @@
clearable
filterable
:placeholder="t('FactoryModeling.ProductBOM.detailMaterialPlaceholder')"
@change="(val) => handleProductChange(val, row)"
>
<el-option
v-for="item in productList"
@ -139,16 +138,6 @@ watch(
)
/** 原料选择变更时,自动带入单位 */
const handleProductChange = (productId: number | undefined, row: any) => {
if (productId) {
const product = productList.value.find((item) => item.id === productId)
row.unitId = product?.unitId ?? undefined
} else {
row.unitId = undefined
}
}
/** 新增按钮操作 */
const handleAdd = () => {
const row = {

@ -37,7 +37,7 @@
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('FactoryModeling.ProductBOM.searchEnableLabel')" prop="isEnable" v-show="showAllFilters">
<el-form-item :label="t('FactoryModeling.ProductBOM.searchEnableLabel')" prop="isEnable">
<el-select
v-model="queryParams.isEnable"
:placeholder="t('FactoryModeling.ProductBOM.searchEnablePlaceholder')"
@ -54,13 +54,6 @@
</el-select>
</el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') : t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('FactoryModeling.ProductBOM.searchButtonText') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('FactoryModeling.ProductBOM.resetButtonText') }}</el-button>
@ -185,13 +178,6 @@ const queryParams = reactive({
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
const showAllFilters = ref(false) //
const filterCount = 4 // codeproductIdremarkisEnable
/** 切换筛选框展开/折叠 */
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
/** 查询列表 */
const getList = async () => {
@ -255,6 +241,6 @@ const handleExport = async () => {
onMounted(async () => {
await getList()
//
productList.value = await ProductApi.getMesProductSimpleList()
productList.value = await ProductApi.getProductSimpleList()
})
</script>

@ -2,7 +2,7 @@
<ContentWrap>
<div >
<el-calendar v-model="currentDate" >
<template #header="{ date }">
<template v-slot:header="{ date }">
<div class="custom-header-with-buttons">
<!-- 左侧标题和统计信息 -->
<div class="header-left">
@ -24,15 +24,13 @@
@click="handleWorkday(date)"
>{{ t('FactoryModeling.CalHoliday.setWorkingDays') }}
</el-button>
<el-button
style="margin-left: 10px"
<el-button style="margin-left: 10px"
type="success"
size="small"
@click="handleHoliday(date)"
>{{ t('FactoryModeling.CalHoliday.setHoliday') }}
</el-button>
<el-button
style="margin-left: 10px"
<el-button style="margin-left: 10px"
size="small"
:icon="ArrowLeft"
@click="handleOriginalPrevMonth"
@ -57,7 +55,7 @@ style="margin-left: 10px"
</div>
</template>
<template #date-cell="{ data }">
<template v-slot:date-cell="{ data }">
<div>
<el-row>
<el-col :span="16">

@ -1,174 +0,0 @@
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle" width="1300" :scroll="true" max-height="70vh" align-center>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="auto">
<el-form-item :label="t('EquipmentManagement.CapacityReport.dialogProductCode')" prop="productCode">
<el-input
v-model="queryParams.productCode"
:placeholder="t('EquipmentManagement.CapacityReport.dialogProductCodePlaceholder')"
clearable
@keyup.enter="handleQuery"
class="!w-200px"
/>
</el-form-item>
<el-form-item :label="t('EquipmentManagement.CapacityReport.dialogProductName')" prop="productName">
<el-input
v-model="queryParams.productName"
:placeholder="t('EquipmentManagement.CapacityReport.dialogProductNamePlaceholder')"
clearable
@keyup.enter="handleQuery"
class="!w-200px"
/>
</el-form-item>
<el-form-item :label="t('EquipmentManagement.CapacityReport.dialogBaogongTime')" prop="baogongTime">
<el-date-picker v-model="baogongTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
:start-placeholder="t('EquipmentManagement.CapacityReport.dialogBaogongTimeStart')"
:end-placeholder="t('EquipmentManagement.CapacityReport.dialogBaogongTimeEnd')"
:shortcuts="baogongTimeShortcuts"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" />
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" />
{{ t('EquipmentManagement.CapacityReport.dialogSearchButtonText') }}
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" />
{{ t('EquipmentManagement.CapacityReport.dialogResetButtonText') }}
</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" style="margin-top: 16px">
<el-table-column :label="t('EquipmentManagement.CapacityReport.dialogProductCode')" align="center" prop="productCode" />
<el-table-column :label="t('EquipmentManagement.CapacityReport.dialogProductName')" align="center" prop="productName" />
<el-table-column :label="t('EquipmentManagement.CapacityReport.dialogBaogongTotal')" align="center" prop="baogongTotal" />
<el-table-column :label="t('EquipmentManagement.CapacityReport.dialogAvgCapacity')" align="center" prop="avgCapacity" />
</el-table>
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</Dialog>
</template>
<script setup lang="ts">
import { PlanApi } from '@/api/mes/plan'
const { t } = useI18n()
const props = defineProps<{
modelValue: boolean
deviceId?: number
deviceName?: string
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
}>()
const dialogVisible = computed({
get() {
return props.modelValue
},
set(value: boolean) {
emit('update:modelValue', value)
}
})
const dialogTitle = computed(() => {
const name = props.deviceName || '-'
return `${t('EquipmentManagement.CapacityReport.dialogTitlePrefix')}${name}`
})
const loading = ref(false)
const list = ref<any[]>([])
const total = ref(0)
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
deviceId: undefined as number | undefined,
productCode: undefined as string | undefined,
productName: undefined as string | undefined,
beginBaogongTime: undefined as string | undefined,
endBaogongTime: undefined as string | undefined
})
const baogongTime = ref<[string, string]>(['', ''])
const baogongTimeShortcuts = [
{
text: t('EquipmentManagement.CapacityReport.shortcutLastWeek'),
value: () => {
const end = new Date()
const start = new Date()
start.setDate(start.getDate() - 7)
return [start, end]
}
},
{
text: t('EquipmentManagement.CapacityReport.shortcutLastHalfYear'),
value: () => {
const end = new Date()
const start = new Date()
start.setMonth(start.getMonth() - 6)
return [start, end]
}
},
{
text: t('EquipmentManagement.CapacityReport.shortcutLastYear'),
value: () => {
const end = new Date()
const start = new Date()
start.setFullYear(start.getFullYear() - 1)
return [start, end]
}
}
]
const queryFormRef = ref()
const getList = async () => {
if (!props.deviceId) {
list.value = []
total.value = 0
return
}
loading.value = true
try {
const params = {
...queryParams,
beginBaogongTime: baogongTime.value?.[0] || undefined,
endBaogongTime: baogongTime.value?.[1] || undefined
}
const data = await PlanApi.getProductCapacityPage(params)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
const resetQuery = () => {
queryFormRef.value?.resetFields()
baogongTime.value = ['', '']
queryParams.deviceId = props.deviceId
handleQuery()
}
watch(
() => [props.modelValue, props.deviceId],
([visible, deviceId]) => {
if (!visible) return
queryParams.deviceId = deviceId as number
queryParams.pageNo = 1
queryParams.productCode = undefined
queryParams.productName = undefined
queryParams.taskCode = undefined
getList()
}
)
</script>

@ -1,234 +0,0 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" min-label-width="80px">
<el-form-item :label="t('EquipmentManagement.CapacityReport.deviceCode')" prop="deviceCode">
<el-input v-model="queryParams.deviceCode"
:placeholder="t('EquipmentManagement.CapacityReport.placeholderDeviceCode')" clearable
@keyup.enter="handleQuery" class="!w-240px" />
</el-form-item>
<el-form-item :label="t('EquipmentManagement.CapacityReport.deviceName')" prop="deviceName">
<el-input v-model="queryParams.deviceName"
:placeholder="t('EquipmentManagement.CapacityReport.placeholderDeviceName')" clearable
@keyup.enter="handleQuery" class="!w-240px" />
</el-form-item>
<el-form-item :label="t('EquipmentManagement.CapacityReport.baogongTime')" prop="baogongTime">
<el-date-picker v-model="baogongTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
:start-placeholder="t('EquipmentManagement.CapacityReport.placeholderBaogongTimeStart')"
:end-placeholder="t('EquipmentManagement.CapacityReport.placeholderBaogongTimeEnd')"
:shortcuts="baogongTimeShortcuts"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" />
</el-form-item>
<el-form-item v-if="filterCount > 3">
<el-button type="text" class="text-primary" @click="toggleFilters">
<Icon :icon="showAllFilters ? 'ep:arrow-up' : 'ep:arrow-down'" class="mr-5px" />
{{ showAllFilters ? t('FactoryModeling.FactoryStructure.collapseText') :
t('FactoryModeling.FactoryStructure.expandText') }}
</el-button>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}
</el-button>
<el-button type="success" plain @click="handleExport" :loading="exportLoading">
<Icon icon="ep:download" class="mr-5px" /> {{ t('EquipmentManagement.EquipmentLedger.export') }}
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" :row-key="(row) => row.id"
@select-all="handleSelectAll" @select="handleSelect">
<el-table-column type="selection" width="55" align="center" />
<el-table-column :label="t('EquipmentManagement.CapacityReport.deviceCode')" align="center" prop="deviceCode"
sortable />
<el-table-column :label="t('EquipmentManagement.CapacityReport.deviceName')" align="center" prop="deviceName"
sortable />
<el-table-column :label="t('EquipmentManagement.CapacityReport.deviceType')" align="center" prop="typeName"
sortable />
<el-table-column :label="t('EquipmentManagement.CapacityReport.ratedCapacity')" align="center"
prop="ratedCapacity" sortable />
<el-table-column align="center" prop="reportCapacity">
<template #header>
<span>{{ t('EquipmentManagement.CapacityReport.reportCapacity') }}</span>
<el-tooltip :content="t('EquipmentManagement.CapacityReport.reportCapacityTooltip')" placement="top">
<Icon icon="ep:question-filled" class="ml-4px"
style="vertical-align: middle; color: var(--el-text-color-secondary)" />
</el-tooltip>
</template>
<template #default="scope">
<el-button link type="primary" @click="openProductCapacity(scope.row)">
{{ t('EquipmentManagement.CapacityReport.reportCapacityViewDetail') }}
</el-button>
</template>
</el-table-column>
<el-table-column align="center" prop="actualCapacity" sortable>
<template #header>
<span>{{ t('EquipmentManagement.CapacityReport.actualCapacity') }}</span>
<el-tooltip :content="t('EquipmentManagement.CapacityReport.actualCapacityTooltip')" placement="top">
<Icon icon="ep:question-filled" class="ml-4px"
style="vertical-align: middle; color: var(--el-text-color-secondary)" />
</el-tooltip>
</template>
</el-table-column>
<el-table-column :label="t('EquipmentManagement.CapacityReport.workshop')" align="center" prop="workshopName"
sortable />
</el-table>
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
@pagination="getList" />
</ContentWrap>
<ProductCapacityDialog v-model="productCapacityVisible" :device-id="productCapacityDeviceId"
:device-name="productCapacityDeviceName" />
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import download from '@/utils/download'
import { DeviceLedgerApi, CapacityReportVO } from '@/api/mes/device-ledger'
import ProductCapacityDialog from './ProductCapacityDialog.vue'
defineOptions({ name: 'CapacityReport' })
const message = useMessage()
const { t } = useI18n()
const loading = ref(true)
const list = ref<CapacityReportVO[]>([])
const total = ref(0)
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
deviceCode: undefined,
deviceName: undefined,
deviceType: undefined,
workshop: undefined,
beginTime: undefined,
endTime: undefined
})
const baogongTime = ref<[string, string]>(['', ''])
const queryFormRef = ref()
const exportLoading = ref(false)
const showAllFilters = ref(false)
const filterCount = 3
const selectedIds = ref<number[]>([])
const productCapacityVisible = ref(false)
const productCapacityDeviceId = ref<number>()
const productCapacityDeviceName = ref<string>()
const baogongTimeShortcuts = [
{
text: t('EquipmentManagement.CapacityReport.shortcutLastWeek'),
value: () => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
return [start, end]
}
},
{
text: t('EquipmentManagement.CapacityReport.shortcutLastHalfYear'),
value: () => {
const end = new Date()
const start = new Date()
start.setMonth(start.getMonth() - 6)
return [start, end]
}
},
{
text: t('EquipmentManagement.CapacityReport.shortcutLastYear'),
value: () => {
const end = new Date()
const start = new Date()
start.setFullYear(start.getFullYear() - 1)
return [start, end]
}
}
]
const openProductCapacity = (row: CapacityReportVO) => {
productCapacityDeviceId.value = row.id
productCapacityDeviceName.value = row.deviceName
productCapacityVisible.value = true
}
/** 切换筛选框展开/折叠 */
const toggleFilters = () => {
showAllFilters.value = !showAllFilters.value
}
const getList = async () => {
loading.value = true
try {
const params = {
...queryParams,
beginTime: baogongTime.value?.[0] || undefined,
endTime: baogongTime.value?.[1] || undefined
}
const data = await DeviceLedgerApi.getCapacityReportPage(params)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
const handleQuery = () => {
queryParams.pageNo = 1
selectedIds.value = []
getList()
}
const resetQuery = () => {
queryFormRef.value?.resetFields()
baogongTime.value = ['', '']
selectedIds.value = []
handleQuery()
}
const handleSelectAll = (val: boolean) => {
if (val) {
selectedIds.value = list.value.map(item => item.id)
} else {
selectedIds.value = []
}
}
const handleSelect = (val: boolean, row: CapacityReportVO) => {
if (val) {
selectedIds.value.push(row.id)
} else {
const index = selectedIds.value.indexOf(row.id)
if (index > -1) {
selectedIds.value.splice(index, 1)
}
}
}
const handleExport = async () => {
try {
await message.exportConfirm()
exportLoading.value = true
const params: { ids?: string } = {}
if (selectedIds.value.length > 0) {
params.ids = selectedIds.value.join(',')
}
const data = await DeviceLedgerApi.exportCapacityReport(params)
download.excel(data, t('EquipmentManagement.CapacityReport.exportFilename'))
} catch {
} finally {
exportLoading.value = false
}
}
onMounted(() => {
getList()
})
</script>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save