Compare commits
16 Commits
main
...
devlopment
Author | SHA1 | Date |
---|---|---|
|
19cdc0998f | |
|
7efb1fa115 | |
|
66f6cce01b | |
|
53a81f0b01 | |
|
946c965935 | |
|
36db98a343 | |
|
4d4e6fdc2c | |
|
e9d68d7ae3 | |
|
9a4d9232fc | |
|
9be59bee8a | |
|
b44bcebf0f | |
|
45d54c50d5 | |
|
2fa43bf7cf | |
|
79527eb506 | |
|
817a709639 | |
|
6317d898a3 |
|
@ -1 +1,4 @@
|
|||
node_modules
|
||||
dist
|
||||
.gitignore
|
||||
.env.development
|
||||
|
|
|
@ -60,21 +60,27 @@
|
|||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^2.16.3",
|
||||
"@arco-design/web-vue": "^2.57.0",
|
||||
"@eslint/js": "^9.32.0",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.2.5",
|
||||
"@types/query-string": "^6.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.39.0",
|
||||
"@typescript-eslint/parser": "^8.39.0",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"boxen": "^7.1.1",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint": "^9.32.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"globals": "^16.3.0",
|
||||
"less": "^4.1.3",
|
||||
"less-loader": "^11.0.0",
|
||||
"picocolors": "^1.0.0",
|
||||
"sass": "^1.62.1",
|
||||
"sass-loader": "^13.2.2",
|
||||
"typescript": "~5.0.4",
|
||||
"typescript-eslint": "^8.39.0",
|
||||
"unplugin-auto-import": "^0.16.4",
|
||||
"unplugin-vue-components": "^0.25.1",
|
||||
"vite": "^5.1.5",
|
||||
|
@ -210,6 +216,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@antfu/eslint-config/node_modules/globals": {
|
||||
"version": "15.15.0",
|
||||
"resolved": "https://registry.npmmirror.com/globals/-/globals-15.15.0.tgz",
|
||||
"integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@antfu/install-pkg": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.4.1.tgz",
|
||||
|
@ -3324,17 +3342,16 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz",
|
||||
"integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==",
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.0.tgz",
|
||||
"integrity": "sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.38.0",
|
||||
"@typescript-eslint/type-utils": "8.38.0",
|
||||
"@typescript-eslint/utils": "8.38.0",
|
||||
"@typescript-eslint/visitor-keys": "8.38.0",
|
||||
"@typescript-eslint/scope-manager": "8.39.0",
|
||||
"@typescript-eslint/type-utils": "8.39.0",
|
||||
"@typescript-eslint/utils": "8.39.0",
|
||||
"@typescript-eslint/visitor-keys": "8.39.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^7.0.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
|
@ -3348,9 +3365,9 @@
|
|||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.38.0",
|
||||
"@typescript-eslint/parser": "^8.39.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
|
||||
|
@ -3364,16 +3381,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz",
|
||||
"integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==",
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.39.0.tgz",
|
||||
"integrity": "sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.38.0",
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
"@typescript-eslint/typescript-estree": "8.38.0",
|
||||
"@typescript-eslint/visitor-keys": "8.38.0",
|
||||
"@typescript-eslint/scope-manager": "8.39.0",
|
||||
"@typescript-eslint/types": "8.39.0",
|
||||
"@typescript-eslint/typescript-estree": "8.39.0",
|
||||
"@typescript-eslint/visitor-keys": "8.39.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -3385,18 +3401,17 @@
|
|||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz",
|
||||
"integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==",
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.39.0.tgz",
|
||||
"integrity": "sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.38.0",
|
||||
"@typescript-eslint/types": "^8.38.0",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.39.0",
|
||||
"@typescript-eslint/types": "^8.39.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -3407,18 +3422,17 @@
|
|||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz",
|
||||
"integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==",
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.39.0.tgz",
|
||||
"integrity": "sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
"@typescript-eslint/visitor-keys": "8.38.0"
|
||||
"@typescript-eslint/types": "8.39.0",
|
||||
"@typescript-eslint/visitor-keys": "8.39.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
|
@ -3429,11 +3443,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz",
|
||||
"integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==",
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.0.tgz",
|
||||
"integrity": "sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
|
@ -3442,19 +3455,18 @@
|
|||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz",
|
||||
"integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==",
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.39.0.tgz",
|
||||
"integrity": "sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
"@typescript-eslint/typescript-estree": "8.38.0",
|
||||
"@typescript-eslint/utils": "8.38.0",
|
||||
"@typescript-eslint/types": "8.39.0",
|
||||
"@typescript-eslint/typescript-estree": "8.39.0",
|
||||
"@typescript-eslint/utils": "8.39.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
|
@ -3467,15 +3479,14 @@
|
|||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz",
|
||||
"integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==",
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.39.0.tgz",
|
||||
"integrity": "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
|
@ -3485,16 +3496,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz",
|
||||
"integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==",
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.0.tgz",
|
||||
"integrity": "sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.38.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.38.0",
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
"@typescript-eslint/visitor-keys": "8.38.0",
|
||||
"@typescript-eslint/project-service": "8.39.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.39.0",
|
||||
"@typescript-eslint/types": "8.39.0",
|
||||
"@typescript-eslint/visitor-keys": "8.39.0",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
|
@ -3510,20 +3520,19 @@
|
|||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz",
|
||||
"integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==",
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.39.0.tgz",
|
||||
"integrity": "sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@typescript-eslint/scope-manager": "8.38.0",
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
"@typescript-eslint/typescript-estree": "8.38.0"
|
||||
"@typescript-eslint/scope-manager": "8.39.0",
|
||||
"@typescript-eslint/types": "8.39.0",
|
||||
"@typescript-eslint/typescript-estree": "8.39.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
|
@ -3534,17 +3543,16 @@
|
|||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz",
|
||||
"integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==",
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.0.tgz",
|
||||
"integrity": "sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.38.0",
|
||||
"@typescript-eslint/types": "8.39.0",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -4823,6 +4831,28 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/array-includes": {
|
||||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmmirror.com/array-includes/-/array-includes-3.1.9.tgz",
|
||||
"integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.8",
|
||||
"call-bound": "^1.0.4",
|
||||
"define-properties": "^1.2.1",
|
||||
"es-abstract": "^1.24.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"get-intrinsic": "^1.3.0",
|
||||
"is-string": "^1.1.1",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/array-unique": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
|
||||
|
@ -4833,6 +4863,78 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/array.prototype.findlast": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmmirror.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz",
|
||||
"integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.7",
|
||||
"define-properties": "^1.2.1",
|
||||
"es-abstract": "^1.23.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.0.0",
|
||||
"es-shim-unscopables": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/array.prototype.flat": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmmirror.com/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz",
|
||||
"integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.8",
|
||||
"define-properties": "^1.2.1",
|
||||
"es-abstract": "^1.23.5",
|
||||
"es-shim-unscopables": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/array.prototype.flatmap": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmmirror.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz",
|
||||
"integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.8",
|
||||
"define-properties": "^1.2.1",
|
||||
"es-abstract": "^1.23.5",
|
||||
"es-shim-unscopables": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/array.prototype.tosorted": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz",
|
||||
"integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.7",
|
||||
"define-properties": "^1.2.1",
|
||||
"es-abstract": "^1.23.3",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-shim-unscopables": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/arraybuffer.prototype.slice": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz",
|
||||
|
@ -6437,6 +6539,18 @@
|
|||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/doctrine": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-2.1.0.tgz",
|
||||
"integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esutils": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
|
||||
|
@ -6764,6 +6878,33 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-iterator-helpers": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz",
|
||||
"integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.8",
|
||||
"call-bound": "^1.0.3",
|
||||
"define-properties": "^1.2.1",
|
||||
"es-abstract": "^1.23.6",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-set-tostringtag": "^2.0.3",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"globalthis": "^1.0.4",
|
||||
"gopd": "^1.2.0",
|
||||
"has-property-descriptors": "^1.0.2",
|
||||
"has-proto": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"internal-slot": "^1.1.0",
|
||||
"iterator.prototype": "^1.1.4",
|
||||
"safe-array-concat": "^1.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz",
|
||||
|
@ -6798,6 +6939,18 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-shim-unscopables": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz",
|
||||
"integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-to-primitive": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz",
|
||||
|
@ -7645,6 +7798,18 @@
|
|||
"eslint": ">=8.23.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-n/node_modules/globals": {
|
||||
"version": "15.15.0",
|
||||
"resolved": "https://registry.npmmirror.com/globals/-/globals-15.15.0.tgz",
|
||||
"integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-no-only-tests": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-3.3.0.tgz",
|
||||
|
@ -7692,6 +7857,86 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react": {
|
||||
"version": "7.37.5",
|
||||
"resolved": "https://registry.npmmirror.com/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz",
|
||||
"integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"array-includes": "^3.1.8",
|
||||
"array.prototype.findlast": "^1.2.5",
|
||||
"array.prototype.flatmap": "^1.3.3",
|
||||
"array.prototype.tosorted": "^1.1.4",
|
||||
"doctrine": "^2.1.0",
|
||||
"es-iterator-helpers": "^1.2.1",
|
||||
"estraverse": "^5.3.0",
|
||||
"hasown": "^2.0.2",
|
||||
"jsx-ast-utils": "^2.4.1 || ^3.0.0",
|
||||
"minimatch": "^3.1.2",
|
||||
"object.entries": "^1.1.9",
|
||||
"object.fromentries": "^2.0.8",
|
||||
"object.values": "^1.2.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"resolve": "^2.0.0-next.5",
|
||||
"semver": "^6.3.1",
|
||||
"string.prototype.matchall": "^4.0.12",
|
||||
"string.prototype.repeat": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react/node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react/node_modules/resolve": {
|
||||
"version": "2.0.0-next.5",
|
||||
"resolved": "https://registry.npmmirror.com/resolve/-/resolve-2.0.0-next.5.tgz",
|
||||
"integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.13.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-regexp": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.9.0.tgz",
|
||||
|
@ -7786,6 +8031,18 @@
|
|||
"eslint": ">=8.56.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-unicorn/node_modules/globals": {
|
||||
"version": "15.15.0",
|
||||
"resolved": "https://registry.npmmirror.com/globals/-/globals-15.15.0.tgz",
|
||||
"integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-unused-imports": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz",
|
||||
|
@ -8839,11 +9096,10 @@
|
|||
"peer": true
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "15.15.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
|
||||
"integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
|
||||
"version": "16.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/globals/-/globals-16.3.0.tgz",
|
||||
"integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
|
@ -9995,6 +10251,23 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/iterator.prototype": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmmirror.com/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
|
||||
"integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.4",
|
||||
"es-object-atoms": "^1.0.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"get-proto": "^1.0.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"set-function-name": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-worker": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
|
||||
|
@ -10191,6 +10464,21 @@
|
|||
"html2canvas": "^1.0.0-rc.5"
|
||||
}
|
||||
},
|
||||
"node_modules/jsx-ast-utils": {
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmmirror.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
|
||||
"integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"array-includes": "^3.1.6",
|
||||
"array.prototype.flat": "^1.3.1",
|
||||
"object.assign": "^4.1.4",
|
||||
"object.values": "^1.1.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
|
@ -10576,6 +10864,18 @@
|
|||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/lower-case": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
|
||||
|
@ -11412,6 +11712,39 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/object.entries": {
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmmirror.com/object.entries/-/object.entries-1.1.9.tgz",
|
||||
"integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.8",
|
||||
"call-bound": "^1.0.4",
|
||||
"define-properties": "^1.2.1",
|
||||
"es-object-atoms": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/object.fromentries": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmmirror.com/object.fromentries/-/object.fromentries-2.0.8.tgz",
|
||||
"integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.7",
|
||||
"define-properties": "^1.2.1",
|
||||
"es-abstract": "^1.23.2",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/object.pick": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
|
||||
|
@ -11435,6 +11768,24 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object.values": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/object.values/-/object.values-1.2.1.tgz",
|
||||
"integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.8",
|
||||
"call-bound": "^1.0.3",
|
||||
"define-properties": "^1.2.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
|
@ -12053,6 +12404,17 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-changeset": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.3.1.tgz",
|
||||
|
@ -12365,6 +12727,12 @@
|
|||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/read-pkg": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
|
||||
|
@ -13897,6 +14265,43 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/string.prototype.matchall": {
|
||||
"version": "4.0.12",
|
||||
"resolved": "https://registry.npmmirror.com/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
|
||||
"integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.8",
|
||||
"call-bound": "^1.0.3",
|
||||
"define-properties": "^1.2.1",
|
||||
"es-abstract": "^1.23.6",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.0.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"internal-slot": "^1.1.0",
|
||||
"regexp.prototype.flags": "^1.5.3",
|
||||
"set-function-name": "^2.0.2",
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/string.prototype.repeat": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz",
|
||||
"integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"define-properties": "^1.1.3",
|
||||
"es-abstract": "^1.17.5"
|
||||
}
|
||||
},
|
||||
"node_modules/string.prototype.trim": {
|
||||
"version": "1.2.10",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
|
||||
|
@ -14888,6 +15293,29 @@
|
|||
"node": ">=12.20"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.39.0.tgz",
|
||||
"integrity": "sha512-lH8FvtdtzcHJCkMOKnN73LIn6SLTpoojgJqDAxPm1jCR14eWSGPX8ul/gggBdPMk/d5+u9V854vTYQ8T5jF/1Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.39.0",
|
||||
"@typescript-eslint/parser": "8.39.0",
|
||||
"@typescript-eslint/typescript-estree": "8.39.0",
|
||||
"@typescript-eslint/utils": "8.39.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uc.micro": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||
|
|
|
@ -66,21 +66,27 @@
|
|||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^2.16.3",
|
||||
"@arco-design/web-vue": "^2.57.0",
|
||||
"@eslint/js": "^9.32.0",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.2.5",
|
||||
"@types/query-string": "^6.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.39.0",
|
||||
"@typescript-eslint/parser": "^8.39.0",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"boxen": "^7.1.1",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"globals": "^16.3.0",
|
||||
"less": "^4.1.3",
|
||||
"less-loader": "^11.0.0",
|
||||
"picocolors": "^1.0.0",
|
||||
"sass": "^1.62.1",
|
||||
"sass-loader": "^13.2.2",
|
||||
"typescript": "~5.0.4",
|
||||
"typescript-eslint": "^8.39.0",
|
||||
"unplugin-auto-import": "^0.16.4",
|
||||
"unplugin-vue-components": "^0.25.1",
|
||||
"vite": "^5.1.5",
|
||||
|
|
|
@ -26,6 +26,15 @@ export function getFolderTreeApi() {
|
|||
})
|
||||
}
|
||||
|
||||
// 获取文件夹列表
|
||||
export function getFolderListApi(params?: { page?: number; pageSize?: number; folderName?: string }) {
|
||||
return request<{ list: KnowledgeFolder[]; total: number }>({
|
||||
url: '/knowledge/folder-list',
|
||||
method: 'get',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
// 获取文件列表(按文件夹)
|
||||
export function getFilesApi(folderId: string) {
|
||||
return request<KnowledgeFile[]>({
|
||||
|
@ -35,6 +44,8 @@ export function getFilesApi(folderId: string) {
|
|||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 创建文件夹
|
||||
export function createFolderApi(data: { name: string; parentId?: string }) {
|
||||
return request({
|
||||
|
@ -44,6 +55,23 @@ export function createFolderApi(data: { name: string; parentId?: string }) {
|
|||
})
|
||||
}
|
||||
|
||||
// 更新文件夹
|
||||
export function updateFolderApi(folderId: string, data: { name: string }) {
|
||||
return request({
|
||||
url: `/knowledge/update-folder/${folderId}`,
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
// 删除文件夹
|
||||
export function deleteFolderApi(folderId: string) {
|
||||
return request({
|
||||
url: `/knowledge/delete-folder/${folderId}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
export function deleteFileApi(fileId: string) {
|
||||
return request({
|
||||
|
@ -60,3 +88,32 @@ export function downloadFileApi(fileId: string) {
|
|||
responseType: 'blob',
|
||||
})
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
export function uploadFileApi(data: FormData) {
|
||||
return request({
|
||||
url: '/knowledge/upload',
|
||||
method: 'post',
|
||||
data,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 更新文件名
|
||||
export function updateFileNameApi(fileId: string, newName: string) {
|
||||
return request({
|
||||
url: `/knowledge/update-file-name/${fileId}`,
|
||||
method: 'put',
|
||||
data: { name: newName },
|
||||
})
|
||||
}
|
||||
|
||||
// 预览文件
|
||||
export function previewFileApi(fileId: string) {
|
||||
return request({
|
||||
url: `/knowledge/preview/${fileId}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
// @/apis/bussiness/index.ts - 商务数据库信息模块API
|
||||
import http from '@/utils/http'
|
||||
import type {
|
||||
FolderInfo,
|
||||
FileInfo,
|
||||
FolderListParams,
|
||||
FileListParams,
|
||||
FolderListResponse,
|
||||
FileListResponse,
|
||||
CreateFolderParams,
|
||||
RenameFolderParams,
|
||||
DeleteFolderParams,
|
||||
UploadFileParams,
|
||||
DownloadFileParams,
|
||||
DeleteFileParams,
|
||||
PreviewFileParams,
|
||||
RenameFileParams
|
||||
} from './type'
|
||||
|
||||
const { request, requestRaw } = http
|
||||
|
||||
// 导出类型定义
|
||||
export type {
|
||||
FolderInfo,
|
||||
FileInfo,
|
||||
FolderListParams,
|
||||
FileListParams,
|
||||
FolderListResponse,
|
||||
FileListResponse,
|
||||
CreateFolderParams,
|
||||
RenameFolderParams,
|
||||
DeleteFolderParams,
|
||||
UploadFileParams,
|
||||
DownloadFileParams,
|
||||
DeleteFileParams,
|
||||
PreviewFileParams,
|
||||
RenameFileParams
|
||||
}
|
||||
|
||||
// 获取文件夹列表(分页)
|
||||
export function getFolderListApi(params?: FolderListParams) {
|
||||
return request<FolderListResponse>({
|
||||
url: '/businessData/folder/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
page: params?.page || 1,
|
||||
pageSize: params?.pageSize || 10,
|
||||
folderName: params?.folderName
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取文件列表(分页)
|
||||
export function getFilesApi(params?: FileListParams) {
|
||||
return request<FileListResponse>({
|
||||
url: '/businessData/file/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
page: params?.page || 1,
|
||||
pageSize: params?.pageSize || 10,
|
||||
folderId: params?.folderId || '0',
|
||||
fileName: params?.fileName
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 创建文件夹
|
||||
export function createFolderApi(data: CreateFolderParams) {
|
||||
return request({
|
||||
url: '/businessData/folder/creatFolder',
|
||||
method: 'post',
|
||||
data: {
|
||||
name: data.name,
|
||||
parentId: data.parentId || '0'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 重命名文件夹
|
||||
export function updateFolderApi(folderId: string, newName: string) {
|
||||
return request({
|
||||
url: '/businessData/folder/rename',
|
||||
method: 'put',
|
||||
params: {
|
||||
folderId: folderId,
|
||||
newName: newName
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 删除文件夹
|
||||
export function deleteFolderApi(folderId: string) {
|
||||
return request({
|
||||
url: '/businessData/folder/delete',
|
||||
method: 'delete',
|
||||
params: {
|
||||
folderId: folderId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
export function uploadFileApi(
|
||||
file: File,
|
||||
folderId: string,
|
||||
onUploadProgress?: (progressEvent: any) => void,
|
||||
cancelToken?: any
|
||||
) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
return requestRaw({
|
||||
url: '/businessData/file/add',
|
||||
method: 'post',
|
||||
params: {
|
||||
folderId: folderId
|
||||
},
|
||||
data: formData,
|
||||
onUploadProgress,
|
||||
cancelToken,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
}).then(response => response.data)
|
||||
.catch(error => {
|
||||
// 确保错误不会抛出,而是返回一个错误对象
|
||||
console.error('上传文件API错误:', error)
|
||||
return {
|
||||
code: 500,
|
||||
msg: error.message || '上传失败',
|
||||
success: false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
export function downloadFileApi(fileId: string) {
|
||||
return request({
|
||||
url: '/businessData/file/download',
|
||||
method: 'get',
|
||||
params: {
|
||||
fileId: fileId
|
||||
},
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
export function deleteFileApi(fileId: string) {
|
||||
return request({
|
||||
url: '/businessData/file/delete',
|
||||
method: 'delete',
|
||||
params: {
|
||||
fileId: fileId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 预览文件(后端没有提供预览接口,使用下载接口)
|
||||
export function previewFileApi(fileId: string) {
|
||||
return request({
|
||||
url: '/businessData/file/download',
|
||||
method: 'get',
|
||||
params: {
|
||||
fileId: fileId
|
||||
},
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
// 重命名文件
|
||||
export function renameFileApi(fileId: string, newFileName: string) {
|
||||
return request({
|
||||
url: '/businessData/file/rename',
|
||||
method: 'put',
|
||||
params: {
|
||||
fileId: fileId,
|
||||
newFileName: newFileName
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 重命名文件(兼容旧接口)
|
||||
export function updateFileNameApi(fileId: string, data: RenameFileParams) {
|
||||
return renameFileApi(fileId, data.newFileName)
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/** 文件夹信息接口 */
|
||||
export interface FolderInfo {
|
||||
folderId: string
|
||||
name: string
|
||||
parentId: string
|
||||
createTime?: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
/** 文件信息接口 */
|
||||
export interface FileInfo {
|
||||
fileId: string
|
||||
fileName: string
|
||||
fileSize: number
|
||||
fileType: string
|
||||
folderId: string
|
||||
createTime?: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
/** 文件夹列表查询参数 */
|
||||
export interface FolderListParams {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
folderName?: string
|
||||
}
|
||||
|
||||
/** 文件列表查询参数 */
|
||||
export interface FileListParams {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
folderId?: string
|
||||
fileName?: string
|
||||
}
|
||||
|
||||
/** 文件夹列表响应 */
|
||||
export interface FolderListResponse {
|
||||
data: FolderInfo[]
|
||||
total: number
|
||||
current: number
|
||||
size: number
|
||||
}
|
||||
|
||||
/** 文件列表响应 */
|
||||
export interface FileListResponse {
|
||||
data: FileInfo[]
|
||||
total: number
|
||||
current: number
|
||||
size: number
|
||||
}
|
||||
|
||||
/** 创建文件夹请求参数 */
|
||||
export interface CreateFolderParams {
|
||||
name: string
|
||||
parentId?: string
|
||||
}
|
||||
|
||||
/** 重命名文件夹请求参数 */
|
||||
export interface RenameFolderParams {
|
||||
folderId: string
|
||||
newName: string
|
||||
}
|
||||
|
||||
/** 删除文件夹请求参数 */
|
||||
export interface DeleteFolderParams {
|
||||
folderId: string
|
||||
}
|
||||
|
||||
/** 上传文件请求参数 */
|
||||
export interface UploadFileParams {
|
||||
file: File
|
||||
folderId: string
|
||||
onUploadProgress?: (progressEvent: any) => void
|
||||
cancelToken?: any
|
||||
}
|
||||
|
||||
/** 下载文件请求参数 */
|
||||
export interface DownloadFileParams {
|
||||
fileId: string
|
||||
}
|
||||
|
||||
/** 删除文件请求参数 */
|
||||
export interface DeleteFileParams {
|
||||
fileId: string
|
||||
}
|
||||
|
||||
/** 预览文件请求参数 */
|
||||
export interface PreviewFileParams {
|
||||
fileId: string
|
||||
}
|
||||
|
||||
/** 重命名文件请求参数 */
|
||||
export interface RenameFileParams {
|
||||
fileId: string
|
||||
newFileName: string
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import request from '@/utils/http'
|
||||
|
||||
// 合同查询参数类型
|
||||
export interface ContractQueryParams {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
code?: string
|
||||
customer?: string
|
||||
contractStatus?: string
|
||||
type?: string
|
||||
signDate?: string
|
||||
paymentDate?: string
|
||||
performanceDeadline?: string
|
||||
salespersonId?: string
|
||||
departmentId?: string
|
||||
projectId?: string
|
||||
}
|
||||
|
||||
// 合同响应数据类型
|
||||
export interface ContractData {
|
||||
accountNumber: string
|
||||
amount: number
|
||||
code: string
|
||||
contractId: string
|
||||
contractStatus: string
|
||||
contractText: string
|
||||
customer: string
|
||||
departmentId: string
|
||||
duration: string
|
||||
notes: string
|
||||
page: number
|
||||
pageSize: number
|
||||
paymentAddress: string
|
||||
paymentDate: string
|
||||
performanceDeadline: string
|
||||
productService: string
|
||||
projectId: string
|
||||
projectName: string
|
||||
receivedAmount: number
|
||||
salespersonDeptName: string
|
||||
salespersonId: string
|
||||
salespersonName: string
|
||||
settlementAmount: number
|
||||
signDate: string
|
||||
type: string
|
||||
}
|
||||
|
||||
// 合同列表响应类型
|
||||
export interface ContractListResponse {
|
||||
code: number
|
||||
msg: string
|
||||
rows: ContractData[]
|
||||
total: number
|
||||
}
|
||||
|
||||
const BASE_URL = '/contract'
|
||||
|
||||
/**
|
||||
* 获取合同列表
|
||||
* @param params 查询参数
|
||||
*/
|
||||
export function getContractList(params: ContractQueryParams) {
|
||||
return request.get<ContractListResponse>(`${BASE_URL}/list`, params)
|
||||
}
|
|
@ -46,4 +46,7 @@ export function assignEquipment(equipmentId: string, userId: string) {
|
|||
/** @desc 设备归还 */
|
||||
export function returnEquipment(equipmentId: string) {
|
||||
return http.put(`${BASE_URL}/${equipmentId}/return`)
|
||||
}
|
||||
}
|
||||
|
||||
// 导出设备采购 API
|
||||
export * from './procurement'
|
|
@ -0,0 +1,125 @@
|
|||
import http from '@/utils/http'
|
||||
import type { EquipmentListReq, EquipmentReq, EquipmentResp } from './type'
|
||||
|
||||
/**
|
||||
* 设备采购管理API
|
||||
*/
|
||||
export const equipmentProcurementApi = {
|
||||
/**
|
||||
* 分页查询设备采购记录
|
||||
*/
|
||||
page: (params: EquipmentListReq) => {
|
||||
console.log('🔍 API - equipmentProcurementApi.page 被调用')
|
||||
console.log('🔍 API - 接收到的参数:', params)
|
||||
console.log('🔍 API - 参数类型:', typeof params)
|
||||
console.log('🔍 API - 参数的键值对:')
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
console.log(` ${key}: ${value} (${typeof value})`)
|
||||
})
|
||||
|
||||
// 确保参数格式正确
|
||||
const requestParams = {
|
||||
...params,
|
||||
// 确保分页参数存在
|
||||
page: params.page || 1,
|
||||
pageSize: params.pageSize || 10,
|
||||
}
|
||||
|
||||
console.log('🔍 API - 最终请求参数:', requestParams)
|
||||
console.log('🔍 API - 准备发送GET请求到 /equipment/procurement/page')
|
||||
console.log('🔍 API - 请求参数序列化前:', requestParams)
|
||||
|
||||
// 手动序列化参数进行调试(使用URLSearchParams)
|
||||
const searchParams = new URLSearchParams()
|
||||
Object.entries(requestParams).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
searchParams.append(key, String(value))
|
||||
}
|
||||
})
|
||||
console.log('🔍 API - 手动序列化后的参数:', searchParams.toString())
|
||||
|
||||
// 参考设备模块的调用方式,直接将参数作为第二个参数传递
|
||||
return http.get<ApiRes<PageRes<EquipmentResp>>>('/equipment/procurement/page', requestParams)
|
||||
},
|
||||
|
||||
/**
|
||||
* 测试参数传递 - 使用API文档中的参数格式
|
||||
*/
|
||||
testPage: () => {
|
||||
console.log('🧪 API - 测试参数传递')
|
||||
|
||||
// 使用API文档中的参数格式进行测试
|
||||
const testParams = {
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
equipmentName: '测试设备',
|
||||
supplierName: '测试供应商',
|
||||
quantity: 10,
|
||||
unitPrice: 100.50,
|
||||
totalPrice: 1005.00,
|
||||
accountNumber: 'TEST001',
|
||||
brand: '测试品牌',
|
||||
locationStatus: 'spare',
|
||||
physicalLocation: '测试位置',
|
||||
purchaseOrder: 'PO001',
|
||||
inventoryBasis: '测试依据',
|
||||
dynamicRecord: '测试记录'
|
||||
}
|
||||
|
||||
console.log('🧪 API - 测试参数:', testParams)
|
||||
|
||||
return http.get<ApiRes<PageRes<EquipmentResp>>>('/equipment/procurement/page', testParams)
|
||||
},
|
||||
|
||||
/**
|
||||
* 新增设备采购记录
|
||||
*/
|
||||
add: (data: EquipmentReq) => {
|
||||
return http.post<ApiRes<null>>('/equipment/procurement', data)
|
||||
},
|
||||
|
||||
/**
|
||||
* 修改设备采购记录
|
||||
*/
|
||||
update: (equipmentId: string, data: EquipmentReq) => {
|
||||
return http.put<ApiRes<null>>(`/equipment/procurement/${equipmentId}`, data)
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除设备采购记录
|
||||
*/
|
||||
delete: (equipmentId: string) => {
|
||||
return http.del<ApiRes<null>>(`/equipment/procurement/${equipmentId}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取设备采购记录详情
|
||||
*/
|
||||
detail: (equipmentId: string) => {
|
||||
return http.get<ApiRes<EquipmentResp>>(`/equipment/procurement/detail/${equipmentId}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取采购统计信息
|
||||
*/
|
||||
getStats: () => {
|
||||
return http.get<ApiRes<unknown>>('/equipment/procurement/stats')
|
||||
},
|
||||
|
||||
/**
|
||||
* 批量删除设备采购记录
|
||||
*/
|
||||
batchDelete: (equipmentIds: string[]) => {
|
||||
return http.del<ApiRes<null>>('/equipment/procurement/batch', { data: equipmentIds })
|
||||
},
|
||||
|
||||
/**
|
||||
* 导出设备采购记录
|
||||
*/
|
||||
export: (params: EquipmentListReq) => {
|
||||
return http.get<Blob>('/equipment/procurement/export', {
|
||||
params,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
}
|
|
@ -17,6 +17,9 @@ export * as HealthRecordAPI from './health-record'
|
|||
export * as InsuranceFileAPI from './insurance-file'
|
||||
export * as EmployeeAPI from './employee'
|
||||
export * as RegulationAPI from './regulation'
|
||||
export * as TrainingAPI from './training'
|
||||
export * as EquipmentAPI from './equipment'
|
||||
export * as BussinessAPI from './bussiness/bussiness'
|
||||
|
||||
export * from './area/type'
|
||||
export * from './auth/type'
|
||||
|
|
|
@ -34,6 +34,7 @@ export const regulationApi = {
|
|||
level: string
|
||||
remark?: string
|
||||
createBy?: string
|
||||
createByName?: string
|
||||
}) => {
|
||||
return http.post('/regulation/proposal', data)
|
||||
},
|
||||
|
|
|
@ -45,6 +45,7 @@ export interface CreateProposalRequest {
|
|||
level: RegulationLevel
|
||||
remark?: string
|
||||
createBy?: string
|
||||
createByName?: string
|
||||
}
|
||||
|
||||
// 分页参数接口
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import http from '@/utils/http'
|
||||
import type * as T from '@/types/training.d'
|
||||
|
||||
const BASE_URL = '/training'
|
||||
|
||||
/** @desc 分页查询培训计划列表 */
|
||||
export function pageTrainingPlan(query: T.TrainingPlanPageQuery) {
|
||||
return http.get<T.TrainingPlanResp[]>(`${BASE_URL}/plan/page`, query)
|
||||
}
|
||||
|
||||
/** @desc 查询培训计划列表 */
|
||||
export function listTrainingPlan(query?: T.TrainingPlanPageQuery) {
|
||||
return http.get<T.TrainingPlanResp[]>(`${BASE_URL}/plan/list`, query)
|
||||
}
|
||||
|
||||
/** @desc 查询培训计划详情 */
|
||||
export function getTrainingPlanDetail(planId: string) {
|
||||
return http.get<T.TrainingPlanResp>(`${BASE_URL}/plan/detail/${planId}`)
|
||||
}
|
||||
|
||||
/** @desc 新增培训计划 */
|
||||
export function createTrainingPlan(data: T.TrainingPlanReq) {
|
||||
return http.post(`${BASE_URL}/plan`, data)
|
||||
}
|
||||
|
||||
/** @desc 更新培训计划 */
|
||||
export function updateTrainingPlan(planId: string, data: T.TrainingPlanReq) {
|
||||
return http.put(`${BASE_URL}/plan/${planId}`, data)
|
||||
}
|
||||
|
||||
/** @desc 删除培训计划 */
|
||||
export function deleteTrainingPlan(planId: string) {
|
||||
return http.del(`${BASE_URL}/plan/${planId}`)
|
||||
}
|
||||
|
||||
/** @desc 发布培训计划 */
|
||||
export function publishTrainingPlan(planId: string) {
|
||||
return http.put(`${BASE_URL}/plan/${planId}/publish`)
|
||||
}
|
||||
|
||||
/** @desc 取消培训计划 */
|
||||
export function cancelTrainingPlan(planId: string) {
|
||||
return http.put(`${BASE_URL}/plan/${planId}/cancel`)
|
||||
}
|
|
@ -80,7 +80,7 @@ import { onMounted, ref, nextTick } from 'vue'
|
|||
import Message from './Message.vue'
|
||||
import SettingDrawer from './SettingDrawer.vue'
|
||||
import Search from './Search.vue'
|
||||
import { getUnreadMessageCount } from '@/apis'
|
||||
|
||||
import { useUserStore } from '@/stores'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import { useBreakpoint, useDevice } from '@/hooks'
|
||||
|
|
|
@ -11,8 +11,7 @@
|
|||
>
|
||||
<template #icon>
|
||||
<MenuIcon
|
||||
:svg-icon="onlyOneChild?.meta?.svgIcon || item?.meta?.svgIcon"
|
||||
:icon="onlyOneChild?.meta?.icon || item?.meta?.icon"
|
||||
:icon="onlyOneChild?.meta?.svgIcon || onlyOneChild?.meta?.icon || item?.meta?.svgIcon || item?.meta?.icon"
|
||||
/>
|
||||
</template>
|
||||
<a-tooltip :content="onlyOneChild?.meta?.title" position="right">
|
||||
|
@ -22,7 +21,7 @@
|
|||
|
||||
<a-sub-menu v-else v-bind="attrs" :key="item.path" :title="item?.meta?.title">
|
||||
<template #icon>
|
||||
<MenuIcon :icon="item?.meta?.icon" />
|
||||
<MenuIcon :icon="item?.meta?.svgIcon || item?.meta?.icon" />
|
||||
</template>
|
||||
<MenuItem v-for="child in item.children" :key="child.path" :item="child"></MenuItem>
|
||||
</a-sub-menu>
|
||||
|
|
|
@ -852,26 +852,7 @@ export const systemRoutes: RouteRecordRaw[] = [
|
|||
|
||||
],
|
||||
},
|
||||
// 添加商务知识库
|
||||
{
|
||||
path: '/bussiness-knowledge',
|
||||
name: 'bussinesskonwledge',
|
||||
component: Layout,
|
||||
redirect: '/bussiness-knowledge/data',
|
||||
meta: { title: '商务资料知识库', icon: 'message', hidden: false, sort: 6 },
|
||||
children: [
|
||||
{
|
||||
path: '/bussiness-konwledge/data',
|
||||
name: 'bussiness-knowledge',
|
||||
component: () => import('@/views/bussiness-data/bussiness.vue'),
|
||||
meta: {
|
||||
title: '商务数据库信息',
|
||||
icon: 'info-circle',
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
path: 'project-management/project-implementation/',
|
||||
name: 'Project-Implementation',
|
||||
|
@ -1166,6 +1147,26 @@ export const systemRoutes: RouteRecordRaw[] = [
|
|||
},
|
||||
// ],
|
||||
// },
|
||||
// 商务数据库信息模块
|
||||
{
|
||||
path: '/bussiness-knowledge',
|
||||
name: 'bussinesskonwledge',
|
||||
component: Layout,
|
||||
redirect: '/bussiness-knowledge/data',
|
||||
meta: { title: '商务资料知识库', icon: 'database', hidden: false, sort: 5.5 },
|
||||
children: [
|
||||
{
|
||||
path: '/bussiness-konwledge/data',
|
||||
name: 'bussiness-knowledge',
|
||||
component: () => import('@/views/bussiness-data/bussiness.vue'),
|
||||
meta: {
|
||||
title: '商务数据库信息',
|
||||
icon: 'info-circle',
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/chat-platform',
|
||||
name: 'ChatPlatform',
|
||||
|
|
|
@ -164,6 +164,82 @@ const storeSetup = () => {
|
|||
sort: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2000,
|
||||
parentId: 0,
|
||||
title: '资产管理',
|
||||
type: 1,
|
||||
path: '/asset-management',
|
||||
name: 'AssetManagement',
|
||||
component: 'Layout',
|
||||
redirect: '/asset-management/device-management/device-center',
|
||||
icon: 'desktop',
|
||||
isExternal: false,
|
||||
isCache: false,
|
||||
isHidden: false,
|
||||
sort: 2,
|
||||
children: [
|
||||
{
|
||||
id: 2010,
|
||||
parentId: 2000,
|
||||
title: '设备管理',
|
||||
type: 1,
|
||||
path: '/asset-management/device-management',
|
||||
name: 'DeviceManagement',
|
||||
component: 'Layout',
|
||||
redirect: '/asset-management/device-management/device-center',
|
||||
icon: 'desktop',
|
||||
isExternal: false,
|
||||
isCache: false,
|
||||
isHidden: false,
|
||||
sort: 1,
|
||||
children: [
|
||||
{
|
||||
id: 2011,
|
||||
parentId: 2010,
|
||||
title: '设备中心',
|
||||
type: 2,
|
||||
path: '/asset-management/device-management/device-center',
|
||||
name: 'DeviceCenter',
|
||||
component: 'system-resource/device-management/index',
|
||||
icon: 'desktop',
|
||||
isExternal: false,
|
||||
isCache: false,
|
||||
isHidden: false,
|
||||
sort: 1,
|
||||
},
|
||||
{
|
||||
id: 2012,
|
||||
parentId: 2010,
|
||||
title: '设备采购',
|
||||
type: 2,
|
||||
path: '/asset-management/device-management/procurement',
|
||||
name: 'DeviceProcurement',
|
||||
component: 'system-resource/device-management/procurement/index',
|
||||
icon: 'shopping-cart',
|
||||
isExternal: false,
|
||||
isCache: false,
|
||||
isHidden: false,
|
||||
sort: 2,
|
||||
},
|
||||
{
|
||||
id: 2013,
|
||||
parentId: 2010,
|
||||
title: '设备详情',
|
||||
type: 2,
|
||||
path: '/asset-management/device-management/device-detail/:id',
|
||||
name: 'DeviceDetail',
|
||||
component: 'system-resource/device-management/detail',
|
||||
icon: 'file-text',
|
||||
isExternal: false,
|
||||
isCache: false,
|
||||
isHidden: true,
|
||||
sort: 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}]
|
||||
// 使用已转换的数据生成路由
|
||||
const asyncRoutes = formatAsyncRoutes(data as unknown as RouteItem[])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import './var.scss';
|
||||
@use './var.scss' as *;
|
||||
|
||||
body {
|
||||
--margin: 14px; // 通用外边距
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* 全局样式 */
|
||||
@import './var.scss';
|
||||
@use './var.scss' as *;
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
// 基础样式
|
||||
@import './base.scss';
|
||||
@use './base.scss';
|
||||
|
||||
// 全局类名样式
|
||||
@import './global.scss';
|
||||
@use './global.scss';
|
||||
|
||||
// 自定义原生滚动条样式
|
||||
@import './scrollbar-reset.scss';
|
||||
@use './scrollbar-reset.scss';
|
||||
|
||||
// 自定义 nprogress 插件进度条颜色
|
||||
@import './nprogress.scss';
|
||||
@use './nprogress.scss';
|
||||
|
||||
// 富文本的css主题颜色变量
|
||||
@import './editor.scss';
|
||||
@use './editor.scss';
|
||||
|
||||
// 动画类名
|
||||
@import './animated.scss';
|
||||
@use './animated.scss';
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
<template>
|
||||
<div>
|
||||
<h2>Console.log 测试</h2>
|
||||
<button @click="testConsole">测试 Console.log</button>
|
||||
<p>{{ message }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const message = ref('')
|
||||
|
||||
const testConsole = () => {
|
||||
console.log('测试 console.log 是否正常工作')
|
||||
console.warn('测试 console.warn')
|
||||
console.error('测试 console.error')
|
||||
message.value = '请查看浏览器控制台,应该能看到上述日志信息'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
margin: 10px;
|
||||
background-color: #1890ff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #40a9ff;
|
||||
}
|
||||
</style>
|
|
@ -57,7 +57,6 @@ declare global {
|
|||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useId: typeof import('vue')['useId']
|
||||
const useLink: typeof import('vue-router')['useLink']
|
||||
const useModel: typeof import('vue')['useModel']
|
||||
const useRoute: typeof import('vue-router')['useRoute']
|
||||
const useRouter: typeof import('vue-router')['useRouter']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
|
@ -70,6 +69,6 @@ declare global {
|
|||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
|
||||
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
export interface TrainingPlanPageQuery {
|
||||
planName?: string
|
||||
trainingType?: string
|
||||
trainingLevel?: string
|
||||
status?: string
|
||||
trainer?: string
|
||||
startTime?: string
|
||||
endTime?: string
|
||||
page?: number
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export interface TrainingPlanReq {
|
||||
planName: string
|
||||
trainingType: string
|
||||
trainingLevel: string
|
||||
trainingContent?: string
|
||||
trainer?: string
|
||||
trainingLocation?: string
|
||||
startTime: string
|
||||
endTime: string
|
||||
status?: string
|
||||
maxParticipants?: number
|
||||
requirements?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
export interface TrainingPlanResp {
|
||||
planId: string
|
||||
planName: string
|
||||
trainingType: string
|
||||
trainingLevel: string
|
||||
trainingContent?: string
|
||||
trainer?: string
|
||||
trainingLocation?: string
|
||||
startTime: string
|
||||
endTime: string
|
||||
status: string
|
||||
maxParticipants?: number
|
||||
currentParticipants?: number
|
||||
requirements?: string
|
||||
remark?: string
|
||||
createTime: string
|
||||
createBy: string
|
||||
materials?: TrainingMaterialResp[]
|
||||
records?: TrainingRecordResp[]
|
||||
}
|
||||
|
||||
export interface TrainingMaterialResp {
|
||||
materialId: string
|
||||
materialName: string
|
||||
materialType: string
|
||||
materialPath?: string
|
||||
materialSize?: number
|
||||
description?: string
|
||||
sortOrder?: number
|
||||
}
|
||||
|
||||
export interface TrainingRecordResp {
|
||||
recordId: string
|
||||
userId: string
|
||||
userName: string
|
||||
deptId?: string
|
||||
deptName?: string
|
||||
attendanceStatus: string
|
||||
signInTime?: string
|
||||
signOutTime?: string
|
||||
score?: number
|
||||
feedback?: string
|
||||
certificateId?: string
|
||||
}
|
|
@ -161,6 +161,32 @@ const requestNative = async <T = unknown>(config: AxiosRequestConfig): Promise<A
|
|||
.catch((err: { msg: string }) => Promise.reject(err))
|
||||
}
|
||||
|
||||
// 完全绕过拦截器的请求方法
|
||||
const requestRaw = async <T = unknown>(config: AxiosRequestConfig): Promise<AxiosResponse> => {
|
||||
// 创建一个新的axios实例,不包含拦截器
|
||||
const rawAxios = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_PREFIX ?? import.meta.env.VITE_API_BASE_URL,
|
||||
timeout: 30 * 1000,
|
||||
})
|
||||
|
||||
// 只添加请求拦截器来设置token,不添加响应拦截器
|
||||
rawAxios.interceptors.request.use(
|
||||
(config: AxiosRequestConfig) => {
|
||||
const token = getToken()
|
||||
if (token) {
|
||||
if (!config.headers) {
|
||||
config.headers = {}
|
||||
}
|
||||
config.headers.Authorization = `${token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
(error) => Promise.reject(error),
|
||||
)
|
||||
|
||||
return rawAxios.request<T>(config)
|
||||
}
|
||||
|
||||
const createRequest = (method: string) => {
|
||||
return <T = any>(url: string, params?: object, config?: AxiosRequestConfig): Promise<ApiRes<T>> => {
|
||||
return request({
|
||||
|
@ -196,5 +222,6 @@ export default {
|
|||
del: createRequest('delete'),
|
||||
request,
|
||||
requestNative,
|
||||
requestRaw,
|
||||
download,
|
||||
}
|
||||
|
|
|
@ -64,34 +64,37 @@
|
|||
@click="handleFolderClick(folder.id)"
|
||||
:tooltip="sidebarCollapsed ? folder.name : ''"
|
||||
>
|
||||
<div class="folder-icon-wrapper">
|
||||
<IconFolder class="folder-icon" :style="{ color: folderColor }" />
|
||||
<!-- 第一行:文件夹图标和名称 -->
|
||||
<div class="folder-main-info">
|
||||
<div class="folder-icon-wrapper">
|
||||
<IconFolder class="folder-icon" :style="{ color: folderColor }" />
|
||||
</div>
|
||||
<span v-if="!sidebarCollapsed" class="folder-name">{{ folder.name }}</span>
|
||||
</div>
|
||||
<span v-if="!sidebarCollapsed">{{ folder.name }}</span>
|
||||
|
||||
<!-- 文件夹操作按钮 -->
|
||||
<div v-if="!sidebarCollapsed" class="folder-actions">
|
||||
<a-button
|
||||
type="text"
|
||||
shape="circle"
|
||||
size="small"
|
||||
@click.stop="handleRenameFolder(folder, folder.id, folder.name)"
|
||||
tooltip="重命名"
|
||||
class="action-btn"
|
||||
>
|
||||
<icon-edit />
|
||||
</a-button>
|
||||
<a-button
|
||||
type="text"
|
||||
shape="circle"
|
||||
size="small"
|
||||
@click.stop="handleDeleteFolder(folder)"
|
||||
tooltip="删除"
|
||||
status="danger"
|
||||
class="action-btn"
|
||||
>
|
||||
<icon-delete />
|
||||
</a-button>
|
||||
<!-- 第二行:文件夹操作按钮 -->
|
||||
<div v-if="!sidebarCollapsed" class="folder-actions-row">
|
||||
<a-button
|
||||
type="text"
|
||||
shape="circle"
|
||||
size="small"
|
||||
@click.stop="handleRenameFolder(folder, folder.id, folder.name)"
|
||||
tooltip="重命名"
|
||||
class="action-btn"
|
||||
>
|
||||
<icon-edit />
|
||||
</a-button>
|
||||
<a-button
|
||||
type="text"
|
||||
shape="circle"
|
||||
size="small"
|
||||
@click.stop="handleDeleteFolder(folder)"
|
||||
tooltip="删除"
|
||||
status="danger"
|
||||
class="action-btn"
|
||||
>
|
||||
<icon-delete />
|
||||
</a-button>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
|
@ -155,6 +158,20 @@
|
|||
<a-layout-content class="file-content">
|
||||
<a-card :bordered="false" class="file-card">
|
||||
<a-descriptions :title="`文件列表 (${fileList.length})`" v-if="currentFolderId" />
|
||||
|
||||
<!-- 文件搜索功能 -->
|
||||
<div v-if="currentFolderId" class="file-search-container">
|
||||
<a-input-search
|
||||
v-model="fileSearchKeyword"
|
||||
placeholder="搜索文件名..."
|
||||
class="file-search-input"
|
||||
@search="handleFileSearch"
|
||||
@input="handleFileSearchInput"
|
||||
@clear="handleFileSearchClear"
|
||||
allow-clear
|
||||
/>
|
||||
</div>
|
||||
|
||||
<a-divider size="small" v-if="currentFolderId" />
|
||||
|
||||
<template v-if="!currentFolderId">
|
||||
|
@ -300,6 +317,21 @@
|
|||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 文件分页 -->
|
||||
<div v-if="currentFolderId && !loading && totalFiles > 0" class="file-pagination">
|
||||
<a-pagination
|
||||
:total="totalFiles"
|
||||
:current="fileCurrentPage"
|
||||
:page-size="filePageSize"
|
||||
:show-total="true"
|
||||
:show-page-size="true"
|
||||
:page-size-options="[10, 20, 50, 100]"
|
||||
:show-jumper="true"
|
||||
@change="handleFilePageChange"
|
||||
@page-size-change="handleFilePageSizeChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<a-empty
|
||||
v-if="!loading && currentFolderId && fileList.length === 0"
|
||||
|
@ -381,7 +413,6 @@
|
|||
:show-file-list="false"
|
||||
@change="handleFileChange"
|
||||
:accept="allowedFileTypes"
|
||||
multiple
|
||||
>
|
||||
<a-button type="primary" class="upload-btn">
|
||||
<icon-upload />
|
||||
|
@ -496,6 +527,26 @@
|
|||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 重命名文件对话框 -->
|
||||
<a-modal
|
||||
v-model:visible="renameFileModalVisible"
|
||||
title="重命名文件"
|
||||
width="520px"
|
||||
@ok="confirmRenameFile"
|
||||
@cancel="renameFileModalVisible = false"
|
||||
>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="新文件名">
|
||||
<a-input
|
||||
v-model="renameFileForm.newName"
|
||||
placeholder="请输入新的文件名称(不含扩展名)"
|
||||
max-length="100"
|
||||
@keyup.enter="confirmRenameFile"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
|
@ -531,8 +582,9 @@ import {
|
|||
downloadFileApi,
|
||||
uploadFileApi,
|
||||
updateFileNameApi,
|
||||
renameFileApi,
|
||||
previewFileApi
|
||||
} from '@/apis/bussiness/bussiness.js'
|
||||
} from '@/apis/bussiness'
|
||||
|
||||
// 状态管理
|
||||
const folderList = ref([]);
|
||||
|
@ -580,10 +632,10 @@ const folderColor = '#165DFF';
|
|||
const refreshing = ref(false);
|
||||
const folderSubmitting = ref(false);
|
||||
const uploading = ref(false);
|
||||
const allowedFileTypes = '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.zip,.txt';
|
||||
const allowedFileTypesText = 'PDF, Word, Excel, PPT, 压缩文件, 文本文件';
|
||||
const maxFileSize = 100 * 1024 * 1024; // 100MB
|
||||
const maxFileSizeText = '100MB';
|
||||
const allowedFileTypes = '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.zip,.txt,.jpg,.jpeg,.png,.gif,.bmp,.webp';
|
||||
const allowedFileTypesText = 'PDF, Word, Excel, PPT, 压缩文件, 文本文件, 图片文件';
|
||||
const maxFileSize = 1000 * 1024 * 1024; // 1000MB
|
||||
const maxFileSizeText = '1000MB';
|
||||
const cancelTokens = ref({});
|
||||
|
||||
// 计算属性:是否有文件可上传
|
||||
|
@ -610,6 +662,11 @@ const canUpload = computed(() => {
|
|||
return hasFiles.value && !uploading.value && uploadForm.folderId;
|
||||
});
|
||||
|
||||
// 搜索相关
|
||||
const searchKeyword = ref(''); // 文件夹搜索关键词
|
||||
const fileSearchKeyword = ref(''); // 文件搜索关键词
|
||||
const searchTimeout = ref(null);
|
||||
|
||||
// 初始化文件夹数据
|
||||
const initData = async () => {
|
||||
try {
|
||||
|
@ -688,10 +745,6 @@ const handlePageSizeChange = (current, size) => {
|
|||
initData();
|
||||
};
|
||||
|
||||
// 搜索相关
|
||||
const searchKeyword = ref('');
|
||||
const searchTimeout = ref(null);
|
||||
|
||||
const handleFolderSearch = () => {
|
||||
console.log('=== 执行搜索 ===');
|
||||
console.log('搜索关键词:', searchKeyword.value);
|
||||
|
@ -732,13 +785,61 @@ const handleSearchClear = () => {
|
|||
initData();
|
||||
};
|
||||
|
||||
// 文件搜索相关函数
|
||||
const handleFileSearch = () => {
|
||||
console.log('=== 执行文件搜索 ===');
|
||||
console.log('文件搜索关键词:', fileSearchKeyword.value);
|
||||
// 重置到第一页并搜索
|
||||
fileCurrentPage.value = 1;
|
||||
console.log('重置文件页码为:', fileCurrentPage.value);
|
||||
if (currentFolderId.value) {
|
||||
loadFiles(currentFolderId.value);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileSearchInput = (value) => {
|
||||
console.log('=== 文件搜索输入 ===');
|
||||
console.log('输入值:', value);
|
||||
fileSearchKeyword.value = value;
|
||||
console.log('设置文件搜索关键词为:', fileSearchKeyword.value);
|
||||
// 防抖搜索:300ms后自动搜索
|
||||
if (searchTimeout.value) {
|
||||
clearTimeout(searchTimeout.value);
|
||||
console.log('清除之前的文件搜索定时器');
|
||||
}
|
||||
searchTimeout.value = setTimeout(() => {
|
||||
console.log('=== 防抖文件搜索执行 ===');
|
||||
fileCurrentPage.value = 1;
|
||||
console.log('重置文件页码为:', fileCurrentPage.value);
|
||||
if (currentFolderId.value) {
|
||||
loadFiles(currentFolderId.value);
|
||||
}
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const handleFileSearchClear = () => {
|
||||
console.log('=== 清除文件搜索 ===');
|
||||
fileSearchKeyword.value = '';
|
||||
console.log('清空文件搜索关键词');
|
||||
if (searchTimeout.value) {
|
||||
clearTimeout(searchTimeout.value);
|
||||
console.log('清除文件搜索定时器');
|
||||
}
|
||||
fileCurrentPage.value = 1;
|
||||
console.log('重置文件页码为:', fileCurrentPage.value);
|
||||
if (currentFolderId.value) {
|
||||
loadFiles(currentFolderId.value);
|
||||
}
|
||||
};
|
||||
|
||||
const loadFiles = async (folderId) => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const res = await getFilesApi({
|
||||
folderId: folderId,
|
||||
page: fileCurrentPage.value,
|
||||
pageSize: filePageSize.value
|
||||
pageSize: filePageSize.value,
|
||||
fileName: fileSearchKeyword.value || undefined
|
||||
});
|
||||
|
||||
// 根据后端返回的数据结构处理
|
||||
|
@ -774,6 +875,8 @@ const handleFolderClick = (folderId) => {
|
|||
const id = String(folderId);
|
||||
if (currentFolderId.value !== id) {
|
||||
fileCurrentPage.value = 1;
|
||||
// 切换文件夹时清空文件搜索关键词
|
||||
fileSearchKeyword.value = '';
|
||||
}
|
||||
currentFolderId.value = id;
|
||||
};
|
||||
|
@ -787,6 +890,14 @@ const renameForm = reactive({
|
|||
isRoot: false
|
||||
});
|
||||
|
||||
// 重命名文件对话框状态
|
||||
const renameFileModalVisible = ref(false);
|
||||
const renameFileForm = reactive({
|
||||
fileId: '',
|
||||
newName: '',
|
||||
fileExtension: ''
|
||||
});
|
||||
|
||||
// 文件夹重命名处理函数
|
||||
const handleRenameFolder = (folder, folderId, currentName) => {
|
||||
console.log('handleRenameFolder 被调用:', { folder, folderId, currentName });
|
||||
|
@ -968,7 +1079,21 @@ const handleFileChange = (info) => {
|
|||
console.log('文件列表:', fileList);
|
||||
console.log('文件列表长度:', fileList.length);
|
||||
|
||||
// 处理新选择的文件
|
||||
// 每次选择文件时,强制清空所有状态
|
||||
fileListTemp.value = [];
|
||||
console.log('已清空之前的文件列表');
|
||||
|
||||
// 强制重置上传组件状态
|
||||
if (uploadRef.value) {
|
||||
try {
|
||||
uploadRef.value.reset();
|
||||
console.log('已强制重置上传组件');
|
||||
} catch (error) {
|
||||
console.log('重置上传组件时出错:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理新选择的文件 - 只处理当前选择的文件
|
||||
fileList.forEach((file, index) => {
|
||||
console.log(`处理第${index + 1}个文件:`, file);
|
||||
console.log('文件名称:', file.name);
|
||||
|
@ -987,35 +1112,22 @@ const handleFileChange = (info) => {
|
|||
originFileObj: file.file || file // 保存原始File对象
|
||||
};
|
||||
|
||||
// 只处理新选择的文件,避免重复添加
|
||||
if (!fileListTemp.value.find(f => f.uid === fileObj.uid)) {
|
||||
console.log('这是新文件,开始验证...');
|
||||
|
||||
// 验证文件
|
||||
const isValid = validateFile(fileObj);
|
||||
console.log('文件验证结果:', isValid);
|
||||
|
||||
if (isValid) {
|
||||
fileListTemp.value.push(fileObj);
|
||||
console.log('✅ 成功添加文件到列表:', fileObj.name);
|
||||
} else {
|
||||
console.log('❌ 文件验证失败:', fileObj.name, '错误:', fileObj.error);
|
||||
}
|
||||
console.log('开始验证新文件...');
|
||||
|
||||
// 验证文件
|
||||
const isValid = validateFile(fileObj);
|
||||
console.log('文件验证结果:', isValid);
|
||||
|
||||
if (isValid) {
|
||||
// 确保只添加当前选择的文件
|
||||
fileListTemp.value = [fileObj]; // 只保留当前文件
|
||||
console.log('✅ 成功设置文件到列表:', fileObj.name);
|
||||
} else {
|
||||
console.log('文件已存在,跳过:', fileObj.name);
|
||||
console.log('❌ 文件验证失败:', fileObj.name, '错误:', fileObj.error);
|
||||
}
|
||||
});
|
||||
|
||||
// 清理已移除的文件
|
||||
const beforeCleanCount = fileListTemp.value.length;
|
||||
fileListTemp.value = fileListTemp.value.filter(file =>
|
||||
fileList.some(f => f.uid === file.uid)
|
||||
);
|
||||
const afterCleanCount = fileListTemp.value.length;
|
||||
|
||||
if (beforeCleanCount !== afterCleanCount) {
|
||||
console.log(`清理了 ${beforeCleanCount - afterCleanCount} 个已移除的文件`);
|
||||
}
|
||||
// 文件列表已在上方清空并重新填充,无需额外清理
|
||||
|
||||
console.log('=== 当前文件列表状态 ===');
|
||||
console.log('fileListTemp长度:', fileListTemp.value.length);
|
||||
|
@ -1078,7 +1190,14 @@ const fileColor = (extension) => {
|
|||
ppt: '#faad14',
|
||||
pptx: '#faad14',
|
||||
zip: '#722ed1',
|
||||
txt: '#8c8c8c'
|
||||
txt: '#8c8c8c',
|
||||
// 图片格式颜色
|
||||
jpg: '#52c41a',
|
||||
jpeg: '#52c41a',
|
||||
png: '#1890ff',
|
||||
gif: '#faad14',
|
||||
bmp: '#722ed1',
|
||||
webp: '#13c2c2'
|
||||
};
|
||||
return colorMap[extension.toLowerCase()] || '#8c8c8c';
|
||||
};
|
||||
|
@ -1111,9 +1230,6 @@ const handleUploadSubmit = async () => {
|
|||
!file.error && file.status !== 'removed' && file.status !== 'canceled'
|
||||
);
|
||||
|
||||
console.log('提交上传 - 所有文件:', fileListTemp.value);
|
||||
console.log('提交上传 - 有效文件:', validFiles);
|
||||
|
||||
if (validFiles.length === 0) {
|
||||
Message.warning('请选择有效的文件');
|
||||
return;
|
||||
|
@ -1126,16 +1242,15 @@ const handleUploadSubmit = async () => {
|
|||
}
|
||||
|
||||
uploading.value = true;
|
||||
let successCount = 0;
|
||||
let totalFiles = validFiles.length;
|
||||
let hasError = false;
|
||||
let hasFileExists = false;
|
||||
|
||||
try {
|
||||
for (const fileItem of validFiles) {
|
||||
for (const fileItem of validFiles) {
|
||||
// 获取原始File对象
|
||||
const realFile = fileItem.originFileObj || fileItem;
|
||||
|
||||
if (!realFile) {
|
||||
fileItem.error = '文件数据无效';
|
||||
hasError = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -1146,74 +1261,73 @@ const handleUploadSubmit = async () => {
|
|||
const source = axios.CancelToken.source();
|
||||
cancelTokens.value[fileItem.uid] = source;
|
||||
|
||||
try {
|
||||
// 调用API
|
||||
const result = await uploadFileApi(
|
||||
realFile,
|
||||
Number(uploadForm.folderId),
|
||||
(progressEvent) => {
|
||||
if (progressEvent.lengthComputable) {
|
||||
fileItem.percent = Math.round((progressEvent.loaded / progressEvent.total) * 100);
|
||||
}
|
||||
},
|
||||
source.token
|
||||
);
|
||||
|
||||
// 检查上传结果
|
||||
if (result.code === 0 && result.success) {
|
||||
fileItem.status = 'success';
|
||||
fileItem.percent = 100;
|
||||
successCount++;
|
||||
} else {
|
||||
fileItem.status = 'error';
|
||||
fileItem.error = result.msg || '上传失败';
|
||||
}
|
||||
} catch (error) {
|
||||
if (!axios.isCancel(error)) {
|
||||
fileItem.status = 'error';
|
||||
fileItem.error = error.message || '上传失败';
|
||||
}
|
||||
// 调用API
|
||||
const result = await uploadFileApi(
|
||||
realFile,
|
||||
Number(uploadForm.folderId),
|
||||
(progressEvent) => {
|
||||
if (progressEvent.lengthComputable) {
|
||||
fileItem.percent = Math.round((progressEvent.loaded / progressEvent.total) * 100);
|
||||
}
|
||||
},
|
||||
source.token
|
||||
);
|
||||
|
||||
// 检查上传结果
|
||||
if (result.code === 200) {
|
||||
fileItem.status = 'success';
|
||||
fileItem.percent = 100;
|
||||
} else if (result.code === 400 && result.msg && result.msg.includes('已存在')) {
|
||||
// 文件已存在的情况
|
||||
fileItem.status = 'error';
|
||||
fileItem.error = '文件已存在';
|
||||
hasFileExists = true;
|
||||
} else {
|
||||
fileItem.status = 'error';
|
||||
fileItem.error = result.msg || '上传失败';
|
||||
hasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示结果
|
||||
if (successCount === totalFiles) {
|
||||
Message.success('所有文件上传成功');
|
||||
resetUpload();
|
||||
// 刷新当前文件夹文件列表
|
||||
if (currentFolderId.value === uploadForm.folderId) {
|
||||
loadFiles(currentFolderId.value);
|
||||
}
|
||||
} else if (successCount >= 0) {
|
||||
Message.warning(`${successCount}/${totalFiles} 个文件上传成功`);
|
||||
resetUpload();
|
||||
} else {
|
||||
Message.error('所有文件上传失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('上传过程出错:', error);
|
||||
Message.error('上传过程出错');
|
||||
} finally {
|
||||
uploading.value = false;
|
||||
}
|
||||
|
||||
// 根据结果显示相应的消息
|
||||
if (hasFileExists && !hasError) {
|
||||
Message.warning('文件已存在');
|
||||
} else if (hasError) {
|
||||
Message.error('上传失败');
|
||||
} else {
|
||||
Message.success('上传成功');
|
||||
// 刷新当前文件夹文件列表
|
||||
if (currentFolderId.value === uploadForm.folderId) {
|
||||
loadFiles(currentFolderId.value);
|
||||
}
|
||||
}
|
||||
|
||||
resetUpload();
|
||||
};
|
||||
|
||||
// 重置上传表单
|
||||
const resetUpload = () => {
|
||||
console.log('=== 重置上传表单 ===');
|
||||
|
||||
// 取消所有正在进行的上传
|
||||
Object.values(cancelTokens.value).forEach(source => {
|
||||
source.cancel('上传已取消');
|
||||
});
|
||||
|
||||
// 重置所有状态
|
||||
uploadDialogVisible.value = false;
|
||||
uploadForm.folderId = currentFolderId.value || '';
|
||||
fileListTemp.value = [];
|
||||
cancelTokens.value = {};
|
||||
uploading.value = false;
|
||||
|
||||
// 清空上传组件
|
||||
if (uploadRef.value) {
|
||||
uploadRef.value.reset();
|
||||
console.log('已重置上传组件');
|
||||
}
|
||||
|
||||
console.log('上传表单重置完成');
|
||||
};
|
||||
|
||||
// 预览文件
|
||||
|
@ -1268,11 +1382,87 @@ const handleDownload = async (file) => {
|
|||
|
||||
// 重命名文件
|
||||
const handleEditFile = (file) => {
|
||||
Modal.warning({
|
||||
title: '功能提示',
|
||||
content: '后端暂不支持文件重命名功能,如需重命名请先删除文件再重新上传。',
|
||||
okText: '知道了'
|
||||
console.log('=== 重命名文件函数被调用 ===');
|
||||
console.log('重命名文件 - 文件对象:', file);
|
||||
console.log('文件对象的所有属性:', Object.keys(file));
|
||||
console.log('文件属性详情:', {
|
||||
fileId: file.fileId,
|
||||
fileName: file.fileName,
|
||||
name: file.name,
|
||||
originalName: file.originalName,
|
||||
displayName: file.displayName,
|
||||
title: file.title
|
||||
});
|
||||
|
||||
// 尝试多种可能的文件名字段
|
||||
let fileName = '';
|
||||
if (file.fileName) {
|
||||
fileName = file.fileName;
|
||||
console.log('使用 fileName 字段:', fileName);
|
||||
} else if (file.name) {
|
||||
fileName = file.name;
|
||||
console.log('使用 name 字段:', fileName);
|
||||
} else if (file.originalName) {
|
||||
fileName = file.originalName;
|
||||
console.log('使用 originalName 字段:', fileName);
|
||||
} else if (file.displayName) {
|
||||
fileName = file.displayName;
|
||||
console.log('使用 displayName 字段:', fileName);
|
||||
} else if (file.title) {
|
||||
fileName = file.title;
|
||||
console.log('使用 title 字段:', fileName);
|
||||
}
|
||||
|
||||
console.log('最终获取到的文件名:', fileName);
|
||||
|
||||
if (!fileName) {
|
||||
console.error('无法获取文件名,文件对象:', file);
|
||||
Message.error('无法获取文件名,请检查文件数据');
|
||||
return;
|
||||
}
|
||||
|
||||
const fileExtension = getFileExtension(fileName);
|
||||
const fileNameWithoutExtension = fileExtension ? fileName.substring(0, fileName.lastIndexOf('.')) : fileName;
|
||||
|
||||
console.log('处理后的文件名信息:', {
|
||||
originalName: fileName,
|
||||
extension: fileExtension,
|
||||
nameWithoutExtension: fileNameWithoutExtension
|
||||
});
|
||||
|
||||
// 设置重命名表单数据
|
||||
renameFileForm.fileId = file.fileId;
|
||||
renameFileForm.newName = fileNameWithoutExtension;
|
||||
renameFileForm.fileExtension = fileExtension;
|
||||
|
||||
console.log('设置的重命名表单数据:', renameFileForm);
|
||||
|
||||
// 显示重命名弹窗
|
||||
renameFileModalVisible.value = true;
|
||||
};
|
||||
|
||||
// 确认重命名文件
|
||||
const confirmRenameFile = async () => {
|
||||
if (!renameFileForm.newName.trim()) {
|
||||
Message.warning('请输入文件名称');
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用用户输入的文件名,不添加任何扩展名
|
||||
const newFileName = renameFileForm.newName.trim();
|
||||
|
||||
try {
|
||||
await renameFileApi(renameFileForm.fileId, newFileName);
|
||||
Message.success('文件重命名成功');
|
||||
renameFileModalVisible.value = false;
|
||||
// 刷新当前文件夹的文件列表
|
||||
if (currentFolderId.value) {
|
||||
loadFiles(currentFolderId.value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('重命名文件失败:', error);
|
||||
Message.error('重命名失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 删除文件夹
|
||||
|
@ -1351,6 +1541,13 @@ const fileTypeText = (type) => {
|
|||
pptx: 'PPT演示',
|
||||
zip: '压缩文件',
|
||||
txt: '文本文件',
|
||||
// 图片格式
|
||||
jpg: 'JPG图片',
|
||||
jpeg: 'JPEG图片',
|
||||
png: 'PNG图片',
|
||||
gif: 'GIF图片',
|
||||
bmp: 'BMP图片',
|
||||
webp: 'WebP图片',
|
||||
unknown: '未知类型'
|
||||
};
|
||||
|
||||
|
@ -1431,7 +1628,7 @@ onMounted(() => {
|
|||
|
||||
.folder-content {
|
||||
padding: 16px 0;
|
||||
height: calc(100vh - 280px); /* 为底部分页控件留出空间 */
|
||||
height: calc(100vh - 320px); /* 为底部分页控件留出更多空间,因为文件夹项现在更高 */
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
|
@ -1471,7 +1668,8 @@ onMounted(() => {
|
|||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
border: 1px solid transparent;
|
||||
|
||||
&:hover {
|
||||
|
@ -1490,17 +1688,35 @@ onMounted(() => {
|
|||
}
|
||||
}
|
||||
|
||||
/* 文件夹操作按钮样式 */
|
||||
.folder-actions {
|
||||
display: none;
|
||||
gap: 6px;
|
||||
/* 文件夹主要信息样式 */
|
||||
.folder-main-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.folder-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-left: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 文件夹操作按钮样式 */
|
||||
.folder-actions-row {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.folder-list-item:hover .folder-actions {
|
||||
display: flex;
|
||||
.folder-list-item:hover .folder-actions-row {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
@ -1518,13 +1734,7 @@ onMounted(() => {
|
|||
}
|
||||
}
|
||||
|
||||
.folder-list-item span {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: 8px;
|
||||
}
|
||||
/* 删除旧的span样式,因为现在使用.folder-name */
|
||||
|
||||
/* 顶部导航样式 */
|
||||
.file-header {
|
||||
|
@ -2128,7 +2338,7 @@ onMounted(() => {
|
|||
}
|
||||
|
||||
/* 确保在折叠状态下不显示操作按钮 */
|
||||
:deep(.folder-sidebar.collapsed) .folder-actions {
|
||||
:deep(.folder-sidebar.collapsed) .folder-actions-row {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -2324,5 +2534,64 @@ onMounted(() => {
|
|||
100% { transform: translateX(100%); }
|
||||
}
|
||||
|
||||
/* 文件搜索样式 */
|
||||
.file-search-container {
|
||||
margin: 16px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.file-search-input {
|
||||
max-width: 300px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:focus-within {
|
||||
box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* 文件分页样式 */
|
||||
.file-pagination {
|
||||
margin-top: 24px;
|
||||
padding: 16px 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
border-top: 1px solid var(--color-border);
|
||||
background: var(--color-bg-1);
|
||||
|
||||
.arco-pagination {
|
||||
.arco-pagination-total {
|
||||
color: var(--color-text-2);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.arco-pagination-item {
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
&.arco-pagination-item-active {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.arco-pagination-prev,
|
||||
.arco-pagination-next {
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -63,6 +63,10 @@
|
|||
共找到 <strong>{{ pagination.total }}</strong> 条记录
|
||||
</span>
|
||||
</template>
|
||||
<template #regulationType="{ record }">
|
||||
{{ getRegulationTypeName(record.regulationType) }}
|
||||
</template>
|
||||
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
|
@ -105,7 +109,7 @@
|
|||
<h3>{{ currentProposal.title }}</h3>
|
||||
<div class="detail-meta">
|
||||
<span>提案人: {{ currentProposal.createByName }}</span>
|
||||
<span>提案类型: {{ currentProposal.regulationType }}</span>
|
||||
<span>提案类型: {{ getRegulationTypeName(currentProposal.regulationType) }}</span>
|
||||
<span>适用范围: {{ currentProposal.scope }}</span>
|
||||
<span>级别: <a-tag :color="getLevelColor(currentProposal.level)">{{ getLevelText(currentProposal.level) }}</a-tag></span>
|
||||
<span>创建时间: {{ formatDate(currentProposal.createTime) }}</span>
|
||||
|
@ -157,7 +161,7 @@ defineOptions({ name: 'ProcessManagement' })
|
|||
const columns = [
|
||||
{ title: '提案标题', dataIndex: 'title', key: 'title', width: 200 },
|
||||
{ title: '提案人', dataIndex: 'createByName', key: 'createByName', width: 120 },
|
||||
{ title: '提案类型', dataIndex: 'regulationType', key: 'regulationType', width: 120 },
|
||||
{ title: '提案类型', dataIndex: 'regulationType', key: 'regulationType', slotName: 'regulationType', width: 120 },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status', slotName: 'status', width: 100 },
|
||||
{ title: '级别', dataIndex: 'level', key: 'level', slotName: 'level', width: 80 },
|
||||
{ title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 180 },
|
||||
|
@ -194,6 +198,9 @@ const currentUser = ref('管理者') // 从用户认证系统获取当前用户I
|
|||
// 制度管理store
|
||||
const regulationStore = useRegulationStore()
|
||||
|
||||
// 制度类型列表
|
||||
const regulationTypes = ref<RegulationType[]>([])
|
||||
|
||||
// 获取状态颜色
|
||||
const getStatusColor = (status: RegulationStatus) => {
|
||||
const colors = {
|
||||
|
@ -245,6 +252,24 @@ const getLevelText = (level: RegulationLevel) => {
|
|||
return texts[level] || '中'
|
||||
}
|
||||
|
||||
// 根据制度类型ID获取类型名称
|
||||
const getRegulationTypeName = (typeId: string) => {
|
||||
const type = regulationTypes.value.find(t => t.typeId === typeId)
|
||||
return type ? type.typeName : typeId
|
||||
}
|
||||
|
||||
// 获取制度类型列表
|
||||
const getRegulationTypes = async () => {
|
||||
try {
|
||||
const response = await regulationApi.searchRegulationTypes()
|
||||
if (response.status === 200) {
|
||||
regulationTypes.value = response.data.records || response.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取制度类型列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取表格数据 - 使用后端搜索接口
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
@ -345,6 +370,7 @@ const handlePageSizeChange = (pageSize: number) => {
|
|||
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
getRegulationTypes()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
<a-option value="">全部</a-option>
|
||||
<a-option value="DRAFT">草稿</a-option>
|
||||
<a-option value="PUBLISHED">已公告</a-option>
|
||||
<a-option value="APPROVED">已公示</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
|
@ -78,6 +79,10 @@
|
|||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template #regulationType="{ record }">
|
||||
{{ getRegulationTypeName(record.regulationType) }}
|
||||
</template>
|
||||
|
||||
<template #level="{ record }">
|
||||
<a-tag :color="getLevelColor(record.level)">
|
||||
{{ getLevelText(record.level) }}
|
||||
|
@ -165,7 +170,7 @@
|
|||
<a-option
|
||||
v-for="type in regulationTypes"
|
||||
:key="type.typeId"
|
||||
:value="type.typeName"
|
||||
:value="type.typeId"
|
||||
:disabled="type.isEnabled === '0'"
|
||||
>
|
||||
{{ type.typeName }}
|
||||
|
@ -236,7 +241,7 @@
|
|||
<h3>{{ currentProposal.title }}</h3>
|
||||
<div class="detail-meta">
|
||||
<span>提案人: {{ currentProposal.createByName }}</span>
|
||||
<span>提案类型: {{ currentProposal.regulationType }}</span>
|
||||
<span>提案类型: {{ getRegulationTypeName(currentProposal.regulationType) }}</span>
|
||||
<span>适用范围: {{ currentProposal.scope }}</span>
|
||||
<span>级别: <a-tag :color="getLevelColor(currentProposal.level)">{{ getLevelText(currentProposal.level) }}</a-tag></span>
|
||||
<span>创建时间: {{ formatDate(currentProposal.createTime) }}</span>
|
||||
|
@ -326,7 +331,7 @@ defineOptions({ name: 'RegulationProposal' })
|
|||
const columns = [
|
||||
{ title: '提案标题', dataIndex: 'title', key: 'title', width: 220 },
|
||||
{ title: '提案人', dataIndex: 'createByName', key: 'createByName', width: 130 },
|
||||
{ title: '提案类型', dataIndex: 'regulationType', key: 'regulationType', width: 130 },
|
||||
{ title: '提案类型', dataIndex: 'regulationType', key: 'regulationType', slotName: 'regulationType', width: 130 },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status', slotName: 'status', width: 110 },
|
||||
{ title: '级别', dataIndex: 'level', key: 'level', slotName: 'level', width: 90 },
|
||||
{ title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 200 },
|
||||
|
@ -447,6 +452,12 @@ const getLevelText = (level: RegulationLevel) => {
|
|||
return texts[level] || '中'
|
||||
}
|
||||
|
||||
// 根据制度类型ID获取类型名称
|
||||
const getRegulationTypeName = (typeId: string) => {
|
||||
const type = regulationTypes.value.find(t => t.typeId === typeId)
|
||||
return type ? type.typeName : typeId
|
||||
}
|
||||
|
||||
// 获取制度类型列表
|
||||
const getRegulationTypes = async () => {
|
||||
try {
|
||||
|
@ -606,6 +617,7 @@ const handleSubmit = async () => {
|
|||
scope: formData.scope,
|
||||
level: formData.level,
|
||||
remark: formData.remark,
|
||||
createBy: currentUser.value,
|
||||
createByName: currentUser.value
|
||||
}
|
||||
console.log('新增提案数据:', createData)
|
||||
|
|
|
@ -63,6 +63,10 @@
|
|||
共找到 <strong>{{ pagination.total }}</strong> 条记录
|
||||
</span>
|
||||
</template>
|
||||
<template #regulationType="{ record }">
|
||||
{{ getRegulationTypeName(record.regulationType) }}
|
||||
</template>
|
||||
|
||||
<template #confirmStatus="{ record }">
|
||||
<a-tag :color="record.confirmStatus === 'CONFIRMED' ? 'green' : 'orange'">
|
||||
{{ record.confirmStatus === 'CONFIRMED' ? '已确认' : '待确认' }}
|
||||
|
@ -170,7 +174,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import type { Regulation } from '@/apis/regulation/type'
|
||||
import type { Regulation, RegulationType } from '@/apis/regulation/type'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { IconSearch, IconRefresh } from '@arco-design/web-vue/es/icon'
|
||||
import { useRegulationStore } from '@/stores/modules/regulation'
|
||||
|
@ -182,7 +186,7 @@ defineOptions({ name: 'SystemRegulation' })
|
|||
// 表格列定义
|
||||
const columns = [
|
||||
{ title: '制度名称', dataIndex: 'title', key: 'title' },
|
||||
{ title: '制度类型', dataIndex: 'regulationType', key: 'regulationType' },
|
||||
{ title: '制度类型', dataIndex: 'regulationType', key: 'regulationType', slotName: 'regulationType' },
|
||||
{ title: '公示人', dataIndex: 'createByName', key: 'createByName' },
|
||||
{ title: '公示时间', dataIndex: 'publishTime', key: 'publishTime' },
|
||||
{ title: '生效日期', dataIndex: 'effectiveTime', key: 'effectiveTime' },
|
||||
|
@ -218,6 +222,27 @@ const agreeTerms = ref(false)
|
|||
// 制度管理store
|
||||
const regulationStore = useRegulationStore()
|
||||
|
||||
// 制度类型列表
|
||||
const regulationTypes = ref<RegulationType[]>([])
|
||||
|
||||
// 根据制度类型ID获取类型名称
|
||||
const getRegulationTypeName = (typeId: string) => {
|
||||
const type = regulationTypes.value.find(t => t.typeId === typeId)
|
||||
return type ? type.typeName : typeId
|
||||
}
|
||||
|
||||
// 获取制度类型列表
|
||||
const getRegulationTypes = async () => {
|
||||
try {
|
||||
const response = await regulationApi.searchRegulationTypes()
|
||||
if (response.status === 200) {
|
||||
regulationTypes.value = response.data.records || response.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取制度类型列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取表格数据 - 使用后端搜索接口
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
@ -356,6 +381,7 @@ const submitConfirm = async () => {
|
|||
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
getRegulationTypes()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,496 @@
|
|||
<template>
|
||||
<div class="equipment-search-container">
|
||||
<!-- 搜索按钮 -->
|
||||
<div class="search-trigger">
|
||||
<a-button type="primary" @click="showSearchModal = true">
|
||||
<template #icon>
|
||||
<IconSearch />
|
||||
</template>
|
||||
搜索设备
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<!-- 搜索弹窗 -->
|
||||
<a-modal
|
||||
v-model:visible="showSearchModal"
|
||||
title="设备搜索"
|
||||
width="1000px"
|
||||
:footer="false"
|
||||
@cancel="handleCancel"
|
||||
class="search-modal"
|
||||
>
|
||||
<div class="search-content">
|
||||
<a-form layout="vertical" :model="searchForm" class="search-form">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="设备名称">
|
||||
<a-input
|
||||
v-model="searchForm.equipmentName"
|
||||
placeholder="请输入设备名称"
|
||||
allow-clear
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="设备类型">
|
||||
<a-select
|
||||
v-model="searchForm.equipmentType"
|
||||
:options="equipmentTypeOptions"
|
||||
placeholder="请选择设备类型"
|
||||
allow-clear
|
||||
@change="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="设备状态">
|
||||
<a-select
|
||||
v-model="searchForm.equipmentStatus"
|
||||
:options="equipmentStatusOptions"
|
||||
placeholder="请选择设备状态"
|
||||
allow-clear
|
||||
@change="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="位置状态">
|
||||
<a-select
|
||||
v-model="searchForm.locationStatus"
|
||||
:options="locationStatusOptions"
|
||||
placeholder="请选择位置状态"
|
||||
allow-clear
|
||||
@change="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="健康状态">
|
||||
<a-select
|
||||
v-model="searchForm.healthStatus"
|
||||
:options="healthStatusOptions"
|
||||
placeholder="请选择健康状态"
|
||||
allow-clear
|
||||
@change="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="使用状态">
|
||||
<a-select
|
||||
v-model="searchForm.useStatus"
|
||||
:options="useStatusOptions"
|
||||
placeholder="请选择使用状态"
|
||||
allow-clear
|
||||
@change="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="使用部门/人">
|
||||
<a-input
|
||||
v-model="searchForm.usingDepartment"
|
||||
placeholder="请输入使用部门/人"
|
||||
allow-clear
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="负责人">
|
||||
<a-input
|
||||
v-model="searchForm.responsiblePerson"
|
||||
placeholder="请输入负责人"
|
||||
allow-clear
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="维护人员">
|
||||
<a-input
|
||||
v-model="searchForm.maintenancePerson"
|
||||
placeholder="请输入维护人员"
|
||||
allow-clear
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="序列号">
|
||||
<a-input
|
||||
v-model="searchForm.equipmentSn"
|
||||
placeholder="请输入序列号"
|
||||
allow-clear
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="资产编号">
|
||||
<a-input
|
||||
v-model="searchForm.assetCode"
|
||||
placeholder="请输入资产编号"
|
||||
allow-clear
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="品牌">
|
||||
<a-input
|
||||
v-model="searchForm.brand"
|
||||
placeholder="请输入品牌"
|
||||
allow-clear
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="设备型号">
|
||||
<a-input
|
||||
v-model="searchForm.equipmentModel"
|
||||
placeholder="请输入设备型号"
|
||||
allow-clear
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="物理位置">
|
||||
<a-input
|
||||
v-model="searchForm.physicalLocation"
|
||||
placeholder="请输入物理位置"
|
||||
allow-clear
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="供应商">
|
||||
<a-input
|
||||
v-model="searchForm.supplierName"
|
||||
placeholder="请输入供应商"
|
||||
allow-clear
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="发票">
|
||||
<a-input
|
||||
v-model="searchForm.invoice"
|
||||
placeholder="请输入发票"
|
||||
allow-clear
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="条码">
|
||||
<a-input
|
||||
v-model="searchForm.barcode"
|
||||
placeholder="请输入条码"
|
||||
allow-clear
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="库存条码">
|
||||
<a-input
|
||||
v-model="searchForm.inventoryBarcode"
|
||||
placeholder="请输入库存条码"
|
||||
allow-clear
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="规格型号">
|
||||
<a-input
|
||||
v-model="searchForm.specification"
|
||||
placeholder="请输入规格型号"
|
||||
allow-clear
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="资产备注">
|
||||
<a-input
|
||||
v-model="searchForm.assetRemark"
|
||||
placeholder="请输入资产备注"
|
||||
allow-clear
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 搜索操作按钮 -->
|
||||
<div class="search-actions">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleSearch" :loading="loading">
|
||||
<template #icon>
|
||||
<IconSearch />
|
||||
</template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button @click="handleReset">
|
||||
<template #icon>
|
||||
<IconRefresh />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
<a-button @click="handleCancel">
|
||||
取消
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { IconRefresh, IconSearch } from '@arco-design/web-vue/es/icon'
|
||||
import type { EquipmentPageQuery } from '@/types/equipment.d'
|
||||
|
||||
interface Props {
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
loading: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
search: [params: EquipmentPageQuery]
|
||||
reset: []
|
||||
}>()
|
||||
|
||||
// 弹窗控制
|
||||
const showSearchModal = ref(false)
|
||||
|
||||
// 防抖函数
|
||||
// eslint-disable-next-line ts/no-unsafe-function-type
|
||||
const debounce = (func: Function, delay: number) => {
|
||||
let timeoutId: NodeJS.Timeout
|
||||
return (...args: any[]) => {
|
||||
clearTimeout(timeoutId)
|
||||
// eslint-disable-next-line prefer-spread
|
||||
timeoutId = setTimeout(() => func.apply(null, args), delay)
|
||||
}
|
||||
}
|
||||
|
||||
const searchForm = reactive<EquipmentPageQuery>({
|
||||
equipmentName: '',
|
||||
equipmentType: '',
|
||||
equipmentStatus: '',
|
||||
locationStatus: '',
|
||||
healthStatus: '',
|
||||
usingDepartment: '',
|
||||
invoice: '',
|
||||
barcode: '',
|
||||
importer: '',
|
||||
equipmentSn: '',
|
||||
assetCode: '',
|
||||
brand: '',
|
||||
responsiblePerson: '',
|
||||
useStatus: '',
|
||||
equipmentModel: '',
|
||||
specification: '',
|
||||
physicalLocation: '',
|
||||
supplierName: '',
|
||||
maintenancePerson: '',
|
||||
inventoryBarcode: '',
|
||||
assetRemark: '',
|
||||
})
|
||||
|
||||
const equipmentTypeOptions = [
|
||||
{ label: '计算机设备', value: 'computer' },
|
||||
{ label: '网络设备', value: 'network' },
|
||||
{ label: '存储设备', value: 'storage' },
|
||||
{ label: '安全设备', value: 'security' },
|
||||
{ label: '办公设备', value: 'office' },
|
||||
{ label: '其他设备', value: 'other' },
|
||||
]
|
||||
|
||||
const equipmentStatusOptions = [
|
||||
{ label: '正常', value: 'normal' },
|
||||
{ label: '维修中', value: 'repair' },
|
||||
{ label: '报废', value: 'scrap' },
|
||||
{ label: '闲置', value: 'idle' },
|
||||
]
|
||||
|
||||
const locationStatusOptions = [
|
||||
{ label: '在库', value: 'in_stock' },
|
||||
{ label: '已分配', value: 'allocated' },
|
||||
{ label: '外借中', value: 'borrowed' },
|
||||
{ label: '维修中', value: 'repair' },
|
||||
{ label: '已报废', value: 'scrapped' },
|
||||
]
|
||||
|
||||
const healthStatusOptions = [
|
||||
{ label: '良好', value: 'good' },
|
||||
{ label: '一般', value: 'normal' },
|
||||
{ label: '较差', value: 'poor' },
|
||||
{ label: '故障', value: 'fault' },
|
||||
]
|
||||
|
||||
const useStatusOptions = [
|
||||
{ label: '空闲中', value: '0' },
|
||||
{ label: '使用中', value: '1' },
|
||||
]
|
||||
|
||||
// 防抖搜索函数
|
||||
const debouncedSearch = debounce(() => {
|
||||
console.log('🔍 EquipmentSearch - 防抖搜索触发')
|
||||
console.log('🔍 EquipmentSearch - 搜索表单数据:', searchForm)
|
||||
emit('search', { ...searchForm })
|
||||
}, 300)
|
||||
|
||||
const handleSearch = () => {
|
||||
console.log('🔍 EquipmentSearch - 搜索按钮被点击')
|
||||
console.log('🔍 EquipmentSearch - 搜索表单数据:', searchForm)
|
||||
console.log('🔍 EquipmentSearch - 发送的搜索参数:', { ...searchForm })
|
||||
emit('search', { ...searchForm })
|
||||
showSearchModal.value = false
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
console.log('🔄 EquipmentSearch - 重置按钮被点击')
|
||||
console.log('🔄 EquipmentSearch - 重置前的表单数据:', { ...searchForm })
|
||||
Object.keys(searchForm).forEach((key) => {
|
||||
searchForm[key as keyof EquipmentPageQuery] = '' as any
|
||||
})
|
||||
console.log('🔄 EquipmentSearch - 重置后的表单数据:', { ...searchForm })
|
||||
emit('reset')
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
showSearchModal.value = false
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
reset: handleReset,
|
||||
getSearchForm: () => ({ ...searchForm }),
|
||||
showModal: () => { showSearchModal.value = true },
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.equipment-search-container {
|
||||
.search-trigger {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.search-modal {
|
||||
.arco-modal-header {
|
||||
padding: 20px 24px 16px;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
|
||||
.arco-modal-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
}
|
||||
|
||||
.arco-modal-body {
|
||||
padding: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.search-content {
|
||||
.search-form {
|
||||
.arco-form-item {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.arco-form-item-label {
|
||||
font-weight: 500;
|
||||
color: var(--color-text-1);
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.arco-input,
|
||||
.arco-select {
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--color-border);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-primary-light-3);
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&.arco-input-focus,
|
||||
&.arco-select-focus {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 2px rgba(var(--primary-6), 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.arco-input-inner {
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.arco-select-view {
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.arco-select-arrow {
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
|
||||
.arco-input-inner::placeholder {
|
||||
color: var(--color-text-3);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.arco-input-clear-btn {
|
||||
color: var(--color-text-3);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 32px;
|
||||
padding-top: 24px;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -54,7 +54,7 @@
|
|||
import { onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { IconUpload } from '@arco-design/web-vue/es/icon'
|
||||
import { getEquipmentDetail } from '@/apis/equipment'
|
||||
import { EquipmentAPI } from '@/apis'
|
||||
import type { EquipmentResp } from '@/types/equipment.d'
|
||||
|
||||
defineOptions({ name: 'DeviceDetail' })
|
||||
|
@ -245,7 +245,7 @@ const getHealthStatusText = (status: string) => {
|
|||
// 加载设备详情
|
||||
const loadDeviceDetail = async () => {
|
||||
try {
|
||||
const res = await getEquipmentDetail(deviceId)
|
||||
const res = await EquipmentAPI.getEquipmentDetail(deviceId)
|
||||
const device = res.data as EquipmentResp
|
||||
|
||||
// 更新基本信息
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,491 @@
|
|||
<template>
|
||||
<a-button type="primary" @click="showSearchModal = true">
|
||||
<template #icon>
|
||||
<IconSearch />
|
||||
</template>
|
||||
搜索采购
|
||||
</a-button>
|
||||
|
||||
<!-- 搜索弹窗 -->
|
||||
<a-modal
|
||||
v-model:visible="showSearchModal"
|
||||
title="采购记录搜索"
|
||||
width="1000px"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="searchForm"
|
||||
layout="vertical"
|
||||
class="search-form"
|
||||
>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="设备名称">
|
||||
<a-input
|
||||
v-model="searchForm.equipmentName"
|
||||
placeholder="请输入设备名称"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="设备型号">
|
||||
<a-input
|
||||
v-model="searchForm.equipmentModel"
|
||||
placeholder="请输入设备型号"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="设备类型">
|
||||
<a-select
|
||||
v-model="searchForm.equipmentType"
|
||||
placeholder="请选择设备类型"
|
||||
allow-clear
|
||||
>
|
||||
<a-option value="detection">检测设备</a-option>
|
||||
<a-option value="security">安防设备</a-option>
|
||||
<a-option value="office">办公设备</a-option>
|
||||
<a-option value="car">车辆</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="品牌">
|
||||
<a-input
|
||||
v-model="searchForm.brand"
|
||||
placeholder="请输入品牌"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="供应商">
|
||||
<a-input
|
||||
v-model="searchForm.supplierName"
|
||||
placeholder="请输入供应商名称"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="采购订单">
|
||||
<a-input
|
||||
v-model="searchForm.purchaseOrder"
|
||||
placeholder="请输入采购订单号"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="设备状态">
|
||||
<a-select
|
||||
v-model="searchForm.equipmentStatus"
|
||||
placeholder="请选择设备状态"
|
||||
allow-clear
|
||||
>
|
||||
<a-option value="normal">正常</a-option>
|
||||
<a-option value="repair">维修中</a-option>
|
||||
<a-option value="maintain">保养中</a-option>
|
||||
<a-option value="scrap">报废</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="位置状态">
|
||||
<a-select
|
||||
v-model="searchForm.locationStatus"
|
||||
placeholder="请选择位置状态"
|
||||
allow-clear
|
||||
>
|
||||
<a-option value="not_in_stock">未入库</a-option>
|
||||
<a-option value="in_stock">库存中</a-option>
|
||||
<a-option value="allocated">已分配</a-option>
|
||||
<a-option value="repair">维修中</a-option>
|
||||
<a-option value="scrap">待报废</a-option>
|
||||
<a-option value="scrapped">已报废</a-option>
|
||||
<a-option value="borrowed">外借中</a-option>
|
||||
<a-option value="lost">丢失</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="健康状态">
|
||||
<a-select
|
||||
v-model="searchForm.healthStatus"
|
||||
placeholder="请选择健康状态"
|
||||
allow-clear
|
||||
>
|
||||
<a-option value="excellent">优秀</a-option>
|
||||
<a-option value="good">良好</a-option>
|
||||
<a-option value="normal">一般</a-option>
|
||||
<a-option value="poor">较差</a-option>
|
||||
<a-option value="critical">危险</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="负责人">
|
||||
<a-input
|
||||
v-model="searchForm.responsiblePerson"
|
||||
placeholder="请输入负责人"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="维护人员">
|
||||
<a-input
|
||||
v-model="searchForm.maintenancePerson"
|
||||
placeholder="请输入维护人员"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="物理位置">
|
||||
<a-input
|
||||
v-model="searchForm.physicalLocation"
|
||||
placeholder="请输入物理位置"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="资产编号">
|
||||
<a-input
|
||||
v-model="searchForm.assetCode"
|
||||
placeholder="请输入资产编号"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="设备序列号">
|
||||
<a-input
|
||||
v-model="searchForm.equipmentSn"
|
||||
placeholder="请输入设备序列号"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="库存条码">
|
||||
<a-input
|
||||
v-model="searchForm.inventoryBarcode"
|
||||
placeholder="请输入库存条码"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="采购时间范围">
|
||||
<a-range-picker
|
||||
v-model="searchForm.purchaseTimeRange"
|
||||
show-time
|
||||
placeholder="['开始时间', '结束时间']"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="入库时间范围">
|
||||
<a-range-picker
|
||||
v-model="searchForm.inStockTimeRange"
|
||||
show-time
|
||||
placeholder="['开始时间', '结束时间']"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="启用时间范围">
|
||||
<a-range-picker
|
||||
v-model="searchForm.activationTimeRange"
|
||||
show-time
|
||||
placeholder="['开始时间', '结束时间']"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="价格范围">
|
||||
<a-input-number
|
||||
v-model="searchForm.minPrice"
|
||||
placeholder="最低价格"
|
||||
:min="0"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="至">
|
||||
<a-input-number
|
||||
v-model="searchForm.maxPrice"
|
||||
placeholder="最高价格"
|
||||
:min="0"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="配置规格">
|
||||
<a-input
|
||||
v-model="searchForm.specification"
|
||||
placeholder="请输入配置规格关键词"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="24">
|
||||
<a-form-item label="备注">
|
||||
<a-input
|
||||
v-model="searchForm.assetRemark"
|
||||
placeholder="请输入备注关键词"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="search-actions">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleSearch" :loading="loading">
|
||||
<template #icon>
|
||||
<IconSearch />
|
||||
</template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button @click="handleReset">
|
||||
<template #icon>
|
||||
<IconRefresh />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
<a-button @click="showSearchModal = false">
|
||||
取消
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { IconSearch, IconRefresh } from '@arco-design/web-vue/es/icon'
|
||||
import type { EquipmentListReq } from '@/apis/equipment/type'
|
||||
|
||||
interface Props {
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'search', params: EquipmentListReq): void
|
||||
(e: 'reset'): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
loading: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const showSearchModal = ref(false)
|
||||
const formRef = ref()
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
equipmentName: '',
|
||||
equipmentModel: '',
|
||||
equipmentType: '',
|
||||
brand: '',
|
||||
supplierName: '',
|
||||
purchaseOrder: '',
|
||||
equipmentStatus: '',
|
||||
locationStatus: '',
|
||||
healthStatus: '',
|
||||
responsiblePerson: '',
|
||||
maintenancePerson: '',
|
||||
physicalLocation: '',
|
||||
assetCode: '',
|
||||
equipmentSn: '',
|
||||
inventoryBarcode: '',
|
||||
purchaseTimeRange: [],
|
||||
inStockTimeRange: [],
|
||||
activationTimeRange: [],
|
||||
minPrice: undefined,
|
||||
maxPrice: undefined,
|
||||
specification: '',
|
||||
assetRemark: '',
|
||||
})
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
const params: EquipmentListReq = {
|
||||
equipmentName: searchForm.equipmentName || undefined,
|
||||
equipmentModel: searchForm.equipmentModel || undefined,
|
||||
equipmentType: searchForm.equipmentType || undefined,
|
||||
brand: searchForm.brand || undefined,
|
||||
supplierName: searchForm.supplierName || undefined,
|
||||
purchaseOrder: searchForm.purchaseOrder || undefined,
|
||||
equipmentStatus: searchForm.equipmentStatus || undefined,
|
||||
locationStatus: searchForm.locationStatus || undefined,
|
||||
healthStatus: searchForm.healthStatus || undefined,
|
||||
responsiblePerson: searchForm.responsiblePerson || undefined,
|
||||
maintenancePerson: searchForm.maintenancePerson || undefined,
|
||||
physicalLocation: searchForm.physicalLocation || undefined,
|
||||
assetCode: searchForm.assetCode || undefined,
|
||||
equipmentSn: searchForm.equipmentSn || undefined,
|
||||
inventoryBarcode: searchForm.inventoryBarcode || undefined,
|
||||
specification: searchForm.specification || undefined,
|
||||
assetRemark: searchForm.assetRemark || undefined,
|
||||
}
|
||||
|
||||
// 处理时间范围
|
||||
if (searchForm.purchaseTimeRange && searchForm.purchaseTimeRange.length === 2) {
|
||||
params.purchaseTimeStart = searchForm.purchaseTimeRange[0]
|
||||
params.purchaseTimeEnd = searchForm.purchaseTimeRange[1]
|
||||
}
|
||||
|
||||
if (searchForm.inStockTimeRange && searchForm.inStockTimeRange.length === 2) {
|
||||
params.inStockTimeStart = searchForm.inStockTimeRange[0]
|
||||
params.inStockTimeEnd = searchForm.inStockTimeRange[1]
|
||||
}
|
||||
|
||||
if (searchForm.activationTimeRange && searchForm.activationTimeRange.length === 2) {
|
||||
params.activationTimeStart = searchForm.activationTimeRange[0]
|
||||
params.activationTimeEnd = searchForm.activationTimeRange[1]
|
||||
}
|
||||
|
||||
// 处理价格范围
|
||||
if (searchForm.minPrice !== undefined) {
|
||||
params.minPrice = searchForm.minPrice
|
||||
}
|
||||
|
||||
if (searchForm.maxPrice !== undefined) {
|
||||
params.maxPrice = searchForm.maxPrice
|
||||
}
|
||||
|
||||
emit('search', params)
|
||||
showSearchModal.value = false
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
Object.keys(searchForm).forEach(key => {
|
||||
if (Array.isArray(searchForm[key])) {
|
||||
searchForm[key] = []
|
||||
} else {
|
||||
searchForm[key] = ''
|
||||
}
|
||||
})
|
||||
formRef.value?.resetFields()
|
||||
emit('reset')
|
||||
showSearchModal.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.search-form {
|
||||
.arco-form-item {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.arco-form-item-label {
|
||||
font-weight: 500;
|
||||
color: var(--color-text-1);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.arco-input,
|
||||
.arco-select,
|
||||
.arco-input-number {
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--color-border);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-primary-light-3);
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&.arco-input-focus,
|
||||
&.arco-select-focus {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 2px rgba(var(--primary-6), 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.arco-range-picker {
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--color-border);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-primary-light-3);
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&.arco-range-picker-focus {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 2px rgba(var(--primary-6), 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--color-border);
|
||||
|
||||
.arco-btn {
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
min-width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.search-form {
|
||||
.arco-row {
|
||||
.arco-col {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
.arco-space {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,856 @@
|
|||
<template>
|
||||
<div class="equipment-procurement-container">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="page-title">
|
||||
<IconDesktop style="font-size: 24px; margin-right: 12px; color: var(--color-primary);" />
|
||||
<h1>设备采购管理</h1>
|
||||
</div>
|
||||
<div class="page-description">
|
||||
管理企业设备采购流程,包括采购申请、订单管理、供应商管理等
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<a-space>
|
||||
<ProcurementSearch
|
||||
:loading="loading"
|
||||
@search="handleSearch"
|
||||
@reset="handleReset"
|
||||
/>
|
||||
<a-button type="primary" @click="handleAdd" size="large">
|
||||
<template #icon>
|
||||
<IconPlus />
|
||||
</template>
|
||||
新增采购
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-container">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :bordered="false">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
|
||||
<IconDesktop />
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-number">{{ pagination.total }}</div>
|
||||
<div class="stat-label">采购总数</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :bordered="false">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
|
||||
<IconClockCircle />
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-number">{{ getPendingCount() }}</div>
|
||||
<div class="stat-label">待处理</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :bordered="false">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
|
||||
<IconCheckCircle />
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-number">{{ getCompletedCount() }}</div>
|
||||
<div class="stat-label">已完成</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :bordered="false">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon" style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);">
|
||||
<IconApps />
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-number">¥{{ getTotalAmount() }}</div>
|
||||
<div class="stat-label">采购总额</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<a-card class="table-card" :bordered="false">
|
||||
<template #title>
|
||||
<div class="card-title">
|
||||
<span>采购记录</span>
|
||||
<div class="table-actions">
|
||||
<a-space>
|
||||
<a-button type="text" @click="refreshData">
|
||||
<template #icon>
|
||||
<IconRefresh />
|
||||
</template>
|
||||
刷新
|
||||
</a-button>
|
||||
<a-button type="text" @click="handleExport">
|
||||
<template #icon>
|
||||
<IconDownload />
|
||||
</template>
|
||||
导出
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:row-selection="rowSelection"
|
||||
row-key="equipmentId"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<!-- 设备状态 -->
|
||||
<template #equipmentStatus="{ record }">
|
||||
<a-tag :color="getEquipmentStatusColor(record.equipmentStatus)">
|
||||
{{ getEquipmentStatusText(record.equipmentStatus) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 位置状态 -->
|
||||
<template #locationStatus="{ record }">
|
||||
<a-tag :color="getLocationStatusColor(record.locationStatus)">
|
||||
{{ getLocationStatusText(record.locationStatus) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 健康状态 -->
|
||||
<template #healthStatus="{ record }">
|
||||
<a-tag :color="getHealthStatusColor(record.healthStatus)">
|
||||
{{ getHealthStatusText(record.healthStatus) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 采购价格 -->
|
||||
<template #purchasePrice="{ record }">
|
||||
<span v-if="record.purchasePrice" class="price-text">
|
||||
¥{{ formatPrice(record.purchasePrice) }}
|
||||
</span>
|
||||
<span v-else class="no-data">-</span>
|
||||
</template>
|
||||
|
||||
<!-- 当前净值 -->
|
||||
<template #currentNetValue="{ record }">
|
||||
<span v-if="record.currentNetValue" class="price-text">
|
||||
¥{{ formatPrice(record.currentNetValue) }}
|
||||
</span>
|
||||
<span v-else class="no-data">-</span>
|
||||
</template>
|
||||
|
||||
<!-- 创建时间 -->
|
||||
<template #createTime="{ record }">
|
||||
<span v-if="record.createTime" class="time-text">
|
||||
{{ formatDateTime(record.createTime) }}
|
||||
</span>
|
||||
<span v-else class="no-data">-</span>
|
||||
</template>
|
||||
|
||||
<!-- 操作 -->
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-button type="text" size="small" @click="handleView(record)">
|
||||
查看
|
||||
</a-button>
|
||||
<a-button type="text" size="small" @click="handleEdit(record)">
|
||||
编辑
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
content="确定要删除这条采购记录吗?"
|
||||
@ok="handleDelete(record)"
|
||||
>
|
||||
<a-button type="text" size="small" status="danger">
|
||||
删除
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 新增/编辑弹窗 -->
|
||||
<ProcurementModal
|
||||
v-model:visible="modalVisible"
|
||||
:procurement-data="currentProcurement"
|
||||
:mode="modalMode"
|
||||
@success="handleModalSuccess"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, ref, watch } from 'vue'
|
||||
import { Modal } from '@arco-design/web-vue'
|
||||
import {
|
||||
IconCheckCircle,
|
||||
IconClockCircle,
|
||||
IconDownload,
|
||||
IconPlus,
|
||||
IconRefresh,
|
||||
IconDesktop,
|
||||
IconApps
|
||||
} from '@arco-design/web-vue/es/icon'
|
||||
import message from '@arco-design/web-vue/es/message'
|
||||
import ProcurementModal from './components/ProcurementModal.vue'
|
||||
import ProcurementSearch from './components/ProcurementSearch.vue'
|
||||
import { equipmentProcurementApi } from '@/apis/equipment/procurement'
|
||||
import type { EquipmentListReq, EquipmentResp } from '@/apis/equipment/type'
|
||||
|
||||
defineOptions({ name: 'EquipmentProcurement' })
|
||||
|
||||
// 当前搜索参数
|
||||
const currentSearchParams = ref<EquipmentListReq>({
|
||||
minPrice: undefined,
|
||||
maxPrice: undefined
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref<EquipmentResp[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
// 分页配置
|
||||
const pagination = reactive<any>({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showPageSize: true,
|
||||
showJumper: true,
|
||||
showTotal: (total: number) => `共 ${total} 条记录`,
|
||||
})
|
||||
|
||||
// 弹窗控制
|
||||
const modalVisible = ref(false)
|
||||
const currentProcurement = ref<EquipmentResp | null>(null)
|
||||
const modalMode = ref<'add' | 'edit' | 'view'>('add')
|
||||
|
||||
// 表格选择
|
||||
const selectedRowKeys = ref<string[]>([])
|
||||
const rowSelection = reactive({
|
||||
type: 'checkbox' as const,
|
||||
showCheckedAll: true,
|
||||
selectedRowKeys,
|
||||
onChange: (keys: string[]) => {
|
||||
selectedRowKeys.value = keys
|
||||
},
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '资产编号',
|
||||
dataIndex: 'assetCode',
|
||||
key: 'assetCode',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '设备名称',
|
||||
dataIndex: 'equipmentName',
|
||||
key: 'equipmentName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '设备类型',
|
||||
dataIndex: 'equipmentType',
|
||||
key: 'equipmentType',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '设备型号',
|
||||
dataIndex: 'equipmentModel',
|
||||
key: 'equipmentModel',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '品牌',
|
||||
dataIndex: 'brand',
|
||||
key: 'brand',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '供应商',
|
||||
dataIndex: 'supplierName',
|
||||
key: 'supplierName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '采购订单',
|
||||
dataIndex: 'purchaseOrder',
|
||||
key: 'purchaseOrder',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '采购价格',
|
||||
dataIndex: 'purchasePrice',
|
||||
key: 'purchasePrice',
|
||||
slotName: 'purchasePrice',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '当前净值',
|
||||
dataIndex: 'currentNetValue',
|
||||
key: 'currentNetValue',
|
||||
slotName: 'currentNetValue',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '设备状态',
|
||||
dataIndex: 'equipmentStatus',
|
||||
key: 'equipmentStatus',
|
||||
slotName: 'equipmentStatus',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '位置状态',
|
||||
dataIndex: 'locationStatus',
|
||||
key: 'locationStatus',
|
||||
slotName: 'locationStatus',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '健康状态',
|
||||
dataIndex: 'healthStatus',
|
||||
key: 'healthStatus',
|
||||
slotName: 'healthStatus',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '负责人',
|
||||
dataIndex: 'responsiblePerson',
|
||||
key: 'responsiblePerson',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
slotName: 'createTime',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
slotName: 'action',
|
||||
width: 200,
|
||||
fixed: 'right',
|
||||
},
|
||||
]
|
||||
|
||||
// 获取设备状态颜色
|
||||
const getEquipmentStatusColor = (status: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
normal: 'green',
|
||||
repair: 'orange',
|
||||
maintain: 'blue',
|
||||
scrap: 'red',
|
||||
}
|
||||
return colorMap[status] || 'blue'
|
||||
}
|
||||
|
||||
// 获取设备状态文本
|
||||
const getEquipmentStatusText = (status: string) => {
|
||||
const textMap: Record<string, string> = {
|
||||
normal: '正常',
|
||||
repair: '维修中',
|
||||
maintain: '保养中',
|
||||
scrap: '报废',
|
||||
}
|
||||
return textMap[status] || '未知'
|
||||
}
|
||||
|
||||
// 获取位置状态颜色
|
||||
const getLocationStatusColor = (status: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
not_in_stock: 'gray',
|
||||
in_stock: 'blue',
|
||||
allocated: 'green',
|
||||
repair: 'orange',
|
||||
scrap: 'red',
|
||||
scrapped: 'red',
|
||||
borrowed: 'purple',
|
||||
lost: 'gray',
|
||||
}
|
||||
return colorMap[status] || 'blue'
|
||||
}
|
||||
|
||||
// 获取位置状态文本
|
||||
const getLocationStatusText = (status: string) => {
|
||||
const textMap: Record<string, string> = {
|
||||
not_in_stock: '未入库',
|
||||
in_stock: '库存中',
|
||||
allocated: '已分配',
|
||||
repair: '维修中',
|
||||
scrap: '待报废',
|
||||
scrapped: '已报废',
|
||||
borrowed: '外借中',
|
||||
lost: '丢失',
|
||||
}
|
||||
return textMap[status] || '未知'
|
||||
}
|
||||
|
||||
// 获取健康状态颜色
|
||||
const getHealthStatusColor = (status: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
excellent: 'green',
|
||||
good: 'blue',
|
||||
normal: 'orange',
|
||||
poor: 'red',
|
||||
critical: 'red',
|
||||
}
|
||||
return colorMap[status] || 'blue'
|
||||
}
|
||||
|
||||
// 获取健康状态文本
|
||||
const getHealthStatusText = (status: string) => {
|
||||
const textMap: Record<string, string> = {
|
||||
excellent: '优秀',
|
||||
good: '良好',
|
||||
normal: '一般',
|
||||
poor: '较差',
|
||||
critical: '危险',
|
||||
}
|
||||
return textMap[status] || '未知'
|
||||
}
|
||||
|
||||
// 格式化价格
|
||||
const formatPrice = (price: number) => {
|
||||
return price.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
})
|
||||
}
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (dateTime: string) => {
|
||||
if (!dateTime) return '-'
|
||||
const date = new Date(dateTime)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
// 转换后端数据
|
||||
const transformBackendData = (data: any[]): EquipmentResp[] => {
|
||||
return data.map((item: any) => ({
|
||||
equipmentId: item.equipmentId || item.id,
|
||||
assetCode: item.assetCode,
|
||||
equipmentName: item.equipmentName,
|
||||
equipmentType: item.equipmentType,
|
||||
equipmentTypeLabel: item.equipmentTypeLabel,
|
||||
equipmentModel: item.equipmentModel,
|
||||
equipmentSn: item.equipmentSn,
|
||||
brand: item.brand,
|
||||
specification: item.specification,
|
||||
equipmentStatus: item.equipmentStatus,
|
||||
equipmentStatusLabel: item.equipmentStatusLabel,
|
||||
useStatus: item.useStatus,
|
||||
locationStatus: item.locationStatus,
|
||||
locationStatusLabel: item.locationStatusLabel,
|
||||
physicalLocation: item.physicalLocation,
|
||||
responsiblePerson: item.responsiblePerson,
|
||||
healthStatus: item.healthStatus,
|
||||
healthStatusLabel: item.healthStatusLabel,
|
||||
purchaseTime: item.purchaseTime,
|
||||
inStockTime: item.inStockTime,
|
||||
activationTime: item.activationTime,
|
||||
expectedScrapTime: item.expectedScrapTime,
|
||||
actualScrapTime: item.actualScrapTime,
|
||||
statusChangeTime: item.statusChangeTime,
|
||||
purchaseOrder: item.purchaseOrder,
|
||||
supplierName: item.supplierName,
|
||||
purchasePrice: item.purchasePrice,
|
||||
currentNetValue: item.currentNetValue,
|
||||
depreciationMethod: item.depreciationMethod,
|
||||
depreciationYears: item.depreciationYears,
|
||||
salvageValue: item.salvageValue,
|
||||
warrantyExpireDate: item.warrantyExpireDate,
|
||||
lastMaintenanceDate: item.lastMaintenanceDate,
|
||||
nextMaintenanceDate: item.nextMaintenanceDate,
|
||||
maintenancePerson: item.maintenancePerson,
|
||||
inventoryBarcode: item.inventoryBarcode,
|
||||
assetRemark: item.assetRemark,
|
||||
projectId: item.projectId,
|
||||
projectName: item.projectName,
|
||||
userId: item.userId,
|
||||
name: item.name,
|
||||
createTime: item.createTime,
|
||||
updateTime: item.updateTime,
|
||||
accountNumber: item.accountNumber,
|
||||
quantity: item.quantity,
|
||||
unitPrice: item.unitPrice,
|
||||
totalPrice: item.totalPrice,
|
||||
inventoryBasis: item.inventoryBasis,
|
||||
dynamicRecord: item.dynamicRecord,
|
||||
}))
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
const loadData = async (searchParams?: EquipmentListReq) => {
|
||||
console.log('📊 loadData - 开始加载数据')
|
||||
console.log('📊 loadData - 接收到的搜索参数:', searchParams)
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const params: EquipmentListReq = {
|
||||
pageSize: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
minPrice: undefined,
|
||||
maxPrice: undefined,
|
||||
...(searchParams || {}),
|
||||
}
|
||||
|
||||
console.log('📊 loadData - 构建的完整请求参数:', params)
|
||||
|
||||
const res = await equipmentProcurementApi.page(params)
|
||||
|
||||
console.log('API响应:', res)
|
||||
|
||||
if (res.success || res.status === 200 || res.code === 200) {
|
||||
let dataList: any[] = []
|
||||
|
||||
if (Array.isArray(res.data)) {
|
||||
dataList = res.data
|
||||
} else if (res.data && Array.isArray((res.data as any).records)) {
|
||||
dataList = (res.data as any).records
|
||||
} else if (res.data && Array.isArray((res.data as any).list)) {
|
||||
dataList = (res.data as any).list
|
||||
} else if (res.data && Array.isArray((res.data as any).rows)) {
|
||||
dataList = (res.data as any).rows
|
||||
}
|
||||
|
||||
console.log('处理后的数据列表:', dataList)
|
||||
|
||||
if (dataList.length > 0) {
|
||||
const transformedData = transformBackendData(dataList)
|
||||
console.log('转换后的数据:', transformedData)
|
||||
tableData.value = transformedData
|
||||
} else {
|
||||
tableData.value = []
|
||||
}
|
||||
|
||||
pagination.total = (res.data as any)?.total || (res as any).total || dataList.length || 0
|
||||
console.log('总数:', pagination.total)
|
||||
} else {
|
||||
message.error(res.msg || '加载数据失败')
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('加载数据失败:', error)
|
||||
message.error(error?.message || '加载数据失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = (searchParams: EquipmentListReq) => {
|
||||
console.log('🔍 主组件 - 接收到的搜索参数:', searchParams)
|
||||
pagination.current = 1
|
||||
currentSearchParams.value = { ...searchParams }
|
||||
loadData(searchParams)
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
console.log('🔄 主组件 - 重置操作')
|
||||
pagination.current = 1
|
||||
currentSearchParams.value = {
|
||||
minPrice: undefined,
|
||||
maxPrice: undefined
|
||||
}
|
||||
loadData()
|
||||
}
|
||||
|
||||
// 表格变化
|
||||
const handleTableChange = (pag: any) => {
|
||||
pagination.current = pag.current || 1
|
||||
pagination.pageSize = pag.pageSize || 10
|
||||
loadData(currentSearchParams.value)
|
||||
}
|
||||
|
||||
// 新增
|
||||
const handleAdd = () => {
|
||||
modalMode.value = 'add'
|
||||
currentProcurement.value = null
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
// 查看
|
||||
const handleView = (record: EquipmentResp) => {
|
||||
modalMode.value = 'view'
|
||||
currentProcurement.value = { ...record }
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑
|
||||
const handleEdit = (record: EquipmentResp) => {
|
||||
modalMode.value = 'edit'
|
||||
currentProcurement.value = { ...record }
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDelete = async (record: EquipmentResp) => {
|
||||
try {
|
||||
await equipmentProcurementApi.delete(record.equipmentId)
|
||||
message.success('删除成功')
|
||||
loadData(currentSearchParams.value)
|
||||
} catch (error: any) {
|
||||
console.error('删除失败:', error)
|
||||
message.error(error?.message || '删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗成功回调
|
||||
const handleModalSuccess = () => {
|
||||
modalVisible.value = false
|
||||
loadData(currentSearchParams.value)
|
||||
}
|
||||
|
||||
// 刷新数据
|
||||
const refreshData = () => {
|
||||
loadData(currentSearchParams.value)
|
||||
}
|
||||
|
||||
// 导出数据
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
const response = await equipmentProcurementApi.export(currentSearchParams.value)
|
||||
const blob = response.data || response
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `设备采购记录_${new Date().toISOString().split('T')[0]}.xlsx`
|
||||
link.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
message.success('导出成功')
|
||||
} catch (error: any) {
|
||||
console.error('导出失败:', error)
|
||||
message.error(error?.message || '导出失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 统计函数
|
||||
const getPendingCount = () => {
|
||||
return tableData.value.filter(item =>
|
||||
item.equipmentStatus === 'pending' ||
|
||||
item.locationStatus === 'pending'
|
||||
).length
|
||||
}
|
||||
|
||||
const getCompletedCount = () => {
|
||||
return tableData.value.filter(item =>
|
||||
item.equipmentStatus === 'completed' ||
|
||||
item.locationStatus === 'completed'
|
||||
).length
|
||||
}
|
||||
|
||||
const getTotalAmount = () => {
|
||||
const total = tableData.value.reduce((sum, item) => {
|
||||
return sum + (item.purchasePrice || 0)
|
||||
}, 0)
|
||||
return formatPrice(total)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.equipment-procurement-container {
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.header-left {
|
||||
.page-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
color: white;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f0f0f0 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
}
|
||||
|
||||
.page-description {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.header-right {
|
||||
.arco-btn {
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.stat-card {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.stat-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 16px;
|
||||
|
||||
.arco-icon {
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
|
||||
.stat-number {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-1);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-card {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.card-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
|
||||
.table-actions {
|
||||
.arco-btn {
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.arco-table {
|
||||
.arco-table-th {
|
||||
background-color: var(--color-fill-2);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.arco-table-tr:hover {
|
||||
background-color: var(--color-fill-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.price-text {
|
||||
color: #f56c6c;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.time-text {
|
||||
color: var(--color-text-2);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
color: var(--color-text-4);
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.equipment-procurement-container {
|
||||
.page-header {
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
.header-right {
|
||||
margin-top: 16px;
|
||||
width: 100%;
|
||||
|
||||
.arco-space {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
.arco-col {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,136 @@
|
|||
<template>
|
||||
<div class="procurement-test">
|
||||
<a-card title="设备采购模块测试" :bordered="false">
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="testApi">
|
||||
测试API
|
||||
</a-button>
|
||||
<a-button @click="testSearch">
|
||||
测试搜索
|
||||
</a-button>
|
||||
<a-button @click="testAdd">
|
||||
测试新增
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<div class="test-results">
|
||||
<h3>测试结果</h3>
|
||||
<a-textarea
|
||||
v-model="testResults"
|
||||
:rows="10"
|
||||
placeholder="测试结果将显示在这里..."
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<div class="test-params">
|
||||
<h3>测试参数</h3>
|
||||
<a-form layout="vertical">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="设备名称">
|
||||
<a-input v-model="testParams.equipmentName" placeholder="测试设备" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="设备型号">
|
||||
<a-input v-model="testParams.equipmentModel" placeholder="测试型号" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="供应商">
|
||||
<a-input v-model="testParams.supplierName" placeholder="测试供应商" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { equipmentProcurementApi } from '@/apis/equipment/procurement'
|
||||
|
||||
defineOptions({ name: 'ProcurementTest' })
|
||||
|
||||
const testResults = ref('')
|
||||
const testParams = reactive({
|
||||
equipmentName: '测试设备',
|
||||
equipmentModel: '测试型号',
|
||||
supplierName: '测试供应商',
|
||||
})
|
||||
|
||||
// 添加测试结果
|
||||
const addTestResult = (message: string) => {
|
||||
const timestamp = new Date().toLocaleString()
|
||||
testResults.value += `[${timestamp}] ${message}\n`
|
||||
}
|
||||
|
||||
// 测试API
|
||||
const testApi = async () => {
|
||||
try {
|
||||
addTestResult('开始测试API...')
|
||||
|
||||
const params = {
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
equipmentName: testParams.equipmentName,
|
||||
equipmentModel: testParams.equipmentModel,
|
||||
supplierName: testParams.supplierName,
|
||||
}
|
||||
|
||||
addTestResult(`请求参数: ${JSON.stringify(params, null, 2)}`)
|
||||
|
||||
const response = await equipmentProcurementApi.page(params)
|
||||
|
||||
addTestResult(`API响应: ${JSON.stringify(response, null, 2)}`)
|
||||
Message.success('API测试成功')
|
||||
} catch (error: any) {
|
||||
addTestResult(`API测试失败: ${error.message}`)
|
||||
Message.error('API测试失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 测试搜索
|
||||
const testSearch = () => {
|
||||
addTestResult('测试搜索功能...')
|
||||
addTestResult(`搜索参数: ${JSON.stringify(testParams, null, 2)}`)
|
||||
Message.info('搜索测试完成')
|
||||
}
|
||||
|
||||
// 测试新增
|
||||
const testAdd = () => {
|
||||
addTestResult('测试新增功能...')
|
||||
addTestResult(`新增参数: ${JSON.stringify(testParams, null, 2)}`)
|
||||
Message.info('新增测试完成')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.procurement-test {
|
||||
.test-results {
|
||||
margin-bottom: 24px;
|
||||
|
||||
h3 {
|
||||
margin-bottom: 16px;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
}
|
||||
|
||||
.test-params {
|
||||
h3 {
|
||||
margin-bottom: 16px;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,81 @@
|
|||
<template>
|
||||
<div class="training-detail-container">
|
||||
<a-card title="培训基本信息">
|
||||
<!-- 培训基本信息展示区 -->
|
||||
</a-card>
|
||||
<a-card title="互动与留痕" style="margin-top: 16px">
|
||||
<a-tabs>
|
||||
<a-tab-pane key="sign" tab="签到">
|
||||
<div class="sign-in-section">
|
||||
<a-button type="primary" :disabled="signedIn" @click="handleSignIn">
|
||||
{{ signedIn ? '已签到' : '点击签到' }}
|
||||
</a-button>
|
||||
<div v-if="signedIn" class="sign-in-success">签到成功!</div>
|
||||
<a-divider />
|
||||
<div class="sign-in-records">
|
||||
<h4>签到记录</h4>
|
||||
<a-table :data="signInRecords" :columns="signInColumns" row-key="id" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="quiz" tab="答题">
|
||||
<!-- 答题互动区 -->
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="discussion" tab="讨论">
|
||||
<!-- 讨论区 -->
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="score" tab="打分/评价">
|
||||
<!-- 打分/评价区 -->
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="feedback" tab="反馈/留痕">
|
||||
<!-- 反馈/留痕区 -->
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
<a-card title="案例/公开课关联" style="margin-top: 16px">
|
||||
<!-- 关联案例/公开课展示区 -->
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// 签到状态
|
||||
const signedIn = ref(false)
|
||||
|
||||
// 签到记录(模拟数据)
|
||||
const signInRecords = ref([
|
||||
{ id: 1, name: '张三', time: '2024-06-01 09:00:00' },
|
||||
{ id: 2, name: '李四', time: '2024-06-01 09:01:00' },
|
||||
])
|
||||
|
||||
const signInColumns = [
|
||||
{ title: '姓名', dataIndex: 'name', key: 'name' },
|
||||
{ title: '签到时间', dataIndex: 'time', key: 'time' },
|
||||
]
|
||||
|
||||
// 签到操作
|
||||
function handleSignIn() {
|
||||
signedIn.value = true
|
||||
// 实际应用中应调用后端API并刷新记录
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.training-detail-container {
|
||||
padding: 16px;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.sign-in-section {
|
||||
padding: 16px 0;
|
||||
}
|
||||
.sign-in-success {
|
||||
color: #52c41a;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.sign-in-records {
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,576 @@
|
|||
<template>
|
||||
<a-modal
|
||||
:visible="visible"
|
||||
:title="isEdit ? '编辑培训计划' : '新增培训计划'"
|
||||
width="800px"
|
||||
:confirm-loading="loading"
|
||||
:ok-button-props="{ disabled: !isFormValid }"
|
||||
@cancel="handleCancel"
|
||||
@ok="handleSubmit"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
auto-label-width
|
||||
>
|
||||
<a-form-item label="计划名称" field="planName">
|
||||
<a-input
|
||||
v-model="formData.planName"
|
||||
placeholder="请输入计划名称"
|
||||
:disabled="isView"
|
||||
show-word-limit
|
||||
:max-length="100"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="培训类型" field="trainingType">
|
||||
<a-select
|
||||
v-model="formData.trainingType"
|
||||
:options="trainingTypeOptions"
|
||||
placeholder="请选择培训类型"
|
||||
:disabled="isView"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="培训级别" field="trainingLevel">
|
||||
<a-select
|
||||
v-model="formData.trainingLevel"
|
||||
:options="trainingLevelOptions"
|
||||
placeholder="请选择培训级别"
|
||||
:disabled="isView"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="培训内容" field="trainingContent">
|
||||
<a-textarea
|
||||
v-model="formData.trainingContent"
|
||||
placeholder="请输入培训内容"
|
||||
:rows="4"
|
||||
:disabled="isView"
|
||||
show-word-limit
|
||||
:max-length="500"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="培训讲师" field="trainer">
|
||||
<a-input
|
||||
v-model="formData.trainer"
|
||||
placeholder="请输入培训讲师"
|
||||
:disabled="isView"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="培训地点" field="trainingLocation">
|
||||
<a-input
|
||||
v-model="formData.trainingLocation"
|
||||
placeholder="请输入培训地点"
|
||||
:disabled="isView"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="开始时间" field="startTime">
|
||||
<a-date-picker
|
||||
v-model="formData.startTime"
|
||||
show-time
|
||||
placeholder="请选择开始时间"
|
||||
style="width: 100%"
|
||||
:disabled="isView"
|
||||
:disabled-date="(current) => current && current < dayjs().startOf('day')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="结束时间" field="endTime">
|
||||
<a-date-picker
|
||||
v-model="formData.endTime"
|
||||
show-time
|
||||
placeholder="请选择结束时间"
|
||||
style="width: 100%"
|
||||
:disabled="isView"
|
||||
:disabled-date="(current) => current && current < dayjs().startOf('day')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="最大参训人数" field="maxParticipants">
|
||||
<a-input-number
|
||||
v-model="formData.maxParticipants"
|
||||
placeholder="请输入最大参训人数"
|
||||
:min="1"
|
||||
:max="1000"
|
||||
style="width: 100%"
|
||||
:disabled="isView"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="培训要求" field="requirements">
|
||||
<a-textarea
|
||||
v-model="formData.requirements"
|
||||
placeholder="请输入培训要求"
|
||||
:rows="3"
|
||||
:disabled="isView"
|
||||
show-word-limit
|
||||
:max-length="300"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="备注" field="remark">
|
||||
<a-textarea
|
||||
v-model="formData.remark"
|
||||
placeholder="请输入备注"
|
||||
:rows="3"
|
||||
:disabled="isView"
|
||||
show-word-limit
|
||||
:max-length="200"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 调试按钮 - 仅在开发环境显示 -->
|
||||
<a-form-item v-if="isDev" label="调试">
|
||||
<a-space>
|
||||
<a-button type="dashed" size="small" @click="testFormData">
|
||||
测试表单数据绑定
|
||||
</a-button>
|
||||
<a-button type="dashed" size="small" @click="fillTestData">
|
||||
填充测试数据
|
||||
</a-button>
|
||||
<a-button size="small" type="dashed" @click="clearFormData">
|
||||
清空表单数据
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import type { FormInstance } from '@arco-design/web-vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { createTrainingPlan, updateTrainingPlan } from '@/apis/training'
|
||||
import type { TrainingPlanReq, TrainingPlanResp } from '@/types/training.d'
|
||||
|
||||
defineOptions({ name: 'TrainingPlanModal' })
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
planData?: TrainingPlanResp | null
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:visible', visible: boolean): void
|
||||
(e: 'success'): void
|
||||
}
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref<FormInstance>()
|
||||
const loading = ref(false)
|
||||
|
||||
// 开发环境标识
|
||||
const isDev = import.meta.env.DEV
|
||||
|
||||
// 是否为编辑模式
|
||||
const isEdit = computed(() => !!props.planData?.planId)
|
||||
// 是否为查看模式
|
||||
const isView = computed(() => !!props.planData?.planId && !isEdit.value)
|
||||
|
||||
// 表单数据类型定义
|
||||
interface FormDataType {
|
||||
planName: string
|
||||
trainingType: string
|
||||
trainingLevel: string
|
||||
trainingContent: string
|
||||
trainer: string
|
||||
trainingLocation: string
|
||||
startTime: any // dayjs对象或null
|
||||
endTime: any // dayjs对象或null
|
||||
maxParticipants?: number
|
||||
requirements: string
|
||||
remark: string
|
||||
status?: string
|
||||
}
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive<FormDataType>({
|
||||
planName: '',
|
||||
trainingType: '',
|
||||
trainingLevel: '',
|
||||
trainingContent: '',
|
||||
trainer: '',
|
||||
trainingLocation: '',
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
maxParticipants: undefined,
|
||||
requirements: '',
|
||||
remark: '',
|
||||
status: 'DRAFT',
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
planName: [
|
||||
{ required: true, message: '请输入计划名称', trigger: 'blur' },
|
||||
{ min: 2, max: 100, message: '计划名称长度应在2-100个字符之间', trigger: 'blur' },
|
||||
],
|
||||
trainingType: [{ required: true, message: '请选择培训类型', trigger: 'change' }],
|
||||
trainingLevel: [{ required: true, message: '请选择培训级别', trigger: 'change' }],
|
||||
startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
|
||||
endTime: [
|
||||
{ required: true, message: '请选择结束时间', trigger: 'change' },
|
||||
{
|
||||
validator: (value: any, callback: any) => {
|
||||
if (value && formData.startTime && dayjs(value).isBefore(dayjs(formData.startTime))) {
|
||||
callback('结束时间不能早于开始时间')
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
maxParticipants: [
|
||||
{
|
||||
validator: (value: any, callback: any) => {
|
||||
if (value !== undefined && value !== null) {
|
||||
if (value < 1) {
|
||||
callback('最大参训人数不能小于1')
|
||||
} else if (value > 1000) {
|
||||
callback('最大参训人数不能超过1000')
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
trainingContent: [
|
||||
{ max: 500, message: '培训内容长度不能超过500个字符', trigger: 'blur' },
|
||||
],
|
||||
requirements: [
|
||||
{ max: 300, message: '培训要求长度不能超过300个字符', trigger: 'blur' },
|
||||
],
|
||||
remark: [
|
||||
{ max: 200, message: '备注长度不能超过200个字符', trigger: 'blur' },
|
||||
],
|
||||
}
|
||||
|
||||
// 培训类型选项
|
||||
const trainingTypeOptions = [
|
||||
{ label: '安全教育', value: 'SAFETY' },
|
||||
{ label: '技能培训', value: 'SKILL' },
|
||||
{ label: '企业文化', value: 'CULTURE' },
|
||||
]
|
||||
// 培训级别选项
|
||||
const trainingLevelOptions = [
|
||||
{ label: '现场级', value: 'SITE' },
|
||||
{ label: '部门级', value: 'DEPARTMENT' },
|
||||
{ label: '公司级', value: 'COMPANY' },
|
||||
]
|
||||
|
||||
// 监听弹窗显示状态
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
initFormData()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// 监听计划数据变化
|
||||
watch(
|
||||
() => props.planData,
|
||||
() => {
|
||||
if (props.visible) {
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
initFormData()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// 监听表单数据变化(调试用)
|
||||
watch(
|
||||
formData,
|
||||
(newData) => {
|
||||
console.log('表单数据变化:', newData)
|
||||
console.log('表单数据详情:', {
|
||||
planName: newData.planName,
|
||||
trainingType: newData.trainingType,
|
||||
trainingLevel: newData.trainingLevel,
|
||||
startTime: newData.startTime,
|
||||
endTime: newData.endTime,
|
||||
maxParticipants: newData.maxParticipants,
|
||||
trainer: newData.trainer,
|
||||
trainingLocation: newData.trainingLocation,
|
||||
trainingContent: newData.trainingContent,
|
||||
requirements: newData.requirements,
|
||||
remark: newData.remark,
|
||||
})
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
// 监听开始时间变化,自动调整结束时间
|
||||
watch(
|
||||
() => formData.startTime,
|
||||
(newStartTime) => {
|
||||
console.log('开始时间变化:', newStartTime)
|
||||
if (newStartTime && formData.endTime && dayjs(formData.endTime).isBefore(dayjs(newStartTime))) {
|
||||
// 如果结束时间早于开始时间,清空结束时间
|
||||
formData.endTime = null
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// 监听结束时间变化
|
||||
watch(
|
||||
() => formData.endTime,
|
||||
(newEndTime) => {
|
||||
console.log('结束时间变化:', newEndTime)
|
||||
},
|
||||
)
|
||||
|
||||
// 计算表单是否有效
|
||||
const isFormValid = computed(() => {
|
||||
return formData.planName.trim()
|
||||
&& formData.trainingType
|
||||
&& formData.trainingLevel
|
||||
&& formData.startTime
|
||||
&& formData.endTime
|
||||
})
|
||||
|
||||
// 初始化表单数据
|
||||
const initFormData = () => {
|
||||
console.log('初始化表单数据,props.planData:', props.planData)
|
||||
|
||||
if (props.planData) {
|
||||
// 编辑模式:填充现有数据
|
||||
Object.assign(formData, {
|
||||
planName: props.planData.planName || '',
|
||||
trainingType: props.planData.trainingType || '',
|
||||
trainingLevel: props.planData.trainingLevel || '',
|
||||
trainingContent: props.planData.trainingContent || '',
|
||||
trainer: props.planData.trainer || '',
|
||||
trainingLocation: props.planData.trainingLocation || '',
|
||||
startTime: props.planData.startTime ? dayjs(props.planData.startTime) : null,
|
||||
endTime: props.planData.endTime ? dayjs(props.planData.endTime) : null,
|
||||
maxParticipants: props.planData.maxParticipants,
|
||||
requirements: props.planData.requirements || '',
|
||||
remark: props.planData.remark || '',
|
||||
status: props.planData.status || 'DRAFT',
|
||||
})
|
||||
} else {
|
||||
// 新增模式:重置为空值
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
resetFormData()
|
||||
}
|
||||
|
||||
console.log('初始化后的表单数据:', formData)
|
||||
}
|
||||
|
||||
// 重置表单数据
|
||||
const resetFormData = () => {
|
||||
Object.assign(formData, {
|
||||
planName: '',
|
||||
trainingType: '',
|
||||
trainingLevel: '',
|
||||
trainingContent: '',
|
||||
trainer: '',
|
||||
trainingLocation: '',
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
maxParticipants: undefined,
|
||||
requirements: '',
|
||||
remark: '',
|
||||
status: 'DRAFT',
|
||||
})
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
console.log('开始提交表单,表单数据:', formData)
|
||||
console.log('表单数据原始值:', {
|
||||
planName: formData.planName,
|
||||
trainingType: formData.trainingType,
|
||||
trainingLevel: formData.trainingLevel,
|
||||
startTime: formData.startTime,
|
||||
endTime: formData.endTime,
|
||||
maxParticipants: formData.maxParticipants,
|
||||
trainer: formData.trainer,
|
||||
trainingLocation: formData.trainingLocation,
|
||||
trainingContent: formData.trainingContent,
|
||||
requirements: formData.requirements,
|
||||
remark: formData.remark,
|
||||
})
|
||||
|
||||
// 表单验证
|
||||
await formRef.value?.validate()
|
||||
loading.value = true
|
||||
|
||||
// 数据验证 - 确保关键字段不为空
|
||||
if (!formData.planName?.trim()) {
|
||||
throw new Error('计划名称不能为空')
|
||||
}
|
||||
if (!formData.trainingType) {
|
||||
throw new Error('培训类型不能为空')
|
||||
}
|
||||
if (!formData.trainingLevel) {
|
||||
throw new Error('培训级别不能为空')
|
||||
}
|
||||
if (!formData.startTime) {
|
||||
throw new Error('开始时间不能为空')
|
||||
}
|
||||
if (!formData.endTime) {
|
||||
throw new Error('结束时间不能为空')
|
||||
}
|
||||
|
||||
// 准备提交数据,确保字段名和格式正确
|
||||
const submitData: TrainingPlanReq = {
|
||||
planName: formData.planName.trim(),
|
||||
trainingType: formData.trainingType,
|
||||
trainingLevel: formData.trainingLevel,
|
||||
trainingContent: formData.trainingContent?.trim() || '',
|
||||
trainer: formData.trainer?.trim() || '',
|
||||
trainingLocation: formData.trainingLocation?.trim() || '',
|
||||
startTime: formData.startTime ? dayjs(formData.startTime).format('YYYY-MM-DD HH:mm:ss') : '',
|
||||
endTime: formData.endTime ? dayjs(formData.endTime).format('YYYY-MM-DD HH:mm:ss') : '',
|
||||
status: formData.status || 'DRAFT',
|
||||
maxParticipants: formData.maxParticipants || undefined,
|
||||
requirements: formData.requirements?.trim() || '',
|
||||
remark: formData.remark?.trim() || '',
|
||||
}
|
||||
|
||||
console.log('提交的数据:', submitData)
|
||||
console.log('提交的数据JSON:', JSON.stringify(submitData, null, 2))
|
||||
|
||||
if (isEdit.value && props.planData) {
|
||||
console.log('执行更新操作')
|
||||
await updateTrainingPlan(props.planData.planId, submitData)
|
||||
Message.success('培训计划更新成功')
|
||||
} else {
|
||||
console.log('执行创建操作')
|
||||
await createTrainingPlan(submitData)
|
||||
Message.success('培训计划创建成功')
|
||||
}
|
||||
|
||||
emit('success')
|
||||
emit('update:visible', false)
|
||||
} catch (error: any) {
|
||||
console.error('提交失败:', error)
|
||||
|
||||
// 更详细的错误处理
|
||||
if (error?.response?.data?.message) {
|
||||
Message.error(error.response.data.message)
|
||||
} else if (error?.message) {
|
||||
Message.error(error.message)
|
||||
} else if (typeof error === 'string') {
|
||||
Message.error(error)
|
||||
} else {
|
||||
Message.error('操作失败,请检查表单数据或稍后重试')
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 取消
|
||||
const handleCancel = () => {
|
||||
// 重置表单
|
||||
formRef.value?.resetFields()
|
||||
resetFormData()
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
// 测试表单数据绑定
|
||||
const testFormData = () => {
|
||||
console.log('=== 表单数据绑定测试 ===')
|
||||
console.log('当前表单数据:', formData)
|
||||
console.log('当前表单数据详情:', {
|
||||
planName: formData.planName,
|
||||
trainingType: formData.trainingType,
|
||||
trainingLevel: formData.trainingLevel,
|
||||
startTime: formData.startTime,
|
||||
endTime: formData.endTime,
|
||||
maxParticipants: formData.maxParticipants,
|
||||
trainer: formData.trainer,
|
||||
trainingLocation: formData.trainingLocation,
|
||||
trainingContent: formData.trainingContent,
|
||||
requirements: formData.requirements,
|
||||
remark: formData.remark,
|
||||
})
|
||||
|
||||
// 测试表单验证
|
||||
formRef.value?.validate().then(() => {
|
||||
console.log('✅ 表单验证通过')
|
||||
}).catch((errors) => {
|
||||
console.log('❌ 表单验证失败:', errors)
|
||||
})
|
||||
|
||||
// 测试表单字段值
|
||||
console.log('表单字段值测试:')
|
||||
console.log('- planName:', formData.planName, typeof formData.planName)
|
||||
console.log('- trainingType:', formData.trainingType, typeof formData.trainingType)
|
||||
console.log('- trainingLevel:', formData.trainingLevel, typeof formData.trainingLevel)
|
||||
console.log('- startTime:', formData.startTime, typeof formData.startTime)
|
||||
console.log('- endTime:', formData.endTime, typeof formData.endTime)
|
||||
}
|
||||
|
||||
// 填充测试数据
|
||||
const fillTestData = () => {
|
||||
Object.assign(formData, {
|
||||
planName: '测试计划名称',
|
||||
trainingType: 'SAFETY',
|
||||
trainingLevel: 'SITE',
|
||||
trainingContent: '测试培训内容',
|
||||
trainer: '测试讲师',
|
||||
trainingLocation: '测试地点',
|
||||
startTime: dayjs('2023-10-27 10:00'),
|
||||
endTime: dayjs('2023-10-27 12:00'),
|
||||
maxParticipants: 50,
|
||||
requirements: '测试培训要求',
|
||||
remark: '测试备注',
|
||||
status: 'DRAFT',
|
||||
})
|
||||
console.log('已填充测试数据')
|
||||
}
|
||||
|
||||
// 清空表单数据
|
||||
const clearFormData = () => {
|
||||
resetFormData()
|
||||
console.log('已清空表单数据')
|
||||
}
|
||||
|
||||
// 监听表单字段变化
|
||||
const watchFormField = (fieldName: string) => {
|
||||
watch(() => formData[fieldName as keyof typeof formData], (newValue, oldValue) => {
|
||||
console.log(`字段 ${fieldName} 变化:`, { oldValue, newValue })
|
||||
})
|
||||
}
|
||||
|
||||
// 监听所有表单字段
|
||||
Object.keys(formData).forEach((fieldName) => {
|
||||
watchFormField(fieldName)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ant-form-item {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,577 @@
|
|||
<template>
|
||||
<div class="training-plan-container">
|
||||
<!-- 搜索表单 -->
|
||||
<a-card class="search-card" :bordered="false">
|
||||
<a-form layout="inline" :model="searchForm" @submit="handleSearch">
|
||||
<a-form-item label="计划名称">
|
||||
<a-input
|
||||
v-model:value="searchForm.planName"
|
||||
placeholder="请输入计划名称"
|
||||
allow-clear
|
||||
style="width: 200px"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="培训类型">
|
||||
<a-select
|
||||
v-model:value="searchForm.trainingType"
|
||||
:options="trainingTypeOptions"
|
||||
placeholder="请选择培训类型"
|
||||
allow-clear
|
||||
style="width: 150px"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="培训级别">
|
||||
<a-select
|
||||
v-model:value="searchForm.trainingLevel"
|
||||
:options="trainingLevelOptions"
|
||||
placeholder="请选择培训级别"
|
||||
allow-clear
|
||||
style="width: 150px"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-select
|
||||
v-model:value="searchForm.status"
|
||||
:options="statusOptions"
|
||||
placeholder="请选择状态"
|
||||
allow-clear
|
||||
style="width: 120px"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" html-type="submit" :loading="loading">
|
||||
<template #icon>
|
||||
<IconSearch />
|
||||
</template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="handleReset">
|
||||
<template #icon>
|
||||
<IconRefresh />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<a-card class="table-card" :bordered="false">
|
||||
<template #title>
|
||||
<div class="card-title">
|
||||
<span>培训计划列表</span>
|
||||
<a-button type="primary" @click="handleAdd">
|
||||
<template #icon>
|
||||
<IconPlus />
|
||||
</template>
|
||||
新增计划
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
row-key="planId"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #trainingType="{ record }">
|
||||
<a-tag :color="getTrainingTypeColor(record.trainingType)">
|
||||
{{ getTrainingTypeText(record.trainingType) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template #trainingLevel="{ record }">
|
||||
<a-tag :color="getTrainingLevelColor(record.trainingLevel)">
|
||||
{{ getTrainingLevelText(record.trainingLevel) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template #participants="{ record }">
|
||||
<span>{{ record.currentParticipants || 0 }}/{{ record.maxParticipants || '-' }}</span>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-button type="text" size="small" @click="handleView(record)">
|
||||
查看
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="record.status === 'DRAFT'"
|
||||
type="text"
|
||||
size="small"
|
||||
@click="handleEdit(record)"
|
||||
>
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="record.status === 'DRAFT'"
|
||||
type="text"
|
||||
size="small"
|
||||
@click="handlePublish(record)"
|
||||
>
|
||||
发布
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="['DRAFT', 'PUBLISHED', 'IN_PROGRESS'].includes(record.status)"
|
||||
type="text"
|
||||
size="small"
|
||||
danger
|
||||
@click="handleCancel(record)"
|
||||
>
|
||||
取消
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="record.status === 'DRAFT'"
|
||||
type="text"
|
||||
size="small"
|
||||
danger
|
||||
@click="handleDelete(record)"
|
||||
>
|
||||
删除
|
||||
</a-button>
|
||||
<a-button type="text" size="small" @click="handleDetail(record)">详情</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 新增/编辑弹窗 -->
|
||||
<TrainingPlanModal
|
||||
v-model:visible="modalVisible"
|
||||
:plan-data="currentPlan"
|
||||
@success="handleModalSuccess"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, ref, watch } from 'vue'
|
||||
import { Modal } from '@arco-design/web-vue'
|
||||
import { IconPlus, IconRefresh, IconSearch } from '@arco-design/web-vue/es/icon'
|
||||
import message from '@arco-design/web-vue/es/message'
|
||||
import TrainingPlanModal from './components/TrainingPlanModal.vue'
|
||||
import {
|
||||
cancelTrainingPlan,
|
||||
deleteTrainingPlan,
|
||||
pageTrainingPlan,
|
||||
publishTrainingPlan,
|
||||
} from '@/apis/training'
|
||||
import type { TrainingPlanPageQuery, TrainingPlanResp } from '@/types/training.d'
|
||||
import router from '@/router'
|
||||
|
||||
defineOptions({ name: 'TrainingPlan' })
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive<TrainingPlanPageQuery>({
|
||||
planName: '',
|
||||
trainingType: '',
|
||||
trainingLevel: '',
|
||||
status: '',
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref<TrainingPlanResp[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
// 分页配置
|
||||
const pagination = reactive<any>({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showPageSize: true,
|
||||
showJumper: true,
|
||||
showTotal: (total: number) => `共 ${total} 条记录`,
|
||||
})
|
||||
|
||||
// 弹窗控制
|
||||
const modalVisible = ref(false)
|
||||
const currentPlan = ref<TrainingPlanResp | null>(null)
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '计划名称',
|
||||
dataIndex: 'planName',
|
||||
key: 'planName',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '培训类型',
|
||||
dataIndex: 'trainingType',
|
||||
key: 'trainingType',
|
||||
slotName: 'trainingType',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '培训级别',
|
||||
dataIndex: 'trainingLevel',
|
||||
key: 'trainingLevel',
|
||||
slotName: 'trainingLevel',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '培训讲师',
|
||||
dataIndex: 'trainer',
|
||||
key: 'trainer',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '培训地点',
|
||||
dataIndex: 'trainingLocation',
|
||||
key: 'trainingLocation',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '开始时间',
|
||||
dataIndex: 'startTime',
|
||||
key: 'startTime',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '结束时间',
|
||||
dataIndex: 'endTime',
|
||||
key: 'endTime',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
slotName: 'status',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '参训人数',
|
||||
dataIndex: 'participants',
|
||||
key: 'participants',
|
||||
slotName: 'participants',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
slotName: 'action',
|
||||
width: 200,
|
||||
fixed: 'right',
|
||||
},
|
||||
]
|
||||
|
||||
// 下拉选项
|
||||
const trainingTypeOptions = [
|
||||
{ label: '安全教育', value: 'SAFETY' },
|
||||
{ label: '技能培训', value: 'SKILL' },
|
||||
{ label: '企业文化', value: 'CULTURE' },
|
||||
]
|
||||
const trainingLevelOptions = [
|
||||
{ label: '现场级', value: 'SITE' },
|
||||
{ label: '部门级', value: 'DEPARTMENT' },
|
||||
{ label: '公司级', value: 'COMPANY' },
|
||||
]
|
||||
const statusOptions = [
|
||||
{ label: '草稿', value: 'DRAFT' },
|
||||
{ label: '已发布', value: 'PUBLISHED' },
|
||||
{ label: '进行中', value: 'IN_PROGRESS' },
|
||||
{ label: '已完成', value: 'COMPLETED' },
|
||||
{ label: '已取消', value: 'CANCELLED' },
|
||||
]
|
||||
|
||||
// 获取培训类型文本
|
||||
const getTrainingTypeText = (type: string) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
SAFETY: '安全教育',
|
||||
SKILL: '技能培训',
|
||||
CULTURE: '企业文化',
|
||||
安全教育: '安全教育',
|
||||
技能培训: '技能培训',
|
||||
企业文化: '企业文化',
|
||||
}
|
||||
return typeMap[type] || type
|
||||
}
|
||||
|
||||
// 获取培训类型颜色
|
||||
const getTrainingTypeColor = (type: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
SAFETY: 'red',
|
||||
SKILL: 'blue',
|
||||
CULTURE: 'green',
|
||||
安全教育: 'red',
|
||||
技能培训: 'blue',
|
||||
企业文化: 'green',
|
||||
}
|
||||
return colorMap[type] || 'default'
|
||||
}
|
||||
|
||||
// 获取培训级别文本
|
||||
const getTrainingLevelText = (level: string) => {
|
||||
const levelMap: Record<string, string> = {
|
||||
SITE: '现场级',
|
||||
DEPARTMENT: '部门级',
|
||||
COMPANY: '公司级',
|
||||
现场级: '现场级',
|
||||
部门级: '部门级',
|
||||
公司级: '公司级',
|
||||
}
|
||||
return levelMap[level] || level
|
||||
}
|
||||
|
||||
// 获取培训级别颜色
|
||||
const getTrainingLevelColor = (level: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
SITE: 'orange',
|
||||
DEPARTMENT: 'purple',
|
||||
COMPANY: 'cyan',
|
||||
现场级: 'orange',
|
||||
部门级: 'purple',
|
||||
公司级: 'cyan',
|
||||
}
|
||||
return colorMap[level] || 'default'
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status: string) => {
|
||||
const statusMap: Record<string, string> = {
|
||||
DRAFT: '草稿',
|
||||
PUBLISHED: '已发布',
|
||||
IN_PROGRESS: '进行中',
|
||||
COMPLETED: '已完成',
|
||||
CANCELLED: '已取消',
|
||||
}
|
||||
return statusMap[status] || status
|
||||
}
|
||||
|
||||
// 获取状态颜色
|
||||
const getStatusColor = (status: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
DRAFT: 'default',
|
||||
PUBLISHED: 'blue',
|
||||
IN_PROGRESS: 'processing',
|
||||
COMPLETED: 'success',
|
||||
CANCELLED: 'error',
|
||||
}
|
||||
return colorMap[status] || 'default'
|
||||
}
|
||||
|
||||
// 数据转换函数
|
||||
const transformBackendData = (data: any[]) => {
|
||||
console.log('转换前的数据:', data)
|
||||
return data.map((item) => {
|
||||
console.log('处理单个项目:', item)
|
||||
return {
|
||||
planId: item.planId || item.id,
|
||||
planName: item.planName || item.name,
|
||||
trainingType: item.trainingType || item.type,
|
||||
trainingLevel: item.trainingLevel || item.level,
|
||||
trainingContent: item.trainingContent || item.content,
|
||||
trainer: item.trainer,
|
||||
trainingLocation: item.trainingLocation || item.location,
|
||||
startTime: item.startTime ? new Date(item.startTime).toLocaleString() : '',
|
||||
endTime: item.endTime ? new Date(item.endTime).toLocaleString() : '',
|
||||
status: item.status,
|
||||
maxParticipants: item.maxParticipants || item.maxParticipantsNum,
|
||||
currentParticipants: item.currentParticipants || item.currentParticipantsNum || 0,
|
||||
requirements: item.requirements,
|
||||
remark: item.remark,
|
||||
createTime: item.createTime ? new Date(item.createTime).toLocaleString() : '',
|
||||
createBy: item.createBy || item.createByUser,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
pageSize: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
...searchForm, // 添加搜索条件
|
||||
}
|
||||
const res = await pageTrainingPlan(params)
|
||||
|
||||
console.log('API响应:', res)
|
||||
|
||||
// 兼容不同的响应格式
|
||||
if (res.success || res.status === 200 || res.code === 200) {
|
||||
// 处理数据,确保数据格式正确
|
||||
let dataList: any[] = []
|
||||
|
||||
// 检查不同的数据字段
|
||||
if (Array.isArray(res.data)) {
|
||||
dataList = res.data
|
||||
} else if (res.data && Array.isArray((res as any).rows)) {
|
||||
dataList = (res as any).rows
|
||||
} else if (res.data && Array.isArray((res.data as any).records)) {
|
||||
dataList = (res.data as any).records
|
||||
} else if (res.data && Array.isArray((res.data as any).list)) {
|
||||
dataList = (res.data as any).list
|
||||
}
|
||||
|
||||
console.log('处理后的数据列表:', dataList)
|
||||
|
||||
if (dataList.length > 0) {
|
||||
const transformedData = transformBackendData(dataList)
|
||||
console.log('转换后的数据:', transformedData)
|
||||
tableData.value = transformedData
|
||||
} else {
|
||||
tableData.value = []
|
||||
}
|
||||
|
||||
// 设置总数
|
||||
pagination.total = (res as any).total || (res.data as any)?.total || dataList.length || 0
|
||||
console.log('总数:', pagination.total)
|
||||
} else {
|
||||
message.error(res.msg || '加载数据失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error)
|
||||
message.error('加载数据失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, {
|
||||
planName: '',
|
||||
trainingType: '',
|
||||
trainingLevel: '',
|
||||
status: '',
|
||||
})
|
||||
pagination.current = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
// 表格变化
|
||||
const handleTableChange = (pag: any) => {
|
||||
pagination.current = pag.current || 1
|
||||
pagination.pageSize = pag.pageSize || 10
|
||||
loadData()
|
||||
}
|
||||
|
||||
// 新增
|
||||
const handleAdd = () => {
|
||||
currentPlan.value = null
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
// 查看
|
||||
const handleView = (record: TrainingPlanResp) => {
|
||||
currentPlan.value = record
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑
|
||||
const handleEdit = (record: TrainingPlanResp) => {
|
||||
currentPlan.value = record
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
// 发布
|
||||
const handlePublish = async (record: TrainingPlanResp) => {
|
||||
try {
|
||||
await publishTrainingPlan(record.planId)
|
||||
message.success('发布成功')
|
||||
loadData()
|
||||
} catch (error) {
|
||||
console.error('发布失败:', error)
|
||||
message.error('发布失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 取消
|
||||
const handleCancel = async (record: TrainingPlanResp) => {
|
||||
Modal.confirm({
|
||||
title: '确认取消',
|
||||
content: `确定要取消培训计划"${record.planName}"吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
await cancelTrainingPlan(record.planId)
|
||||
message.success('取消成功')
|
||||
loadData()
|
||||
} catch (error) {
|
||||
console.error('取消失败:', error)
|
||||
message.error('取消失败')
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDelete = async (record: TrainingPlanResp) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: `确定要删除培训计划"${record.planName}"吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
await deleteTrainingPlan(record.planId)
|
||||
message.success('删除成功')
|
||||
loadData()
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error)
|
||||
message.error('删除失败')
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 详情
|
||||
const handleDetail = (record: TrainingPlanResp) => {
|
||||
// 跳转到培训详情页面,并传递 planId
|
||||
router.push({ name: 'TrainingDetail', params: { id: record.planId } })
|
||||
}
|
||||
|
||||
// 弹窗成功回调
|
||||
const handleModalSuccess = () => {
|
||||
modalVisible.value = false
|
||||
loadData()
|
||||
}
|
||||
|
||||
// 监听表格数据变化
|
||||
watch(tableData, (_newData) => {
|
||||
// 数据变化监听
|
||||
}, { deep: true })
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.training-plan-container {
|
||||
padding: 16px;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
.card-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -104,11 +104,11 @@ import { useWindowSize } from '@vueuse/core'
|
|||
import {
|
||||
type MessageQuery,
|
||||
type MessageResp,
|
||||
deleteMessage,
|
||||
getUserMessage,
|
||||
listMessage,
|
||||
readAllMessage,
|
||||
deleteMessage,
|
||||
readMessage,
|
||||
readAllMessage,
|
||||
getUserMessage,
|
||||
} from '@/apis'
|
||||
import { useTable } from '@/hooks'
|
||||
import { useDict } from '@/hooks/app'
|
||||
|
|
Loading…
Reference in New Issue