台账init

master
LiuJiaNan 2025-06-09 11:33:46 +08:00
parent 9d02295729
commit 087daf26a9
24 changed files with 2207 additions and 11 deletions

View File

@ -1,2 +1,2 @@
VITE_BASE=/
VITE_BASE_URL=
VITE_BASE_URL=http://192.168.0.14:8060/qa_kangzai/

494
package-lock.json generated
View File

@ -8,12 +8,16 @@
"name": "qa-kangzai-vue",
"version": "0.0.0",
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@icon-park/vue-next": "^1.4.2",
"@vueuse/core": "^13.3.0",
"animate.css": "^4.1.1",
"autofit.js": "^3.2.8",
"axios": "^1.9.0",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13",
"echarts": "^5.6.0",
"element-plus": "^2.10.1",
"lodash-es": "^4.17.21",
"mitt": "^3.0.1",
"normalize.css": "^8.0.1",
@ -22,7 +26,8 @@
"throttle-debounce": "^5.0.2",
"v-viewer": "^3.0.21",
"vue": "^3.5.13",
"vue-router": "^4.5.1"
"vue-router": "^4.5.1",
"vue3-puzzle-vcode": "^1.1.7"
},
"devDependencies": {
"@types/node": "^18.19.68",
@ -40,6 +45,8 @@
"eslint-plugin-vue": "^9.32.0",
"prettier": "^2.8.8",
"sass": "^1.83.0",
"unplugin-auto-import": "^19.3.0",
"unplugin-vue-components": "^28.7.0",
"vite": "^6.3.5",
"vite-plugin-env-parse": "^1.0.15",
"vite-plugin-eslint": "^1.8.1",
@ -93,6 +100,24 @@
"node": ">=6.9.0"
}
},
"node_modules/@ctrl/tinycolor": {
"version": "3.6.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
"integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/@element-plus/icons-vue": {
"version": "2.3.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
"integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==",
"license": "MIT",
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.5",
"resolved": "https://repo.huaweicloud.com/repository/npm/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
@ -591,6 +616,31 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@floating-ui/core": {
"version": "1.7.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/@floating-ui/core/-/core-1.7.1.tgz",
"integrity": "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.9"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.7.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/@floating-ui/dom/-/dom-1.7.1.tgz",
"integrity": "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.7.1",
"@floating-ui/utils": "^0.2.9"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.9",
"resolved": "https://repo.huaweicloud.com/repository/npm/@floating-ui/utils/-/utils-0.2.9.tgz",
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
"license": "MIT"
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.13.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
@ -629,6 +679,19 @@
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@icon-park/vue-next": {
"version": "1.4.2",
"resolved": "https://repo.huaweicloud.com/repository/npm/@icon-park/vue-next/-/vue-next-1.4.2.tgz",
"integrity": "sha512-+QklF255wkfBOabY+xw6FAI0Bwln/RhdwCunNy/9sKdKuChtaU67QZqU67KGAvZUTeeBgsL+yaHHxqfQeGZXEQ==",
"license": "Apache-2.0",
"engines": {
"node": ">= 8.0.0",
"npm": ">= 5.0.0"
},
"peerDependencies": {
"vue": "3.x"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
@ -1016,6 +1079,17 @@
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@popperjs/core": {
"name": "@sxzz/popperjs-es",
"version": "2.11.7",
"resolved": "https://repo.huaweicloud.com/repository/npm/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
"integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@rollup/pluginutils": {
"version": "4.2.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
@ -1361,6 +1435,21 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/lodash": {
"version": "4.17.17",
"resolved": "https://repo.huaweicloud.com/repository/npm/@types/lodash/-/lodash-4.17.17.tgz",
"integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==",
"license": "MIT"
},
"node_modules/@types/lodash-es": {
"version": "4.17.12",
"resolved": "https://repo.huaweicloud.com/repository/npm/@types/lodash-es/-/lodash-es-4.17.12.tgz",
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
"license": "MIT",
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/node": {
"version": "18.19.111",
"resolved": "https://repo.huaweicloud.com/repository/npm/@types/node/-/node-18.19.111.tgz",
@ -1662,6 +1751,33 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://repo.huaweicloud.com/repository/npm/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"license": "ISC",
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/anymatch/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/argparse/-/argparse-2.0.1.tgz",
@ -1801,6 +1917,12 @@
"node": ">= 0.4"
}
},
"node_modules/async-validator": {
"version": "4.2.5",
"resolved": "https://repo.huaweicloud.com/repository/npm/async-validator/-/async-validator-4.2.5.tgz",
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
"license": "MIT"
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/asynckit/-/asynckit-0.4.0.tgz",
@ -1885,6 +2007,19 @@
"dev": true,
"license": "MIT"
},
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/birpc": {
"version": "2.3.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/birpc/-/birpc-2.3.0.tgz",
@ -1918,7 +2053,6 @@
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"fill-range": "^7.1.1"
},
@ -2202,6 +2336,12 @@
"node": ">= 8"
}
},
"node_modules/crypto-js": {
"version": "4.2.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/crypto-js/-/crypto-js-4.2.0.tgz",
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
"license": "MIT"
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/cssesc/-/cssesc-3.0.0.tgz",
@ -2439,6 +2579,126 @@
"dev": true,
"license": "ISC"
},
"node_modules/element-plus": {
"version": "2.10.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/element-plus/-/element-plus-2.10.1.tgz",
"integrity": "sha512-R+YM8b+s+3aQ3EeY33q0inn3ehRnunP42aDYoJxUtSZPgMPSXzYgmGEhIDP7Xg4NvY8raaSuO0/1fDLEfZ+nlA==",
"license": "MIT",
"dependencies": {
"@ctrl/tinycolor": "^3.4.1",
"@element-plus/icons-vue": "^2.3.1",
"@floating-ui/dom": "^1.0.1",
"@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
"@types/lodash": "^4.14.182",
"@types/lodash-es": "^4.17.6",
"@vueuse/core": "^9.1.0",
"async-validator": "^4.2.5",
"dayjs": "^1.11.13",
"escape-html": "^1.0.3",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"lodash-unified": "^1.0.2",
"memoize-one": "^6.0.0",
"normalize-wheel-es": "^1.2.0"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/element-plus/node_modules/@types/web-bluetooth": {
"version": "0.0.16",
"resolved": "https://repo.huaweicloud.com/repository/npm/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
"integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==",
"license": "MIT"
},
"node_modules/element-plus/node_modules/@vueuse/core": {
"version": "9.13.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/@vueuse/core/-/core-9.13.0.tgz",
"integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==",
"license": "MIT",
"dependencies": {
"@types/web-bluetooth": "^0.0.16",
"@vueuse/metadata": "9.13.0",
"@vueuse/shared": "9.13.0",
"vue-demi": "*"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/element-plus/node_modules/@vueuse/core/node_modules/vue-demi": {
"version": "0.14.10",
"resolved": "https://repo.huaweicloud.com/repository/npm/vue-demi/-/vue-demi-0.14.10.tgz",
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/element-plus/node_modules/@vueuse/metadata": {
"version": "9.13.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/@vueuse/metadata/-/metadata-9.13.0.tgz",
"integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/element-plus/node_modules/@vueuse/shared": {
"version": "9.13.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/@vueuse/shared/-/shared-9.13.0.tgz",
"integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==",
"license": "MIT",
"dependencies": {
"vue-demi": "*"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/element-plus/node_modules/@vueuse/shared/node_modules/vue-demi": {
"version": "0.14.10",
"resolved": "https://repo.huaweicloud.com/repository/npm/vue-demi/-/vue-demi-0.14.10.tgz",
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/entities/-/entities-4.5.0.tgz",
@ -2653,6 +2913,12 @@
"node": ">=6"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://repo.huaweicloud.com/repository/npm/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/escape-string-regexp": {
"version": "5.0.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
@ -3254,7 +3520,6 @@
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
@ -3816,6 +4081,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"license": "MIT",
"dependencies": {
"binary-extensions": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/is-boolean-object": {
"version": "1.2.2",
"resolved": "https://repo.huaweicloud.com/repository/npm/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
@ -3987,7 +4265,6 @@
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.12.0"
}
@ -4328,7 +4605,6 @@
"version": "4.17.21",
"resolved": "https://repo.huaweicloud.com/repository/npm/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true,
"license": "MIT"
},
"node_modules/lodash-es": {
@ -4337,6 +4613,17 @@
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"node_modules/lodash-unified": {
"version": "1.0.3",
"resolved": "https://repo.huaweicloud.com/repository/npm/lodash-unified/-/lodash-unified-1.0.3.tgz",
"integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
"license": "MIT",
"peerDependencies": {
"@types/lodash-es": "*",
"lodash": "*",
"lodash-es": "*"
}
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://repo.huaweicloud.com/repository/npm/lodash.merge/-/lodash.merge-4.6.2.tgz",
@ -4362,6 +4649,12 @@
"node": ">= 0.4"
}
},
"node_modules/memoize-one": {
"version": "6.0.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
"license": "MIT"
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://repo.huaweicloud.com/repository/npm/micromatch/-/micromatch-4.0.8.tgz",
@ -4523,6 +4816,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/normalize-range": {
"version": "0.1.2",
"resolved": "https://repo.huaweicloud.com/repository/npm/normalize-range/-/normalize-range-0.1.2.tgz",
@ -4533,6 +4836,12 @@
"node": ">=0.10.0"
}
},
"node_modules/normalize-wheel-es": {
"version": "1.2.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
"license": "BSD-3-Clause"
},
"node_modules/normalize.css": {
"version": "8.0.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/normalize.css/-/normalize.css-8.0.1.tgz",
@ -5717,7 +6026,6 @@
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"is-number": "^7.0.0"
},
@ -5949,6 +6257,75 @@
"node": ">=18.12.0"
}
},
"node_modules/unplugin-auto-import": {
"version": "19.3.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/unplugin-auto-import/-/unplugin-auto-import-19.3.0.tgz",
"integrity": "sha512-iIi0u4Gq2uGkAOGqlPJOAMI8vocvjh1clGTfSK4SOrJKrt+tirrixo/FjgBwXQNNdS7ofcr7OxzmOb/RjWxeEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"local-pkg": "^1.1.1",
"magic-string": "^0.30.17",
"picomatch": "^4.0.2",
"unimport": "^4.2.0",
"unplugin": "^2.3.4",
"unplugin-utils": "^0.2.4"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@nuxt/kit": "^3.2.2",
"@vueuse/core": "*"
},
"peerDependenciesMeta": {
"@nuxt/kit": {
"optional": true
},
"@vueuse/core": {
"optional": true
}
}
},
"node_modules/unplugin-auto-import/node_modules/estree-walker": {
"version": "3.0.3",
"resolved": "https://repo.huaweicloud.com/repository/npm/estree-walker/-/estree-walker-3.0.3.tgz",
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0"
}
},
"node_modules/unplugin-auto-import/node_modules/unimport": {
"version": "4.2.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/unimport/-/unimport-4.2.0.tgz",
"integrity": "sha512-mYVtA0nmzrysnYnyb3ALMbByJ+Maosee2+WyE0puXl+Xm2bUwPorPaaeZt0ETfuroPOtG8jj1g/qeFZ6buFnag==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.14.1",
"escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3",
"local-pkg": "^1.1.1",
"magic-string": "^0.30.17",
"mlly": "^1.7.4",
"pathe": "^2.0.3",
"picomatch": "^4.0.2",
"pkg-types": "^2.1.0",
"scule": "^1.3.0",
"strip-literal": "^3.0.0",
"tinyglobby": "^0.2.12",
"unplugin": "^2.2.2",
"unplugin-utils": "^0.2.4"
},
"engines": {
"node": ">=18.12.0"
}
},
"node_modules/unplugin-utils": {
"version": "0.2.4",
"resolved": "https://repo.huaweicloud.com/repository/npm/unplugin-utils/-/unplugin-utils-0.2.4.tgz",
@ -5965,6 +6342,106 @@
"url": "https://github.com/sponsors/sxzz"
}
},
"node_modules/unplugin-vue-components": {
"version": "28.7.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/unplugin-vue-components/-/unplugin-vue-components-28.7.0.tgz",
"integrity": "sha512-3SuWAHlTjOiZckqRBGXRdN/k6IMmKyt2Ch5/+DKwYaT321H0ItdZDvW4r8/YkEKQpN9TN3F/SZ0W342gQROC3Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^3.6.0",
"debug": "^4.4.1",
"local-pkg": "^1.1.1",
"magic-string": "^0.30.17",
"mlly": "^1.7.4",
"tinyglobby": "^0.2.14",
"unplugin": "^2.3.4",
"unplugin-utils": "^0.2.4"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@babel/parser": "^7.15.8",
"@nuxt/kit": "^3.2.2",
"vue": "2 || 3"
},
"peerDependenciesMeta": {
"@babel/parser": {
"optional": true
},
"@nuxt/kit": {
"optional": true
}
}
},
"node_modules/unplugin-vue-components/node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/unplugin-vue-components/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://repo.huaweicloud.com/repository/npm/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/unplugin-vue-components/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/unplugin-vue-components/node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/untyped": {
"version": "2.0.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/untyped/-/untyped-2.0.0.tgz",
@ -6240,6 +6717,11 @@
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT"
},
"node_modules/vue3-puzzle-vcode": {
"version": "1.1.7",
"resolved": "https://repo.huaweicloud.com/repository/npm/vue3-puzzle-vcode/-/vue3-puzzle-vcode-1.1.7.tgz",
"integrity": "sha512-mW780dz7HKjrElnE60CeYSeHGidKBKHoMjTDYfqF21330rTkFOsfDK1FQKZ22MktgMtTEoS/imfpEDlM1cxY/g=="
},
"node_modules/webpack-virtual-modules": {
"version": "0.6.2",
"resolved": "https://repo.huaweicloud.com/repository/npm/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",

View File

@ -9,12 +9,16 @@
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@icon-park/vue-next": "^1.4.2",
"@vueuse/core": "^13.3.0",
"animate.css": "^4.1.1",
"autofit.js": "^3.2.8",
"axios": "^1.9.0",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13",
"echarts": "^5.6.0",
"element-plus": "^2.10.1",
"lodash-es": "^4.17.21",
"mitt": "^3.0.1",
"normalize.css": "^8.0.1",
@ -23,7 +27,8 @@
"throttle-debounce": "^5.0.2",
"v-viewer": "^3.0.21",
"vue": "^3.5.13",
"vue-router": "^4.5.1"
"vue-router": "^4.5.1",
"vue3-puzzle-vcode": "^1.1.7"
},
"devDependencies": {
"@types/node": "^18.19.68",
@ -41,6 +46,8 @@
"eslint-plugin-vue": "^9.32.0",
"prettier": "^2.8.8",
"sass": "^1.83.0",
"unplugin-auto-import": "^19.3.0",
"unplugin-vue-components": "^28.7.0",
"vite": "^6.3.5",
"vite-plugin-env-parse": "^1.0.15",
"vite-plugin-eslint": "^1.8.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,25 @@
import CryptoJS from "crypto-js";
const key = CryptoJS.enc.Utf8.parse("daac3ae52eff4cec"); // 16位
const encrypt = (word) => {
let encrypted = "";
if (typeof word === "string") {
const src = CryptoJS.enc.Utf8.parse(word);
encrypted = CryptoJS.AES.encrypt(src, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
} else if (typeof word === "object") {
// 对象格式的转成json字符串
const data = JSON.stringify(word);
const src = CryptoJS.enc.Utf8.parse(data);
encrypted = CryptoJS.AES.encrypt(src, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
}
return encrypted.ciphertext.toString(CryptoJS.enc.Base64);
};
export { encrypt };

View File

@ -0,0 +1,17 @@
import dayjs from "dayjs";
import { setRefreshToken } from "@/request/api.js";
import { useUserStore } from "@/pinia/user.js";
import pinia from "@/pinia/index.js";
import router from "@/router/index.js";
export default async function () {
if (router.currentRoute.value.meta.isLogin) {
const userStore = useUserStore(pinia);
if (userStore.getTokenTime) {
if (dayjs().diff(dayjs(userStore.getTokenTime), "minute") >= 5) {
await userStore.setTokenTime(dayjs().format("YYYY-MM-DD HH:mm:ss"));
await setRefreshToken();
}
}
}
}

480
src/assets/js/utils.js Normal file
View File

@ -0,0 +1,480 @@
import { ElMessage } from "element-plus";
import dayjs from "dayjs";
/**
* @description 计算序号
* @param {Object} pagination 分页数据对象
* @param {number | string} pagination.currentPage 当前页
* @param {number | string} pagination.pageSize 每页条数
* @param {number} index 当页数据的索引值
* @return {number} 序号
**/
export function serialNumber(pagination, index) {
return (pagination.currentPage - 1) * pagination.pageSize + (index + 1);
}
/**
* @description 字符串数组转数组
* @param {string} value 转换的字符串数组
* @return {Array} 转换后的数组
**/
export function toArrayString(value) {
// eslint-disable-next-line no-eval
return value ? eval(value).map(String) : [];
}
/**
* @description 判断文件后缀名是否符合
* @param {string} name 文件名字
* @param {string} suffix 文件后缀
* @return {boolean} 是否符合
**/
export function interceptTheSuffix(name, suffix) {
return (
name.substring(name.lastIndexOf("."), name.length).toLowerCase() ===
suffix.toLowerCase()
);
}
/**
* @description 图片转base64
* @param {string} imgUrl 图片地址
* @return {Promise} Promise实例then包含base64编码
**/
export function image2Base64(imgUrl) {
return new Promise((resolve) => {
const img = new Image();
img.src = imgUrl;
img.crossOrigin = "Anonymous";
img.onload = function () {
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, img.width, img.height);
const ext = img.src.substring(img.src.lastIndexOf(".") + 1).toLowerCase();
resolve(canvas.toDataURL("image/" + ext));
};
});
}
/**
* @description 判断图片是否可访问成功
* @param {string} imgUrl 图片地址
* @return {Promise} Promise实例
**/
export function checkImgExists(imgUrl) {
return new Promise((resolve, reject) => {
const ImgObj = new Image();
ImgObj.src = imgUrl;
ImgObj.onload = function (res) {
resolve(res);
};
ImgObj.onerror = function (err) {
reject(err);
};
});
}
/**
* @description 获取数据类型
* @param {any} data 数据
* @return {string} 数据类型
**/
export function getDataType(data) {
return Object.prototype.toString.call(data).slice(8, -1);
}
/**
* @description 数组去重
* @param {Array<number,string>} arr 去重的数组
* @return {Array} 去重后的数组
**/
export function ArrayDeduplication(arr) {
return [...new Set(arr)];
}
/**
* @description 数组对象去重
* @param {Array} arr 去重的数组
* @param {string} name 去重的key
* @return {Array} 去重后的数组
**/
export function arrayObjectDeduplication(arr, name) {
const obj = {};
arr = arr.reduce(function (previousValue, currentValue) {
if (!obj[currentValue[name]]) {
obj[currentValue[name]] = true;
previousValue.push(currentValue);
}
return previousValue;
}, []);
return arr;
}
/**
* @description 查找字符串中指定的值第几次出现的位置
* @param {Array} str 查找的字符串数组
* @param {string} char 查找的值
* @param {number} num 第几次出现
* @return {number} 出现的位置
**/
export function findCharIndex(str, char, num) {
let index = str.indexOf(char);
if (index === -1) return -1;
for (let i = 0; i < num - 1; i++) {
index = str.indexOf(char, index + 1);
if (index === -1) return -1;
}
return index;
}
/**
* @description 生成指定两个值之间的随机数
* @param {number} min 最小值
* @param {number} max 最大值
* @return {number} 随机数
**/
export function randoms(min, max) {
return Math.random() * (max - min + 1) + min;
}
/**
* @description 千位分隔符
* @param {number | string} num 转换的值
* @return {string} 转换后的值
**/
export function numFormat(num) {
if (num) {
const numArr = num.toString().split(".");
const arr = numArr[0].split("").reverse();
let res = [];
for (let i = 0; i < arr.length; i++) {
if (i % 3 === 0 && i !== 0) {
res.push(",");
}
res.push(arr[i]);
}
res.reverse();
if (numArr[1]) {
res = res.join("").concat("." + numArr[1]);
} else {
res = res.join("");
}
return res;
}
}
/**
* @description 验证是否为空
* @param {any} value 验证的值
* @return {boolean} 是否为空
**/
export function isEmpty(value) {
return (
value === undefined ||
value === null ||
(typeof value === "object" && Object.keys(value).length === 0) ||
(typeof value === "string" && value.trim().length === 0)
);
}
/**
* @description 获取url参数
* @param {string} name 获取的key
* @return {string} 获取的值
**/
export function getUrlParam(name) {
const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
const r = window.location.search.substr(1).match(reg);
if (r != null) return decodeURI(r[2]);
return "";
}
/**
* @description 数据分页
* @param {Array} list 分页的数组
* @param {number | string} currentPage 当前页
* @param {number | string} pageSize 每页条数
* @return {Array} 分页后的数组
**/
export function paging(list, currentPage, pageSize) {
return list.filter((item, index) => {
return (
index < +currentPage * +pageSize &&
index >= (+currentPage - 1) * +pageSize
);
});
}
/**
* @description 获取文件后缀
* @param {string} name 文件名
* @return {string} 文件后缀
**/
export function getFileSuffix(name) {
return name.substring(name.lastIndexOf(".") + 1);
}
/**
* @description 获取文件名称
* @param {string} name 文件地址
* @return {string} 文件名称
**/
export function getFileName(name) {
if (!name) return "";
return name.substring(name.lastIndexOf("/") + 1);
}
/**
* @description 读取txt文档
* @param {string} filePah 文档路径
* @return {resolve,string} 读取后的内容
**/
export function readTxtDocument(filePah) {
return new Promise((resolve) => {
const FILE_URL = import.meta.env.VITE_FILE_URL;
const file_url = FILE_URL + filePah;
const xhr = new XMLHttpRequest();
xhr.open("get", file_url, true);
xhr.responseType = "blob";
xhr.onload = function (event) {
const reader = new FileReader();
reader.readAsText(event.target.response, "GB2312");
reader.onload = function () {
resolve(reader.result);
};
};
xhr.send();
});
}
/**
* @description 将秒转换成时分秒
* @param {string,number} second 需要转换的秒数
* @return {string} 转换后的时间
**/
export function secondConversion(second) {
if (!second) return 0;
const h = parseInt(second / 60 / 60, 10);
const m = parseInt((second / 60) % 60, 10);
const s = parseInt(second % 60, 10);
if (h) {
return h + "小时" + m + "分钟" + s + "秒";
} else {
if (m) {
return m + "分钟" + s + "秒";
} else {
return s + "秒";
}
}
}
/**
* @description 附件添加前缀
* @param {Array} list 附件数组
* @return {Array} 添加完的数组
**/
export function addingPrefixToFile(list) {
if (!list) return [];
const FILE_URL = import.meta.env.VITE_FILE_URL;
for (let i = 0; i < list.length; i++) {
list[i].url = FILE_URL + list[i].FILEPATH;
if (list[i].FILENAME) {
list[i].name = list[i].FILENAME;
} else {
list[i].name = getFileName(list[i].FILEPATH);
}
}
return list;
}
/**
* @description 验证重复选择
* @param {Array} list 验证的数组
* @param {number} index 选择的索引
* @param {string} key 验证的字段
* @param {string} id 验证的值
**/
export async function verifyDuplicateSelection(list, index, key, id) {
return new Promise((resolve, reject) => {
if (list.some((item) => item[key] === id)) {
ElMessage.warning("不能重复选择");
reject(new Error("不能重复选择"));
} else {
list[index][key] = id;
resolve();
}
});
}
/**
* @description 翻译状态
* @param {number | string} status 状态
* @param {Array} list 翻译的数组
* @param {String} idKey
* @param {String} nameKey
* @return {string} 翻译后的状态
**/
export function translationStatus(
status,
list,
idKey = "id",
nameKey = "name"
) {
for (let i = 0; i < list.length; i++) {
if (status === list[i][idKey]) {
return list[i][nameKey];
}
}
}
/**
* @description 计算文件大小
* @param {number | string} size 文件kb
* @return {string} 计算后的文件大小
**/
export function calculateFileSize(size) {
return size > 1024
? (size / 1024 + "").substring(0, (size / 1024 + "").lastIndexOf(".") + 3) +
"MB"
: size + "KB";
}
/**
* @description 根据身份证号获取出生日期和性别
* @param {String} idCard 身份证号
* @return {Object} 出生日期和性别 date sex
**/
export function idCardGetDateAndGender(idCard) {
const reg =
/^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
let sex = "";
let date = "";
if (reg.test(idCard)) {
const org_birthday = idCard.substring(6, 14);
const org_gender = idCard.substring(16, 17);
const birthday =
org_birthday.substring(0, 4) +
"-" +
org_birthday.substring(4, 6) +
"-" +
org_birthday.substring(6, 8);
const birthdays = new Date(birthday.replace(/-/g, "/"));
const Month = birthdays.getMonth() + 1;
let MonthDate;
const DayDate = birthdays.getDate();
let Day;
if (Month < 10) MonthDate = "0" + Month;
else MonthDate = Month;
if (DayDate < 10) Day = "0" + DayDate;
else Day = DayDate;
sex = org_gender % 2 === 1 ? "1" : "0";
date = birthdays.getFullYear() + "-" + MonthDate + "-" + Day;
}
return { sex, date };
}
/**
* @description 获取select的label
* @param {Array} list 获取的数组
* @param {number | string} value 获取的值
* @param {string?} idKey 获取的id
* @param {string?} labelKey 获取的label
* @return {string} 获取的label
**/
export function getSelectLabel(
list,
value,
idKey = "bianma",
labelKey = "name"
) {
const result = list.find((item) => item[idKey] === value);
return result ? result[labelKey] : "";
}
/**
* @description 获取select中指定项组成的数组
* @param {Array} list 获取的数组
* @param {Array} value 获取的值
* @param {string?} idKey 获取的id
* @return {Array} list中指定项组成的数组
**/
export function getSelectAppointItemList(list, value, idKey = "id") {
return list.filter((item) => value.includes(item[idKey]));
}
/**
* @description dayjs改变日期格式
* @param {date} date 传入的日期
* @param {string} column 需要变成的日期格式
* @return {string} 改变的日期格式
**/
export function formatDate(date, column) {
if (date) {
return dayjs(date).format(column);
} else {
return "";
}
}
/**
* @description json转换为树形结构
* @param {Array} json 需要转换的json
* @param {string} idStr id字段
* @param {string} pidStr 父级id字段
* @param {string} chindrenStr 子级字段
* @return {string} 转换完的树形结构
**/
export function listTransTree(json, idStr, pidStr, chindrenStr) {
const r = [];
const hash = {};
const id = idStr;
const pid = pidStr;
const children = chindrenStr;
let i = 0;
let j = 0;
const len = json.length;
for (; i < len; i++) {
hash[json[i][id]] = json[i];
}
for (; j < len; j++) {
const aVal = json[j];
const hashVP = hash[aVal[pid]];
if (hashVP) {
!hashVP[children] && (hashVP[children] = []);
hashVP[children].push(aVal);
} else {
r.push(aVal);
}
}
return r;
}
/**
* @description 附件添加前缀
* @param {Array} list 附件数组
* @return {Array} 添加完的数组
**/
export function fileFeedback(list) {
if (!list) return [];
const FILE_URL = import.meta.env.VITE_FILE_URL;
for (let i = 0; i < list.length; i++) {
list[i].url = FILE_URL + list[i].filepath;
list[i].name = list[i].filepath || getFileName(list[i].filepath);
list[i].imgfilesId = getFileSuffix(list[i].imgfilesId);
}
return list;
}
/**
* @description 获取文件路径前缀地址
* @return {string} 文件路径前缀地址
**/
export function getFileUrl() {
return import.meta.env.VITE_FILE_URL;
}
export function getBaseUrl() {
return import.meta.env[import.meta.env.DEV ? "VITE_PROXY" : "VITE_BASE_URL"];
}

View File

@ -0,0 +1,209 @@
<template>
<div class="mi-captcha">
<div class="mi-captcha-content">
<!-- 没有进行验证-->
<div
v-if="!verificationPass"
class="mi-captcha-radar"
@click="verificationShow = true"
>
<div class="mi-captcha-radar-ready">
<div class="mi-captcha-radar-ring" />
<div class="mi-captcha-radar-dot" />
</div>
<div class="mi-captcha-radar-tip">点击按钮进行验证</div>
</div>
<!-- 验证通过-->
<div
v-if="verificationPass"
class="mi-captcha-radar mi-captcha-radar-pass"
>
<div class="mi-captcha-radar-success mi-captcha-radar-success-icon">
<span role="img" aria-label="verified">
<svg
focusable="false"
class=""
data-icon="verified"
width="1em"
height="1em"
fill="currentColor"
aria-hidden="true"
viewBox="64 64 896 896"
>
<path
d="M447.8 588.8l-7.3-32.5c-.2-1-.6-1.9-1.1-2.7a7.94 7.94 0 00-11.1-2.2L405 567V411c0-4.4-3.6-8-8-8h-81c-4.4 0-8 3.6-8 8v36c0 4.4 3.6 8 8 8h37v192.4a8 8 0 0012.7 6.5l79-56.8c2.6-1.9 3.8-5.1 3.1-8.3zm-56.7-216.6l.2.2c3.2 3 8.3 2.8 11.3-.5l24.1-26.2a8.1 8.1 0 00-.3-11.2l-53.7-52.1a8 8 0 00-11.2.1l-24.7 24.7c-3.1 3.1-3.1 8.2.1 11.3l54.2 53.7z"
/>
<path
d="M866.9 169.9L527.1 54.1C523 52.7 517.5 52 512 52s-11 .7-15.1 2.1L157.1 169.9c-8.3 2.8-15.1 12.4-15.1 21.2v482.4c0 8.8 5.7 20.4 12.6 25.9L499.3 968c3.5 2.7 8 4.1 12.6 4.1s9.2-1.4 12.6-4.1l344.7-268.6c6.9-5.4 12.6-17 12.6-25.9V191.1c.2-8.8-6.6-18.3-14.9-21.2zM810 654.3L512 886.5 214 654.3V226.7l298-101.6 298 101.6v427.6z"
/>
<path
d="M452 297v36c0 4.4 3.6 8 8 8h108v274h-38V405c0-4.4-3.6-8-8-8h-35c-4.4 0-8 3.6-8 8v210h-31c-4.4 0-8 3.6-8 8v37c0 4.4 3.6 8 8 8h244c4.4 0 8-3.6 8-8v-37c0-4.4-3.6-8-8-8h-72V493h58c4.4 0 8-3.6 8-8v-35c0-4.4-3.6-8-8-8h-58V341h63c4.4 0 8-3.6 8-8v-36c0-4.4-3.6-8-8-8H460c-4.4 0-8 3.6-8 8z"
/>
</svg>
</span>
</div>
<div class="mi-captcha-radar-tip">通过验证</div>
</div>
</div>
</div>
<verification
:show="verificationShow"
@success="verificationSuccess"
@close="verificationClose"
/>
</template>
<script setup>
import Verification from "vue3-puzzle-vcode";
import { ref } from "vue";
defineOptions({
name: "AppVerification",
});
const verificationPass = defineModel("verificationPass", {
type: Boolean,
default: false,
});
const verificationShow = ref(false);
const verificationClose = () => {
verificationShow.value = false;
};
const verificationSuccess = () => {
verificationPass.value = true;
verificationClose();
};
</script>
<style scoped lang="scss">
.mi-captcha {
width: 100%;
flex: 1;
height: 2.625rem;
font-family: "Pingfang SC", "Microsoft YaHei", "Monospaced Number",
"Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"PingFang SC", "Hiragino Sans GB", "Helvetica Neue", Helvetica, Arial,
sans-serif;
}
.mi-captcha-content {
width: 100%;
height: 100%;
position: relative;
}
.mi-captcha-radar {
display: flex;
align-items: center;
justify-content: flex-start;
flex-direction: row;
height: 100%;
width: 100%;
background-color: #1d1e23;
background-image: linear-gradient(315deg, #a8b4d3 0%, #adc0ed 74%);
border: 1px solid #96a4c8;
cursor: pointer;
min-width: 10rem;
position: relative;
border-radius: 4px;
}
.mi-captcha-radar-ready,
.mi-captcha-radar-success {
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
flex-wrap: nowrap;
position: relative;
transition: all 0.4s ease;
width: 1.875rem;
height: 1.875rem;
margin: 0.375rem;
}
.mi-captcha-radar-ring,
.mi-captcha-radar-dot {
position: absolute;
border-radius: 50%;
transform: scale(0.4);
width: 100%;
height: 100%;
box-shadow: inset 0 0 0 1px #2c67ec;
background-image: linear-gradient(0, rgba(0, 0, 0, 0) 50%, #fff 50%),
linear-gradient(0, #fff 50%, rgba(0, 0, 0, 0) 50%);
}
.mi-captcha-radar-dot {
background: #2c67ec;
}
.mi-captcha-radar-ring {
animation: mi-anim-wait 1s infinite;
transform: scale(1);
}
.mi-captcha-radar-success {
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
cursor: default;
}
.mi-captcha-radar-success-icon {
color: #f6ca9d;
animation-name: mi-captcha-success;
animation-timing-function: ease;
animation-iteration-count: 1;
animation-delay: 0.5s;
animation-duration: 0.4s;
font-size: 1.25rem;
}
.mi-captcha-radar-tip {
display: flex;
align-items: center;
justify-content: flex-start;
flex-direction: row;
height: 2.625rem;
padding-left: 0.125rem;
font-size: 0.875rem;
color: #fff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: left;
}
.mi-captcha-radar-pass .mi-captcha-radar-tip {
color: #f6ca9d;
}
@keyframes mi-captcha-success {
25% {
transform: rotate(25deg);
}
100% {
transform: rotate(-360deg);
}
}
@keyframes mi-anim-wait {
60% {
transform: scale(0.75);
}
}
:deep {
.vue-auth-box_ {
background: #2e63d8 !important;
border: 1px solid #2752b3 !important;
}
.mi-captcha-radar-pass .mi-captcha-radar-tip {
color: #ffffff !important;
}
.mi-captcha-radar-success-icon {
color: #ffffff !important;
}
}
</style>

View File

@ -0,0 +1,28 @@
import { ElMessage } from "element-plus";
import { useTemplateRef } from "vue";
export default function useFormValidate() {
const formRef = useTemplateRef("formRef");
const validate = (message = "请补全必填项!") => {
return new Promise((resolve, reject) => {
formRef.value.validate((valid) => {
if (valid) {
resolve(valid);
} else {
reject(valid);
ElMessage.warning(message);
setTimeout(() => {
const element = document.querySelectorAll(
".el-form-item__error"
)[0];
element.scrollIntoView({
behavior: "smooth",
block: "center",
});
}, 100);
}
});
});
};
return { validate, formRef };
}

View File

@ -0,0 +1,54 @@
import { useMiscellaneousStore } from "@/pinia/miscellaneous.js";
export const getQueryCriteria = () => {
const miscellaneousStore = useMiscellaneousStore();
const key = window.location.href;
let queryCriteria = miscellaneousStore.getQueryCriteria[key] || {};
if (queryCriteria.tabsActiveName) {
queryCriteria =
miscellaneousStore.getQueryCriteria[
key + "/" + queryCriteria.tabsActiveName
] || {};
}
const pagination = queryCriteria.pagination;
const searchForm = queryCriteria.searchForm;
const tabsActiveName = queryCriteria.tabsActiveName;
return {
pagination,
searchForm,
tabsActiveName,
};
};
export const setQueryCriteria = (data) => {
const miscellaneousStore = useMiscellaneousStore();
let key = window.location.href;
if (data.tabsActiveName) {
miscellaneousStore.setQueryCriteria({
...miscellaneousStore.getQueryCriteria,
[key]: {
...miscellaneousStore.getQueryCriteria[key],
tabsActiveName: data.tabsActiveName,
},
});
key = key + "/" + data.tabsActiveName;
}
miscellaneousStore.setQueryCriteria({
...miscellaneousStore.getQueryCriteria,
[key]: {
...miscellaneousStore.getQueryCriteria[key],
...data,
},
});
};
export const resetQueryCriteria = () => {
const miscellaneousStore = useMiscellaneousStore();
miscellaneousStore.resetQueryCriteria();
};
export const getTabsActiveName = () => {
const key = window.location.href;
const miscellaneousStore = useMiscellaneousStore();
const queryCriteria = miscellaneousStore.getQueryCriteria[key] || {};
return queryCriteria.tabsActiveName;
};

View File

@ -0,0 +1,64 @@
<template>
<div
style="display: flex; justify-content: space-between; align-items: center"
>
<div class="breadcrumb">
<el-breadcrumb class="app-breadcrumb" separator=">">
<transition-group name="breadcrumb">
<el-breadcrumb-item
v-for="(item, index) in breadcrumbList"
:key="item.path"
>
<router-link v-if="index === 0" :to="item.path">
{{ item.meta.title }}
</router-link>
<span v-else-if="index !== breadcrumbList.length - 1">
{{ item.meta.title }}
</span>
<span v-else class="no-redirect">{{ item.meta.title }}</span>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</div>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
import { useRoute } from "vue-router";
defineOptions({
name: "LayoutBreadcrumb",
});
const route = useRoute();
const breadcrumbList = ref([]);
const fnGetBreadcrumb = () => {
const matched = route.matched.filter((item) => item.meta?.title);
if (matched[0].path === "/") matched[0].path = "/index";
breadcrumbList.value = matched.filter(
(item) => item.meta?.title && item.meta?.breadcrumb !== false
);
};
fnGetBreadcrumb();
watch(
() => route,
() => fnGetBreadcrumb(),
{ deep: true }
);
</script>
<style lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
font-size: 14px;
line-height: var(--el-header-height);
.no-redirect {
color: #97a8be;
cursor: text;
}
.el-breadcrumb__inner a {
font-weight: normal;
}
}
</style>

View File

@ -0,0 +1,70 @@
<template>
<el-dialog v-model="visible" title="修改密码" width="600px" @close="fnClose">
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="旧密码" prop="password">
<el-input v-model="form.password" type="password" />
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input v-model="form.newPassword" type="password" />
</el-form-item>
<el-form-item label="确认密码" prop="newPasswordConfirm">
<el-input v-model="form.newPasswordConfirm" type="password" />
</el-form-item>
</el-form>
<template #footer>
<el-button type="primary" @click="fnSubmit"></el-button>
<el-button @click="fnClose"></el-button>
</template>
</el-dialog>
</template>
<script setup>
import { debounce } from "throttle-debounce";
import useFormValidate from "@/hooks/useFormValidate.js";
import { setChangePassword } from "@/request/api.js";
import { ref } from "vue";
const emits = defineEmits(["submit"]);
const visible = defineModel("visible", { type: Boolean, required: true });
const form = ref({
password: "",
newPassword: "",
newPasswordConfirm: "",
});
const { formRef, validate } = useFormValidate();
const validatePass = (_rule, value, callback) => {
if (value === "") {
callback(new Error("请再次输入新密码"));
} else if (value !== form.value.newPassword) {
callback(new Error("两次输入密码不一致!"));
} else {
callback();
}
};
const rules = {
password: [{ required: true, message: "请输入旧密码", trigger: "blur" }],
newPassword: [
{ required: true, message: "请输入新密码", trigger: "blur" },
{ min: 6, max: 18, message: "密码长度为6-18位", trigger: "blur" },
],
newPasswordConfirm: [
{ required: true, validator: validatePass, trigger: "blur" },
],
};
const fnClose = () => {
formRef.value.resetFields();
visible.value = false;
};
const fnSubmit = debounce(
1000,
async () => {
await validate();
await setChangePassword({ ...form.value });
fnClose();
emits("submit");
},
{ atBegin: true }
);
</script>
<style scoped lang="scss"></style>

125
src/layout/header/index.vue Normal file
View File

@ -0,0 +1,125 @@
<template>
<div class="header">
<div class="logo">
<!-- <img-->
<!-- src="/src/assets/images/public/logo1.png"-->
<!-- alt=""-->
<!-- width="134"-->
<!-- height="28"-->
<!-- />-->
{{ logoValue.title }}
</div>
<div class="breadcrumb">
<layout-breadcrumb v-if="route.meta.isBreadcrumb !== false" />
</div>
<div class="right">
<div class="user_dropdown">
<el-dropdown
trigger="click"
placement="bottom-end"
@command="fnUserDropdownCommand"
>
<div class="user_info">
<el-avatar shape="circle" :size="30" fit="fill" :src="tx" />
<span>{{ userInfo.username }}</span>
<icon-down
theme="filled"
size="16"
fill="#a2c2d3"
:stroke-width="3"
/>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="modifyPassword">
修改密码
</el-dropdown-item>
<el-dropdown-item command="signOut">退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<change-password
v-model:visible="passwordDialogVisible"
@submit="fnSignOut"
/>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useUserStore } from "@/pinia/user.js";
import { logout } from "@/request/api.js";
import LayoutBreadcrumb from "@/layout/breadcrumb/index.vue";
import tx from "@/assets/images/public/tx.png";
import ChangePassword from "./components/change_password.vue";
defineOptions({
name: "LayoutHeader",
});
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
const userInfo = userStore.getUserInfo;
const logoValue = ref({
title: "",
});
const passwordDialogVisible = ref(false);
const fnUserDropdownCommand = async (command) => {
if (command === "signOut") {
await fnSignOut();
}
if (command === "modifyPassword") {
passwordDialogVisible.value = true;
}
};
const fnSignOut = async () => {
await logout();
userStore.$reset();
await router.replace("/login");
};
</script>
<style lang="scss" scoped>
.header {
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
padding-right: 20px;
background-color: #fff;
.logo {
width: 210px;
height: 50px;
background-color: #2b2f3a;
color: #ffffff;
text-align: center;
line-height: 60px;
margin-right: 20px;
}
.right {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
.user_dropdown {
margin-top: 5px;
cursor: pointer;
.el-avatar {
margin-right: 5px;
}
.user_info {
display: flex;
align-items: center;
}
}
}
}
</style>

109
src/layout/index.vue Normal file
View File

@ -0,0 +1,109 @@
<template>
<el-container>
<el-header>
<layout-header />
</el-header>
<el-container>
<el-aside>
<el-scrollbar style="height: calc(100vh - 50px)">
<el-menu
router
unique-opened
:default-active="route.meta.activeMenu || route.path"
background-color="rgb(48, 65, 86)"
text-color="#edf7ff"
active-text-color="#409eff"
>
<layout-menu :menus="routes" />
</el-menu>
</el-scrollbar>
</el-aside>
<el-main>
<el-scrollbar style="height: calc(100vh - 50px)">
<router-view v-slot="{ Component }">
<transition name="view" mode="out-in">
<el-card :key="route.path">
<el-page-header
v-if="route.meta.isBack !== false"
title="返回"
@back="router.back()"
>
<template #content>
{{
route.matched.filter((item) => item.meta?.title).at(-1)
.meta.title
}}
</template>
</el-page-header>
<component :is="Component"></component>
</el-card>
</transition>
</router-view>
</el-scrollbar>
</el-main>
</el-container>
</el-container>
</template>
<script setup>
import LayoutHeader from "@/layout/header/index.vue";
import LayoutMenu from "@/layout/menu/index.vue";
import { useRoute, useRouter } from "vue-router";
defineOptions({
name: "Layout",
});
const router = useRouter();
const route = useRoute();
const routes = router.options.routes
.filter((item) => item.name === "app")[0]
.children.filter((item) => item.name === "backend")[0].children;
</script>
<style scoped lang="scss">
.el-header {
--el-header-padding: 0;
--el-header-height: 50px;
}
.el-aside {
width: 210px;
background-color: rgb(48, 65, 86);
}
.el-menu {
width: 210px;
min-height: calc(100vh - 50px);
border-right: none;
background-color: rgb(48, 65, 86);
:deep {
span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
:deep {
.el-sub-menu__title *,
.el-menu-item * {
vertical-align: middle !important;
}
.is-always-shadow {
box-shadow: none !important;
}
}
.el-main {
--el-main-padding: 0;
}
.el-card {
margin: 20px;
border: none !important;
min-height: calc(100vh - 90px);
}
</style>

80
src/layout/menu/index.vue Normal file
View File

@ -0,0 +1,80 @@
<template>
<template v-for="menu in props.menus" :key="menu.path">
<!-- 没有二级导航-->
<el-menu-item
v-if="fnIsShowMenuItem(menu)"
:index="menu.path"
@click="fnNavigate"
>
<component
:is="'icon-' + menu.meta?.icon"
v-if="menu.meta?.icon"
theme="filled"
fill="#a5b2c2"
size="18"
:stroke-width="3"
style="margin-right: 15px"
/>
<span>{{ menu.meta?.title }}</span>
</el-menu-item>
<!-- 有二级导航-->
<el-sub-menu v-else-if="fnIsShowSubmenu(menu)" :index="menu.path">
<template #title>
<component
:is="'icon-' + menu.meta?.icon"
v-if="menu.meta?.icon"
theme="filled"
fill="#bfcbd9"
size="18"
:stroke-width="3"
style="margin-right: 10px"
/>
<span>{{ menu.meta?.title }}</span>
</template>
<!-- 递归调用当前组件生成导航-->
<layout-menu :menus="menu.children" />
</el-sub-menu>
</template>
</template>
<script setup>
import { useRouter } from "vue-router";
import { resetQueryCriteria } from "@/hooks/useQueryCriteria.js";
const router = useRouter();
defineOptions({
name: "LayoutMenu",
});
const props = defineProps({
menus: {
type: Array,
default: () => [],
},
});
const fnIsShowMenuItem = (menu) => {
if (menu.meta?.isMenu === false) {
return false;
}
if (menu.meta?.isSubMenu === false) {
return true;
} else {
return !menu.children || menu.children.length === 0;
}
};
const fnIsShowSubmenu = (menu) => {
if (menu.meta?.isMenu === false) {
return false;
}
if (menu.meta?.isSubMenu === false) {
return false;
} else {
return menu.children && menu.children.length > 0;
}
};
const fnNavigate = (event) => {
resetQueryCriteria();
router.push(event.index);
};
</script>
<style scoped lang="scss"></style>

View File

@ -9,8 +9,12 @@ import "normalize.css";
import "animate.css";
import "viewerjs/dist/viewer.css";
import VueViewer from "v-viewer";
import { install } from "@icon-park/vue-next/es/all";
import "element-plus/es/components/loading/style/css";
import "element-plus/es/components/message/style/css";
const app = createApp(App);
install(app, "icon");
app
.use(pinia)
.use(router)

View File

@ -0,0 +1,24 @@
import { defineStore } from "pinia";
export const useMiscellaneousStore = defineStore("miscellaneousStore", {
state: () => ({
queryCriteria: {},
}),
getters: {
getQueryCriteria() {
return this.queryCriteria;
},
},
actions: {
setQueryCriteria(data) {
this.queryCriteria = data;
},
resetQueryCriteria() {
this.queryCriteria = {};
},
},
persist: {
storage: window.sessionStorage,
paths: [""],
},
});

33
src/pinia/user.js Normal file
View File

@ -0,0 +1,33 @@
import { defineStore } from "pinia";
export const useUserStore = defineStore("userStore", {
state: () => ({
userInfo: {},
token: "",
tokenTime: "1",
permissions: [],
}),
getters: {
getUserInfo: (state) => state.userInfo,
getToken: (state) => state.token,
getTokenTime: (state) => state.tokenTime,
getPermissions: (state) => state.permissions,
},
actions: {
setUserInfo(userInfo) {
this.userInfo = userInfo;
},
async setToken(token) {
this.token = token;
},
async setTokenTime(tokenTime) {
this.tokenTime = tokenTime;
},
setPermissions(permissions) {
this.permissions = permissions;
},
},
persist: {
storage: window.sessionStorage,
},
});

8
src/request/api.js Normal file
View File

@ -0,0 +1,8 @@
import { post } from "./axios";
export const Login = (params) => post("/sys/login", params); // 登录
export const logout = (params) => post("/sys/logout", params); // 退出登录
export const getUserInfo = (params) => post("/sys/user/info", params); // 获取用户信息
export const setChangePassword = (params) => post("/sys/user/password", params); // 修改密码
export const setRefreshToken = (params) =>
post("/sys/refreshToken", { loading: false, ...params }); // 刷新token

147
src/request/axios.js Normal file
View File

@ -0,0 +1,147 @@
import axios from "axios";
import { ElLoading, ElMessage } from "element-plus";
import router from "../router";
import pinia from "../pinia";
import { useUserStore } from "@/pinia/user.js";
import refreshToken from "@/assets/js/refreshToken.js";
import { getBaseUrl } from "@/assets/js/utils.js";
let loading = null;
let isTipTokenFailure = false;
function startLoading() {
loading = ElLoading.service({
lock: true,
text: "加载中...",
background: "rgba(0, 0, 0, 0.5)",
});
}
function endLoading() {
loading && loading.close();
}
axios.defaults.baseURL = getBaseUrl();
axios.defaults.timeout = 1000 * 60 * 10;
// axios.defaults.withCredentials = true;
axios.interceptors.request.use(
async (config) => {
const userStore = useUserStore(pinia);
config.headers.Token = userStore.getToken;
if (config.method === "get" || config.method === "GET") {
if (config.params.loading !== false) startLoading();
}
if (config.method === "post" || config.method === "POST") {
if (config.data.loading !== false) startLoading();
}
return config;
},
(error) => Promise.reject(error)
);
axios.interceptors.response.use(
(config) => {
if (config.config.method === "get" || config.config.method === "GET") {
if (config.config.params.loading !== false) endLoading();
}
if (config.config.method === "post" || config.config.method === "POST") {
if (config.config.headers["Content-Type"] === "multipart/form-data") {
endLoading();
} else {
if (JSON.parse(config.config.data)?.loading !== false) endLoading();
}
}
if (config.data.code === 401) {
if (!isTipTokenFailure) {
isTipTokenFailure = true;
ElMessage.error("登录失效,请重新登录");
router.push("/login").then();
isTipTokenFailure = false;
}
return Promise.reject(config.data.msg);
} else {
refreshToken();
}
return config;
},
(error) => {
if (error && error.response) {
error.message = `连接错误${error.response.status}`;
import.meta.env.DEV &&
ElMessage.error(`连接错误${error.response.status}`);
} else {
error.message = "连接到服务器失败";
ElMessage.error("连接到服务器失败");
}
return Promise.reject(error.message);
}
);
export function post(url, params = {}) {
const userStore = useUserStore(pinia);
return new Promise((resolve, reject) => {
axios
.post(url, {
corpinfoId: userStore.getUserInfo.corpinfoId,
userId: userStore.getUserInfo.userId,
...params,
})
.then((res) => {
if (res.data.result === "success") {
resolve(res.data);
} else {
ElMessage.error(res.data.msg || "系统开小差了");
reject(res.data);
}
})
.catch((err) => {
reject(err);
});
});
}
export function get(url, params = {}) {
const userStore = useUserStore(pinia);
return new Promise((resolve, reject) => {
axios
.get(url, {
params: {
corpinfoId: userStore.getUserInfo.corpinfoId,
userId: userStore.getUserInfo.userId,
...params,
},
})
.then((res) => {
if (res.data.result === "success") {
resolve(res.data);
} else {
ElMessage.error(res.data.msg || "系统开小差了");
reject(res.data);
}
})
.catch((err) => {
reject(err);
});
});
}
export function upload(url, params = {}) {
return new Promise((resolve, reject) => {
axios
.post(url, params, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((res) => {
if (res.data.result === "success") {
resolve(res.data);
} else {
ElMessage.error(res.data.msg || "系统开小差了");
reject(res.data);
}
})
.catch((err) => {
reject(err);
});
});
}

View File

@ -1,19 +1,25 @@
import { createRouter, createWebHashHistory } from "vue-router";
import children from "../components/children/index.vue";
import loayout from "@/layout/index.vue";
const routes = [
{
path: "/login",
name: "/login",
meta: { title: "登录", isLogin: false },
component: () => import("@/views/login/index"),
},
{
path: "/",
name: "app",
redirect: "/map",
meta: { title: "首页", isLogin: true },
meta: { isLogin: true },
component: children,
children: [
{
path: "/map/:type?",
name: "/map",
meta: {
title: "首页",
title: "地图",
breadcrumb: false,
isMenu: false,
isSubMenu: false,
@ -22,6 +28,29 @@ const routes = [
},
component: () => import("@/views/map/index"),
},
{
path: "/backend",
name: "backend",
meta: {
breadcrumb: false,
isMenu: false,
isSubMenu: false,
isBack: false,
},
component: loayout,
children: [
{
path: "/backend/index",
meta: { title: "首页", isMenu: false },
component: () => import("@/views/index/index"),
},
{
path: "/backend/index111",
meta: { title: "首页" },
component: () => import("@/views/index/index"),
},
],
},
],
},
{

View File

@ -0,0 +1,7 @@
<template>
<div>1</div>
</template>
<script setup></script>
<style scoped lang="scss"></style>

185
src/views/login/index.vue Normal file
View File

@ -0,0 +1,185 @@
<template>
<div class="login">
<div class="form">
<div class="title">登录</div>
<el-form
ref="formRef"
:model="form"
:rules="rules"
@submit.prevent="fnLogin"
>
<el-form-item prop="username">
<el-input
v-model="form.username"
placeholder="请输入用户名"
tabindex="1"
>
<template #prepend>
<icon-people theme="filled" size="16" fill="#909399" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="form.password"
type="password"
placeholder="请输入密码"
tabindex="2"
>
<template #prepend>
<icon-lock
theme="filled"
size="16"
fill="#909399"
:stroke-width="3"
/>
</template>
</el-input>
</el-form-item>
<el-form-item>
<verification v-model:verification-pass="verificationPass" />
</el-form-item>
<el-form-item class="button">
<el-button native-type="submit">登录</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useRouter } from "vue-router";
import Verification from "@/components/verification/index";
import { useUserStore } from "@/pinia/user";
import { getUserInfo, Login } from "@/request/api";
import { debounce } from "throttle-debounce";
import useFormValidate from "@/hooks/useFormValidate.js";
import dayjs from "dayjs";
import { encrypt } from "@/assets/js/aes_secret.js";
import { ElMessage } from "element-plus";
const userStore = useUserStore();
const router = useRouter();
const { formRef, validate } = useFormValidate();
const verificationPass = ref(false);
const form = ref({
username: "",
password: "",
});
const rules = {
username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
password: [{ required: true, message: "请输入密码", trigger: "blur" }],
};
const fnLogin = debounce(
1000,
() => {
if (import.meta.env.DEV) {
fnSubmitLogin();
return;
}
if (verificationPass.value) {
fnSubmitLogin();
} else {
ElMessage.warning("请进行登录验证");
}
},
{ atBegin: true }
);
const fnSubmitLogin = async () => {
await validate("请输入用户名密码");
const { token } = await Login({
username: encrypt(form.value.username),
password: encrypt(form.value.password),
});
await userStore.setToken(token);
await userStore.setTokenTime(dayjs().format("YYYY-MM-DD HH:mm:ss"));
const { user } = await getUserInfo();
userStore.setUserInfo({
...userStore.getUserInfo,
...user,
});
await router.replace("/backend/index");
};
</script>
<style scoped lang="scss">
.login {
width: 100%;
max-width: 1920px;
margin: 0 auto;
height: 100vh;
position: relative;
background-color: #fff;
.form {
border-radius: 5px;
box-shadow: 0 0 20px rgb(109 109 109 / 40%);
position: absolute;
right: 230px;
top: 50%;
transform: translateY(-50%);
width: 400px;
height: 490px;
padding: 40px 50px;
background-color: #fff;
z-index: 1;
&:after {
content: "";
position: absolute;
top: 40px;
left: -30px;
width: calc(100% + 60px);
height: calc(100% - 80px);
border-radius: 10px;
background-color: rgba(255, 255, 255, 0.3);
z-index: -1;
}
&:before {
content: "";
position: absolute;
top: 20px;
left: -15px;
width: calc(100% + 30px);
height: calc(100% - 40px);
border-radius: 10px;
background-color: rgba(255, 255, 255, 0.5);
z-index: -2;
}
.title {
font-size: 20px;
color: #000;
line-height: 60px;
}
.el-form-item {
.el-input {
height: 40px;
}
}
.button {
.el-button {
background: #0a7dfe;
height: 45px;
width: 100%;
color: #ffffff;
margin-top: 10px;
}
}
}
}
:deep {
.el-input-group__prepend {
padding-top: 5px !important;
}
.el-carousel__indicators {
display: none;
}
}
</style>

View File

@ -3,6 +3,9 @@ import vue from "@vitejs/plugin-vue";
import eslintPlugin from "vite-plugin-eslint";
import removeConsole from "vite-plugin-remove-console";
import { envParse } from "vite-plugin-env-parse";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
export default ({ mode }) => {
return defineConfig({
@ -11,6 +14,12 @@ export default ({ mode }) => {
vue(),
envParse(),
eslintPlugin(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
removeConsole({
includes: [
"assert",