feat:添加后台管理系统布局
This commit is contained in:
parent
7abe5d116d
commit
2b885dc351
|
|
@ -16,6 +16,9 @@
|
||||||
"format": "prettier --write src/"
|
"format": "prettier --write src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ant-design/icons-vue": "^7.0.1",
|
||||||
|
"ant-design-vue": "^4.2.6",
|
||||||
|
"echarts": "^5.6.0",
|
||||||
"pinia": "^3.0.1",
|
"pinia": "^3.0.1",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-router": "^4.5.0"
|
"vue-router": "^4.5.0"
|
||||||
|
|
|
||||||
219
pnpm-lock.yaml
219
pnpm-lock.yaml
|
|
@ -8,6 +8,15 @@ importers:
|
||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@ant-design/icons-vue':
|
||||||
|
specifier: ^7.0.1
|
||||||
|
version: 7.0.1(vue@3.5.16(typescript@5.8.3))
|
||||||
|
ant-design-vue:
|
||||||
|
specifier: ^4.2.6
|
||||||
|
version: 4.2.6(vue@3.5.16(typescript@5.8.3))
|
||||||
|
echarts:
|
||||||
|
specifier: ^5.6.0
|
||||||
|
version: 5.6.0
|
||||||
pinia:
|
pinia:
|
||||||
specifier: ^3.0.1
|
specifier: ^3.0.1
|
||||||
version: 3.0.3(typescript@5.8.3)(vue@3.5.16(typescript@5.8.3))
|
version: 3.0.3(typescript@5.8.3)(vue@3.5.16(typescript@5.8.3))
|
||||||
|
|
@ -100,6 +109,17 @@ packages:
|
||||||
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
|
'@ant-design/colors@6.0.0':
|
||||||
|
resolution: {integrity: sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==}
|
||||||
|
|
||||||
|
'@ant-design/icons-svg@4.4.2':
|
||||||
|
resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==}
|
||||||
|
|
||||||
|
'@ant-design/icons-vue@7.0.1':
|
||||||
|
resolution: {integrity: sha512-eCqY2unfZK6Fe02AwFlDHLfoyEFreP6rBwAZMIJ1LugmfMiVgwWDYlp1YsRugaPtICYOabV1iWxXdP12u9U43Q==}
|
||||||
|
peerDependencies:
|
||||||
|
vue: '>=3.0.3'
|
||||||
|
|
||||||
'@antfu/utils@0.7.10':
|
'@antfu/utils@0.7.10':
|
||||||
resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==}
|
resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==}
|
||||||
|
|
||||||
|
|
@ -230,6 +250,10 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@babel/core': ^7.0.0-0
|
'@babel/core': ^7.0.0-0
|
||||||
|
|
||||||
|
'@babel/runtime@7.27.6':
|
||||||
|
resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/template@7.27.2':
|
'@babel/template@7.27.2':
|
||||||
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
|
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
@ -270,6 +294,16 @@ packages:
|
||||||
resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
|
resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@ctrl/tinycolor@3.6.1':
|
||||||
|
resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
'@emotion/hash@0.9.2':
|
||||||
|
resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
|
||||||
|
|
||||||
|
'@emotion/unitless@0.8.1':
|
||||||
|
resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==}
|
||||||
|
|
||||||
'@esbuild/aix-ppc64@0.25.5':
|
'@esbuild/aix-ppc64@0.25.5':
|
||||||
resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==}
|
resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
@ -685,6 +719,9 @@ packages:
|
||||||
'@sec-ant/readable-stream@0.4.1':
|
'@sec-ant/readable-stream@0.4.1':
|
||||||
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
|
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
|
||||||
|
|
||||||
|
'@simonwep/pickr@1.8.2':
|
||||||
|
resolution: {integrity: sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==}
|
||||||
|
|
||||||
'@sindresorhus/merge-streams@4.0.0':
|
'@sindresorhus/merge-streams@4.0.0':
|
||||||
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
|
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
@ -983,6 +1020,12 @@ packages:
|
||||||
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
|
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
ant-design-vue@4.2.6:
|
||||||
|
resolution: {integrity: sha512-t7eX13Yj3i9+i5g9lqFyYneoIb3OzTvQjq9Tts1i+eiOd3Eva/6GagxBSXM1fOCjqemIu0FYVE1ByZ/38epR3Q==}
|
||||||
|
engines: {node: '>=12.22.0'}
|
||||||
|
peerDependencies:
|
||||||
|
vue: '>=3.2.0'
|
||||||
|
|
||||||
anymatch@3.1.3:
|
anymatch@3.1.3:
|
||||||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
@ -990,10 +1033,16 @@ packages:
|
||||||
argparse@2.0.1:
|
argparse@2.0.1:
|
||||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||||
|
|
||||||
|
array-tree-filter@2.1.0:
|
||||||
|
resolution: {integrity: sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==}
|
||||||
|
|
||||||
assertion-error@2.0.1:
|
assertion-error@2.0.1:
|
||||||
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
async-validator@4.2.5:
|
||||||
|
resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
|
||||||
|
|
||||||
balanced-match@1.0.2:
|
balanced-match@1.0.2:
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
|
|
||||||
|
|
@ -1064,6 +1113,9 @@ packages:
|
||||||
resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
|
resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
compute-scroll-into-view@1.0.20:
|
||||||
|
resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==}
|
||||||
|
|
||||||
concat-map@0.0.1:
|
concat-map@0.0.1:
|
||||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||||
|
|
||||||
|
|
@ -1083,6 +1135,9 @@ packages:
|
||||||
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
|
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
|
||||||
engines: {node: '>=12.13'}
|
engines: {node: '>=12.13'}
|
||||||
|
|
||||||
|
core-js@3.43.0:
|
||||||
|
resolution: {integrity: sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==}
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
@ -1103,6 +1158,9 @@ packages:
|
||||||
resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
|
resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
dayjs@1.11.13:
|
||||||
|
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
|
||||||
|
|
||||||
de-indent@1.0.2:
|
de-indent@1.0.2:
|
||||||
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
|
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
|
||||||
|
|
||||||
|
|
@ -1137,9 +1195,18 @@ packages:
|
||||||
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
|
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
dom-align@1.12.4:
|
||||||
|
resolution: {integrity: sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==}
|
||||||
|
|
||||||
|
dom-scroll-into-view@2.0.1:
|
||||||
|
resolution: {integrity: sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==}
|
||||||
|
|
||||||
eastasianwidth@0.2.0:
|
eastasianwidth@0.2.0:
|
||||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||||
|
|
||||||
|
echarts@5.6.0:
|
||||||
|
resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==}
|
||||||
|
|
||||||
editorconfig@1.0.4:
|
editorconfig@1.0.4:
|
||||||
resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==}
|
resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
@ -1455,6 +1522,10 @@ packages:
|
||||||
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
|
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
is-plain-object@3.0.1:
|
||||||
|
resolution: {integrity: sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
is-potential-custom-element-name@1.0.1:
|
is-potential-custom-element-name@1.0.1:
|
||||||
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
|
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
|
||||||
|
|
||||||
|
|
@ -1563,12 +1634,19 @@ packages:
|
||||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
lodash-es@4.17.21:
|
||||||
|
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
|
||||||
|
|
||||||
lodash.merge@4.6.2:
|
lodash.merge@4.6.2:
|
||||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||||
|
|
||||||
lodash@4.17.21:
|
lodash@4.17.21:
|
||||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||||
|
|
||||||
|
loose-envify@1.4.0:
|
||||||
|
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
loupe@3.1.3:
|
loupe@3.1.3:
|
||||||
resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==}
|
resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==}
|
||||||
|
|
||||||
|
|
@ -1634,6 +1712,9 @@ packages:
|
||||||
engines: {node: ^18 || >=20}
|
engines: {node: ^18 || >=20}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
nanopop@2.4.2:
|
||||||
|
resolution: {integrity: sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==}
|
||||||
|
|
||||||
natural-compare@1.4.0:
|
natural-compare@1.4.0:
|
||||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||||
|
|
||||||
|
|
@ -1809,6 +1890,9 @@ packages:
|
||||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||||
engines: {node: '>=8.10.0'}
|
engines: {node: '>=8.10.0'}
|
||||||
|
|
||||||
|
resize-observer-polyfill@1.5.1:
|
||||||
|
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
|
||||||
|
|
||||||
resolve-from@4.0.0:
|
resolve-from@4.0.0:
|
||||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|
@ -1842,6 +1926,9 @@ packages:
|
||||||
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
|
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
|
||||||
engines: {node: '>=v12.22.7'}
|
engines: {node: '>=v12.22.7'}
|
||||||
|
|
||||||
|
scroll-into-view-if-needed@2.2.31:
|
||||||
|
resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==}
|
||||||
|
|
||||||
scule@1.3.0:
|
scule@1.3.0:
|
||||||
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
|
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
|
||||||
|
|
||||||
|
|
@ -1854,6 +1941,9 @@ packages:
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
shallow-equal@1.2.1:
|
||||||
|
resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==}
|
||||||
|
|
||||||
shebang-command@2.0.0:
|
shebang-command@2.0.0:
|
||||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
@ -1918,6 +2008,9 @@ packages:
|
||||||
strip-literal@3.0.0:
|
strip-literal@3.0.0:
|
||||||
resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==}
|
resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==}
|
||||||
|
|
||||||
|
stylis@4.3.6:
|
||||||
|
resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==}
|
||||||
|
|
||||||
superjson@2.2.2:
|
superjson@2.2.2:
|
||||||
resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==}
|
resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
|
|
@ -1933,6 +2026,10 @@ packages:
|
||||||
resolution: {integrity: sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==}
|
resolution: {integrity: sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==}
|
||||||
engines: {node: ^14.18.0 || >=16.0.0}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
|
|
||||||
|
throttle-debounce@5.0.2:
|
||||||
|
resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==}
|
||||||
|
engines: {node: '>=12.22'}
|
||||||
|
|
||||||
tinybench@2.9.0:
|
tinybench@2.9.0:
|
||||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||||
|
|
||||||
|
|
@ -1984,6 +2081,9 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4'
|
typescript: '>=4.8.4'
|
||||||
|
|
||||||
|
tslib@2.3.0:
|
||||||
|
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
|
||||||
|
|
||||||
type-check@0.4.0:
|
type-check@0.4.0:
|
||||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
@ -2185,6 +2285,12 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=5.0.0'
|
typescript: '>=5.0.0'
|
||||||
|
|
||||||
|
vue-types@3.0.2:
|
||||||
|
resolution: {integrity: sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==}
|
||||||
|
engines: {node: '>=10.15.0'}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.0.0
|
||||||
|
|
||||||
vue@3.5.16:
|
vue@3.5.16:
|
||||||
resolution: {integrity: sha512-rjOV2ecxMd5SiAmof2xzh2WxntRcigkX/He4YFJ6WdRvVUrbt6DxC1Iujh10XLl8xCDRDtGKMeO3D+pRQ1PP9w==}
|
resolution: {integrity: sha512-rjOV2ecxMd5SiAmof2xzh2WxntRcigkX/He4YFJ6WdRvVUrbt6DxC1Iujh10XLl8xCDRDtGKMeO3D+pRQ1PP9w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -2197,6 +2303,9 @@ packages:
|
||||||
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
|
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
warning@4.0.3:
|
||||||
|
resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==}
|
||||||
|
|
||||||
webidl-conversions@7.0.0:
|
webidl-conversions@7.0.0:
|
||||||
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
@ -2277,6 +2386,9 @@ packages:
|
||||||
resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==}
|
resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
zrender@5.6.1:
|
||||||
|
resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==}
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@ampproject/remapping@2.3.0':
|
'@ampproject/remapping@2.3.0':
|
||||||
|
|
@ -2284,6 +2396,18 @@ snapshots:
|
||||||
'@jridgewell/gen-mapping': 0.3.8
|
'@jridgewell/gen-mapping': 0.3.8
|
||||||
'@jridgewell/trace-mapping': 0.3.25
|
'@jridgewell/trace-mapping': 0.3.25
|
||||||
|
|
||||||
|
'@ant-design/colors@6.0.0':
|
||||||
|
dependencies:
|
||||||
|
'@ctrl/tinycolor': 3.6.1
|
||||||
|
|
||||||
|
'@ant-design/icons-svg@4.4.2': {}
|
||||||
|
|
||||||
|
'@ant-design/icons-vue@7.0.1(vue@3.5.16(typescript@5.8.3))':
|
||||||
|
dependencies:
|
||||||
|
'@ant-design/colors': 6.0.0
|
||||||
|
'@ant-design/icons-svg': 4.4.2
|
||||||
|
vue: 3.5.16(typescript@5.8.3)
|
||||||
|
|
||||||
'@antfu/utils@0.7.10': {}
|
'@antfu/utils@0.7.10': {}
|
||||||
|
|
||||||
'@asamuzakjp/css-color@3.2.0':
|
'@asamuzakjp/css-color@3.2.0':
|
||||||
|
|
@ -2460,6 +2584,8 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@babel/runtime@7.27.6': {}
|
||||||
|
|
||||||
'@babel/template@7.27.2':
|
'@babel/template@7.27.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.27.1
|
'@babel/code-frame': 7.27.1
|
||||||
|
|
@ -2503,6 +2629,12 @@ snapshots:
|
||||||
|
|
||||||
'@csstools/css-tokenizer@3.0.4': {}
|
'@csstools/css-tokenizer@3.0.4': {}
|
||||||
|
|
||||||
|
'@ctrl/tinycolor@3.6.1': {}
|
||||||
|
|
||||||
|
'@emotion/hash@0.9.2': {}
|
||||||
|
|
||||||
|
'@emotion/unitless@0.8.1': {}
|
||||||
|
|
||||||
'@esbuild/aix-ppc64@0.25.5':
|
'@esbuild/aix-ppc64@0.25.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
|
@ -2782,6 +2914,11 @@ snapshots:
|
||||||
|
|
||||||
'@sec-ant/readable-stream@0.4.1': {}
|
'@sec-ant/readable-stream@0.4.1': {}
|
||||||
|
|
||||||
|
'@simonwep/pickr@1.8.2':
|
||||||
|
dependencies:
|
||||||
|
core-js: 3.43.0
|
||||||
|
nanopop: 2.4.2
|
||||||
|
|
||||||
'@sindresorhus/merge-streams@4.0.0': {}
|
'@sindresorhus/merge-streams@4.0.0': {}
|
||||||
|
|
||||||
'@tsconfig/node22@22.0.2': {}
|
'@tsconfig/node22@22.0.2': {}
|
||||||
|
|
@ -3176,6 +3313,32 @@ snapshots:
|
||||||
|
|
||||||
ansi-styles@6.2.1: {}
|
ansi-styles@6.2.1: {}
|
||||||
|
|
||||||
|
ant-design-vue@4.2.6(vue@3.5.16(typescript@5.8.3)):
|
||||||
|
dependencies:
|
||||||
|
'@ant-design/colors': 6.0.0
|
||||||
|
'@ant-design/icons-vue': 7.0.1(vue@3.5.16(typescript@5.8.3))
|
||||||
|
'@babel/runtime': 7.27.6
|
||||||
|
'@ctrl/tinycolor': 3.6.1
|
||||||
|
'@emotion/hash': 0.9.2
|
||||||
|
'@emotion/unitless': 0.8.1
|
||||||
|
'@simonwep/pickr': 1.8.2
|
||||||
|
array-tree-filter: 2.1.0
|
||||||
|
async-validator: 4.2.5
|
||||||
|
csstype: 3.1.3
|
||||||
|
dayjs: 1.11.13
|
||||||
|
dom-align: 1.12.4
|
||||||
|
dom-scroll-into-view: 2.0.1
|
||||||
|
lodash: 4.17.21
|
||||||
|
lodash-es: 4.17.21
|
||||||
|
resize-observer-polyfill: 1.5.1
|
||||||
|
scroll-into-view-if-needed: 2.2.31
|
||||||
|
shallow-equal: 1.2.1
|
||||||
|
stylis: 4.3.6
|
||||||
|
throttle-debounce: 5.0.2
|
||||||
|
vue: 3.5.16(typescript@5.8.3)
|
||||||
|
vue-types: 3.0.2(vue@3.5.16(typescript@5.8.3))
|
||||||
|
warning: 4.0.3
|
||||||
|
|
||||||
anymatch@3.1.3:
|
anymatch@3.1.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
normalize-path: 3.0.0
|
normalize-path: 3.0.0
|
||||||
|
|
@ -3183,8 +3346,12 @@ snapshots:
|
||||||
|
|
||||||
argparse@2.0.1: {}
|
argparse@2.0.1: {}
|
||||||
|
|
||||||
|
array-tree-filter@2.1.0: {}
|
||||||
|
|
||||||
assertion-error@2.0.1: {}
|
assertion-error@2.0.1: {}
|
||||||
|
|
||||||
|
async-validator@4.2.5: {}
|
||||||
|
|
||||||
balanced-match@1.0.2: {}
|
balanced-match@1.0.2: {}
|
||||||
|
|
||||||
binary-extensions@2.3.0: {}
|
binary-extensions@2.3.0: {}
|
||||||
|
|
@ -3258,6 +3425,8 @@ snapshots:
|
||||||
|
|
||||||
commander@10.0.1: {}
|
commander@10.0.1: {}
|
||||||
|
|
||||||
|
compute-scroll-into-view@1.0.20: {}
|
||||||
|
|
||||||
concat-map@0.0.1: {}
|
concat-map@0.0.1: {}
|
||||||
|
|
||||||
confbox@0.1.8: {}
|
confbox@0.1.8: {}
|
||||||
|
|
@ -3275,6 +3444,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-what: 4.1.16
|
is-what: 4.1.16
|
||||||
|
|
||||||
|
core-js@3.43.0: {}
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
path-key: 3.1.1
|
path-key: 3.1.1
|
||||||
|
|
@ -3295,6 +3466,8 @@ snapshots:
|
||||||
whatwg-mimetype: 4.0.0
|
whatwg-mimetype: 4.0.0
|
||||||
whatwg-url: 14.2.0
|
whatwg-url: 14.2.0
|
||||||
|
|
||||||
|
dayjs@1.11.13: {}
|
||||||
|
|
||||||
de-indent@1.0.2: {}
|
de-indent@1.0.2: {}
|
||||||
|
|
||||||
debug@4.4.1:
|
debug@4.4.1:
|
||||||
|
|
@ -3316,8 +3489,17 @@ snapshots:
|
||||||
|
|
||||||
define-lazy-prop@3.0.0: {}
|
define-lazy-prop@3.0.0: {}
|
||||||
|
|
||||||
|
dom-align@1.12.4: {}
|
||||||
|
|
||||||
|
dom-scroll-into-view@2.0.1: {}
|
||||||
|
|
||||||
eastasianwidth@0.2.0: {}
|
eastasianwidth@0.2.0: {}
|
||||||
|
|
||||||
|
echarts@5.6.0:
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.3.0
|
||||||
|
zrender: 5.6.1
|
||||||
|
|
||||||
editorconfig@1.0.4:
|
editorconfig@1.0.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@one-ini/wasm': 0.1.1
|
'@one-ini/wasm': 0.1.1
|
||||||
|
|
@ -3654,6 +3836,8 @@ snapshots:
|
||||||
|
|
||||||
is-plain-obj@4.1.0: {}
|
is-plain-obj@4.1.0: {}
|
||||||
|
|
||||||
|
is-plain-object@3.0.1: {}
|
||||||
|
|
||||||
is-potential-custom-element-name@1.0.1: {}
|
is-potential-custom-element-name@1.0.1: {}
|
||||||
|
|
||||||
is-stream@4.0.1: {}
|
is-stream@4.0.1: {}
|
||||||
|
|
@ -3764,10 +3948,16 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-locate: 5.0.0
|
p-locate: 5.0.0
|
||||||
|
|
||||||
|
lodash-es@4.17.21: {}
|
||||||
|
|
||||||
lodash.merge@4.6.2: {}
|
lodash.merge@4.6.2: {}
|
||||||
|
|
||||||
lodash@4.17.21: {}
|
lodash@4.17.21: {}
|
||||||
|
|
||||||
|
loose-envify@1.4.0:
|
||||||
|
dependencies:
|
||||||
|
js-tokens: 4.0.0
|
||||||
|
|
||||||
loupe@3.1.3: {}
|
loupe@3.1.3: {}
|
||||||
|
|
||||||
lru-cache@10.4.3: {}
|
lru-cache@10.4.3: {}
|
||||||
|
|
@ -3822,6 +4012,8 @@ snapshots:
|
||||||
|
|
||||||
nanoid@5.1.5: {}
|
nanoid@5.1.5: {}
|
||||||
|
|
||||||
|
nanopop@2.4.2: {}
|
||||||
|
|
||||||
natural-compare@1.4.0: {}
|
natural-compare@1.4.0: {}
|
||||||
|
|
||||||
node-releases@2.0.19: {}
|
node-releases@2.0.19: {}
|
||||||
|
|
@ -3989,6 +4181,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
|
|
||||||
|
resize-observer-polyfill@1.5.1: {}
|
||||||
|
|
||||||
resolve-from@4.0.0: {}
|
resolve-from@4.0.0: {}
|
||||||
|
|
||||||
reusify@1.1.0: {}
|
reusify@1.1.0: {}
|
||||||
|
|
@ -4035,12 +4229,18 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
xmlchars: 2.2.0
|
xmlchars: 2.2.0
|
||||||
|
|
||||||
|
scroll-into-view-if-needed@2.2.31:
|
||||||
|
dependencies:
|
||||||
|
compute-scroll-into-view: 1.0.20
|
||||||
|
|
||||||
scule@1.3.0: {}
|
scule@1.3.0: {}
|
||||||
|
|
||||||
semver@6.3.1: {}
|
semver@6.3.1: {}
|
||||||
|
|
||||||
semver@7.7.2: {}
|
semver@7.7.2: {}
|
||||||
|
|
||||||
|
shallow-equal@1.2.1: {}
|
||||||
|
|
||||||
shebang-command@2.0.0:
|
shebang-command@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
shebang-regex: 3.0.0
|
shebang-regex: 3.0.0
|
||||||
|
|
@ -4095,6 +4295,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
js-tokens: 9.0.1
|
js-tokens: 9.0.1
|
||||||
|
|
||||||
|
stylis@4.3.6: {}
|
||||||
|
|
||||||
superjson@2.2.2:
|
superjson@2.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
copy-anything: 3.0.5
|
copy-anything: 3.0.5
|
||||||
|
|
@ -4109,6 +4311,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@pkgr/core': 0.2.7
|
'@pkgr/core': 0.2.7
|
||||||
|
|
||||||
|
throttle-debounce@5.0.2: {}
|
||||||
|
|
||||||
tinybench@2.9.0: {}
|
tinybench@2.9.0: {}
|
||||||
|
|
||||||
tinyexec@0.3.2: {}
|
tinyexec@0.3.2: {}
|
||||||
|
|
@ -4148,6 +4352,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.8.3
|
typescript: 5.8.3
|
||||||
|
|
||||||
|
tslib@2.3.0: {}
|
||||||
|
|
||||||
type-check@0.4.0:
|
type-check@0.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
prelude-ls: 1.2.1
|
prelude-ls: 1.2.1
|
||||||
|
|
@ -4392,6 +4598,11 @@ snapshots:
|
||||||
'@vue/language-core': 2.2.10(typescript@5.8.3)
|
'@vue/language-core': 2.2.10(typescript@5.8.3)
|
||||||
typescript: 5.8.3
|
typescript: 5.8.3
|
||||||
|
|
||||||
|
vue-types@3.0.2(vue@3.5.16(typescript@5.8.3)):
|
||||||
|
dependencies:
|
||||||
|
is-plain-object: 3.0.1
|
||||||
|
vue: 3.5.16(typescript@5.8.3)
|
||||||
|
|
||||||
vue@3.5.16(typescript@5.8.3):
|
vue@3.5.16(typescript@5.8.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vue/compiler-dom': 3.5.16
|
'@vue/compiler-dom': 3.5.16
|
||||||
|
|
@ -4406,6 +4617,10 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
xml-name-validator: 5.0.0
|
xml-name-validator: 5.0.0
|
||||||
|
|
||||||
|
warning@4.0.3:
|
||||||
|
dependencies:
|
||||||
|
loose-envify: 1.4.0
|
||||||
|
|
||||||
webidl-conversions@7.0.0: {}
|
webidl-conversions@7.0.0: {}
|
||||||
|
|
||||||
webpack-virtual-modules@0.6.2: {}
|
webpack-virtual-modules@0.6.2: {}
|
||||||
|
|
@ -4461,3 +4676,7 @@ snapshots:
|
||||||
yocto-queue@0.1.0: {}
|
yocto-queue@0.1.0: {}
|
||||||
|
|
||||||
yoctocolors@2.1.1: {}
|
yoctocolors@2.1.1: {}
|
||||||
|
|
||||||
|
zrender@5.6.1:
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.3.0
|
||||||
|
|
|
||||||
86
src/App.vue
86
src/App.vue
|
|
@ -1,85 +1,21 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RouterLink, RouterView } from 'vue-router'
|
// 不需要导入AdminLayout,因为它会通过路由系统加载
|
||||||
import HelloWorld from './components/HelloWorld.vue'
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header>
|
<router-view />
|
||||||
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
|
|
||||||
|
|
||||||
<div class="wrapper">
|
|
||||||
<HelloWorld msg="You did it!" />
|
|
||||||
|
|
||||||
<nav>
|
|
||||||
<RouterLink to="/">Home</RouterLink>
|
|
||||||
<RouterLink to="/about">About</RouterLink>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<RouterView />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style>
|
||||||
header {
|
/* 全局样式 */
|
||||||
line-height: 1.5;
|
html, body {
|
||||||
max-height: 100vh;
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
#app {
|
||||||
display: block;
|
height: 100%;
|
||||||
margin: 0 auto 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
|
||||||
width: 100%;
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a.router-link-exact-active {
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a.router-link-exact-active:hover {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0 1rem;
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a:first-of-type {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
padding-right: calc(var(--section-gap) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
margin: 0 2rem 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
header .wrapper {
|
|
||||||
display: flex;
|
|
||||||
place-items: flex-start;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
|
||||||
text-align: left;
|
|
||||||
margin-left: -1rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
|
|
||||||
padding: 1rem 0;
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
/* color palette from <https://github.com/vuejs/theme> */
|
|
||||||
:root {
|
|
||||||
--vt-c-white: #ffffff;
|
|
||||||
--vt-c-white-soft: #f8f8f8;
|
|
||||||
--vt-c-white-mute: #f2f2f2;
|
|
||||||
|
|
||||||
--vt-c-black: #181818;
|
|
||||||
--vt-c-black-soft: #222222;
|
|
||||||
--vt-c-black-mute: #282828;
|
|
||||||
|
|
||||||
--vt-c-indigo: #2c3e50;
|
|
||||||
|
|
||||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
|
||||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
|
||||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
|
||||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
|
||||||
|
|
||||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
|
||||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
|
||||||
--vt-c-text-dark-1: var(--vt-c-white);
|
|
||||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* semantic color variables for this project */
|
|
||||||
:root {
|
|
||||||
--color-background: var(--vt-c-white);
|
|
||||||
--color-background-soft: var(--vt-c-white-soft);
|
|
||||||
--color-background-mute: var(--vt-c-white-mute);
|
|
||||||
|
|
||||||
--color-border: var(--vt-c-divider-light-2);
|
|
||||||
--color-border-hover: var(--vt-c-divider-light-1);
|
|
||||||
|
|
||||||
--color-heading: var(--vt-c-text-light-1);
|
|
||||||
--color-text: var(--vt-c-text-light-1);
|
|
||||||
|
|
||||||
--section-gap: 160px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--color-background: var(--vt-c-black);
|
|
||||||
--color-background-soft: var(--vt-c-black-soft);
|
|
||||||
--color-background-mute: var(--vt-c-black-mute);
|
|
||||||
|
|
||||||
--color-border: var(--vt-c-divider-dark-2);
|
|
||||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
|
||||||
|
|
||||||
--color-heading: var(--vt-c-text-dark-1);
|
|
||||||
--color-text: var(--vt-c-text-dark-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
min-height: 100vh;
|
|
||||||
color: var(--color-text);
|
|
||||||
background: var(--color-background);
|
|
||||||
transition:
|
|
||||||
color 0.5s,
|
|
||||||
background-color 0.5s;
|
|
||||||
line-height: 1.6;
|
|
||||||
font-family:
|
|
||||||
Inter,
|
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
'Segoe UI',
|
|
||||||
Roboto,
|
|
||||||
Oxygen,
|
|
||||||
Ubuntu,
|
|
||||||
Cantarell,
|
|
||||||
'Fira Sans',
|
|
||||||
'Droid Sans',
|
|
||||||
'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
font-size: 15px;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
@import './base.css';
|
|
||||||
|
|
||||||
#app {
|
|
||||||
max-width: 1280px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
a,
|
|
||||||
.green {
|
|
||||||
text-decoration: none;
|
|
||||||
color: hsla(160, 100%, 37%, 1);
|
|
||||||
transition: 0.4s;
|
|
||||||
padding: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (hover: hover) {
|
|
||||||
a:hover {
|
|
||||||
background-color: hsla(160, 100%, 37%, 0.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
body {
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
padding: 0 2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,15 +8,16 @@ export {}
|
||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
HelloWorld: typeof import('./components/HelloWorld.vue')['default']
|
AdminLayout: typeof import('./components/layout/AdminLayout.vue')['default']
|
||||||
IconCommunity: typeof import('./components/icons/IconCommunity.vue')['default']
|
AdvancedForm: typeof import('./components/form/AdvancedForm.vue')['default']
|
||||||
IconDocumentation: typeof import('./components/icons/IconDocumentation.vue')['default']
|
AdvancedTable: typeof import('./components/table/AdvancedTable.vue')['default']
|
||||||
IconEcosystem: typeof import('./components/icons/IconEcosystem.vue')['default']
|
Breadcrumb: typeof import('./components/common/Breadcrumb.vue')['default']
|
||||||
IconSupport: typeof import('./components/icons/IconSupport.vue')['default']
|
FooterBar: typeof import('./components/layout/FooterBar.vue')['default']
|
||||||
IconTooling: typeof import('./components/icons/IconTooling.vue')['default']
|
HeaderNav: typeof import('./components/layout/HeaderNav.vue')['default']
|
||||||
|
MainContent: typeof import('./components/layout/MainContent.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
TheWelcome: typeof import('./components/TheWelcome.vue')['default']
|
SideMenu: typeof import('./components/layout/SideMenu.vue')['default']
|
||||||
WelcomeItem: typeof import('./components/WelcomeItem.vue')['default']
|
YourComponent: typeof import('./components/YourComponent.vue')['default']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
defineProps<{
|
|
||||||
msg: string
|
|
||||||
}>()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="greetings">
|
|
||||||
<h1 class="green">{{ msg }}</h1>
|
|
||||||
<h3>
|
|
||||||
You’ve successfully created a project with
|
|
||||||
<a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> +
|
|
||||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
h1 {
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 2.6rem;
|
|
||||||
position: relative;
|
|
||||||
top: -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.greetings h1,
|
|
||||||
.greetings h3 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.greetings h1,
|
|
||||||
.greetings h3 {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import WelcomeItem from './WelcomeItem.vue'
|
|
||||||
import DocumentationIcon from './icons/IconDocumentation.vue'
|
|
||||||
import ToolingIcon from './icons/IconTooling.vue'
|
|
||||||
import EcosystemIcon from './icons/IconEcosystem.vue'
|
|
||||||
import CommunityIcon from './icons/IconCommunity.vue'
|
|
||||||
import SupportIcon from './icons/IconSupport.vue'
|
|
||||||
const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md')
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<DocumentationIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Documentation</template>
|
|
||||||
|
|
||||||
Vue’s
|
|
||||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
|
|
||||||
provides you with all information you need to get started.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<ToolingIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Tooling</template>
|
|
||||||
|
|
||||||
This project is served and bundled with
|
|
||||||
<a href="https://vite.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
|
|
||||||
recommended IDE setup is
|
|
||||||
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a>
|
|
||||||
+
|
|
||||||
<a href="https://github.com/vuejs/language-tools" target="_blank" rel="noopener">Vue - Official</a>. If
|
|
||||||
you need to test your components and web pages, check out
|
|
||||||
<a href="https://vitest.dev/" target="_blank" rel="noopener">Vitest</a>
|
|
||||||
and
|
|
||||||
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a>
|
|
||||||
/
|
|
||||||
<a href="https://playwright.dev/" target="_blank" rel="noopener">Playwright</a>.
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
More instructions are available in
|
|
||||||
<a href="javascript:void(0)" @click="openReadmeInEditor"><code>README.md</code></a
|
|
||||||
>.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<EcosystemIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Ecosystem</template>
|
|
||||||
|
|
||||||
Get official tools and libraries for your project:
|
|
||||||
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
|
|
||||||
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
|
|
||||||
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
|
|
||||||
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
|
|
||||||
you need more resources, we suggest paying
|
|
||||||
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
|
|
||||||
a visit.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<CommunityIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Community</template>
|
|
||||||
|
|
||||||
Got stuck? Ask your question on
|
|
||||||
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>
|
|
||||||
(our official Discord server), or
|
|
||||||
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
|
|
||||||
>StackOverflow</a
|
|
||||||
>. You should also follow the official
|
|
||||||
<a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener">@vuejs.org</a>
|
|
||||||
Bluesky account or the
|
|
||||||
<a href="https://x.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
|
|
||||||
X account for latest news in the Vue world.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<SupportIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Support Vue</template>
|
|
||||||
|
|
||||||
As an independent project, Vue relies on community backing for its sustainability. You can help
|
|
||||||
us by
|
|
||||||
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
|
|
||||||
</WelcomeItem>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="item">
|
|
||||||
<i>
|
|
||||||
<slot name="icon"></slot>
|
|
||||||
</i>
|
|
||||||
<div class="details">
|
|
||||||
<h3>
|
|
||||||
<slot name="heading"></slot>
|
|
||||||
</h3>
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.item {
|
|
||||||
margin-top: 2rem;
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details {
|
|
||||||
flex: 1;
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
place-content: center;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-bottom: 0.4rem;
|
|
||||||
color: var(--color-heading);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.item {
|
|
||||||
margin-top: 0;
|
|
||||||
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
top: calc(50% - 25px);
|
|
||||||
left: -26px;
|
|
||||||
position: absolute;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
background: var(--color-background);
|
|
||||||
border-radius: 8px;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:before {
|
|
||||||
content: ' ';
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: calc(50% + 25px);
|
|
||||||
height: calc(50% - 25px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:after {
|
|
||||||
content: ' ';
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: calc(50% + 25px);
|
|
||||||
height: calc(50% - 25px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:first-of-type:before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:last-of-type:after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
import { describe, it, expect } from 'vitest'
|
|
||||||
|
|
||||||
import { mount } from '@vue/test-utils'
|
|
||||||
import HelloWorld from '../HelloWorld.vue'
|
|
||||||
|
|
||||||
describe('HelloWorld', () => {
|
|
||||||
it('renders properly', () => {
|
|
||||||
const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
|
|
||||||
expect(wrapper.text()).toContain('Hello Vitest')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
<template>
|
||||||
|
<a-breadcrumb class="breadcrumb">
|
||||||
|
<a-breadcrumb-item v-for="(item, index) in breadcrumbItems" :key="index">
|
||||||
|
<router-link v-if="item.path && index !== breadcrumbItems.length - 1" :to="item.path">
|
||||||
|
{{ item.title }}
|
||||||
|
</router-link>
|
||||||
|
<span v-else>{{ item.title }}</span>
|
||||||
|
</a-breadcrumb-item>
|
||||||
|
</a-breadcrumb>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
// 面包屑项目
|
||||||
|
const breadcrumbItems = ref<{ title: string; path: string }[]>([])
|
||||||
|
|
||||||
|
// 根据路由生成面包屑
|
||||||
|
const generateBreadcrumb = () => {
|
||||||
|
const matched = route.matched
|
||||||
|
const items: { title: string; path: string }[] = []
|
||||||
|
|
||||||
|
matched.forEach((record) => {
|
||||||
|
if (record.meta && record.meta.title) {
|
||||||
|
items.push({
|
||||||
|
title: record.meta.title as string,
|
||||||
|
path: record.path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 如果没有匹配到任何带标题的路由,则显示默认面包屑
|
||||||
|
if (items.length === 0) {
|
||||||
|
const pathSnippets = route.path.split('/').filter((i) => i)
|
||||||
|
let path = ''
|
||||||
|
|
||||||
|
pathSnippets.forEach((snippet) => {
|
||||||
|
path += `/${snippet}`
|
||||||
|
const title = snippet.charAt(0).toUpperCase() + snippet.slice(1)
|
||||||
|
items.push({
|
||||||
|
title,
|
||||||
|
path,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 如果路径为空(即首页),添加首页面包屑
|
||||||
|
if (items.length === 0) {
|
||||||
|
items.push({
|
||||||
|
title: '首页',
|
||||||
|
path: '/',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
breadcrumbItems.value = items
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听路由变化
|
||||||
|
watch(
|
||||||
|
() => route.path,
|
||||||
|
() => {
|
||||||
|
generateBreadcrumb()
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.breadcrumb {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
|
|
||||||
<template>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
aria-hidden="true"
|
|
||||||
role="img"
|
|
||||||
class="iconify iconify--mdi"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
preserveAspectRatio="xMidYMid meet"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
|
|
||||||
fill="currentColor"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
|
|
@ -0,0 +1,332 @@
|
||||||
|
<template>
|
||||||
|
<a-layout class="admin-layout">
|
||||||
|
<!-- 侧边栏 -->
|
||||||
|
<a-layout-sider
|
||||||
|
v-model:collapsed="collapsed"
|
||||||
|
:trigger="null"
|
||||||
|
collapsible
|
||||||
|
class="admin-sider"
|
||||||
|
>
|
||||||
|
<div class="logo">
|
||||||
|
<img src="@/assets/logo.svg" alt="Logo" />
|
||||||
|
<h1 v-show="!collapsed">Admin System</h1>
|
||||||
|
</div>
|
||||||
|
<a-menu
|
||||||
|
v-model:selectedKeys="selectedKeys"
|
||||||
|
v-model:openKeys="openKeys"
|
||||||
|
mode="inline"
|
||||||
|
theme="dark"
|
||||||
|
>
|
||||||
|
<template v-for="menu in menus" :key="menu.key">
|
||||||
|
<!-- 有子菜单的情况 -->
|
||||||
|
<template v-if="menu.children && menu.children.length > 0">
|
||||||
|
<a-sub-menu :key="menu.key">
|
||||||
|
<template #title>
|
||||||
|
<span>
|
||||||
|
<component :is="menu.icon" />
|
||||||
|
<span>{{ menu.title }}</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<a-menu-item
|
||||||
|
v-for="child in menu.children"
|
||||||
|
:key="child.key"
|
||||||
|
@click="() => navigateTo(child.path)"
|
||||||
|
>
|
||||||
|
<component :is="child.icon" />
|
||||||
|
<span>{{ child.title }}</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-sub-menu>
|
||||||
|
</template>
|
||||||
|
<!-- 没有子菜单的情况 -->
|
||||||
|
<template v-else>
|
||||||
|
<a-menu-item :key="menu.key" @click="() => navigateTo(menu.path)">
|
||||||
|
<component :is="menu.icon" />
|
||||||
|
<span>{{ menu.title }}</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-menu>
|
||||||
|
</a-layout-sider>
|
||||||
|
|
||||||
|
<!-- 内容区域 -->
|
||||||
|
<a-layout class="admin-right-layout">
|
||||||
|
<!-- 头部 -->
|
||||||
|
<a-layout-header class="admin-header">
|
||||||
|
<div class="header-left">
|
||||||
|
<menu-unfold-outlined
|
||||||
|
v-if="collapsed"
|
||||||
|
class="trigger"
|
||||||
|
@click="() => (collapsed = !collapsed)"
|
||||||
|
/>
|
||||||
|
<menu-fold-outlined
|
||||||
|
v-else
|
||||||
|
class="trigger"
|
||||||
|
@click="() => (collapsed = !collapsed)"
|
||||||
|
/>
|
||||||
|
<breadcrumb />
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<a-dropdown>
|
||||||
|
<a class="user-dropdown" @click.prevent>
|
||||||
|
<a-avatar>
|
||||||
|
<template #icon><user-outlined /></template>
|
||||||
|
</a-avatar>
|
||||||
|
<span class="username">管理员</span>
|
||||||
|
</a>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu>
|
||||||
|
<a-menu-item key="profile">
|
||||||
|
<user-outlined />
|
||||||
|
个人中心
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="settings">
|
||||||
|
<setting-outlined />
|
||||||
|
个人设置
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-divider />
|
||||||
|
<a-menu-item key="logout" @click="handleLogout">
|
||||||
|
<logout-outlined />
|
||||||
|
退出登录
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</div>
|
||||||
|
</a-layout-header>
|
||||||
|
|
||||||
|
<!-- 内容 -->
|
||||||
|
<a-layout-content class="admin-content">
|
||||||
|
<router-view v-slot="{ Component }">
|
||||||
|
<transition name="fade" mode="out-in">
|
||||||
|
<component :is="Component" />
|
||||||
|
</transition>
|
||||||
|
</router-view>
|
||||||
|
</a-layout-content>
|
||||||
|
|
||||||
|
<!-- 页脚 -->
|
||||||
|
<a-layout-footer class="admin-footer">
|
||||||
|
Admin System ©{{ new Date().getFullYear() }} Created by Your Company
|
||||||
|
</a-layout-footer>
|
||||||
|
</a-layout>
|
||||||
|
</a-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted, watch } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import {
|
||||||
|
MenuUnfoldOutlined,
|
||||||
|
MenuFoldOutlined,
|
||||||
|
DashboardOutlined,
|
||||||
|
SettingOutlined,
|
||||||
|
UserOutlined,
|
||||||
|
TeamOutlined,
|
||||||
|
MenuOutlined,
|
||||||
|
LogoutOutlined,
|
||||||
|
} from '@ant-design/icons-vue'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
import Breadcrumb from '@/components/common/Breadcrumb.vue'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
// 侧边栏折叠状态
|
||||||
|
const collapsed = ref(false)
|
||||||
|
|
||||||
|
// 菜单选中状态
|
||||||
|
const selectedKeys = ref<string[]>([])
|
||||||
|
const openKeys = ref<string[]>([])
|
||||||
|
|
||||||
|
// 模拟菜单数据
|
||||||
|
const menus = reactive([
|
||||||
|
{
|
||||||
|
key: 'dashboard',
|
||||||
|
title: '仪表盘',
|
||||||
|
icon: DashboardOutlined,
|
||||||
|
path: '/dashboard',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system',
|
||||||
|
title: '系统管理',
|
||||||
|
icon: SettingOutlined,
|
||||||
|
path: '/system',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: 'user',
|
||||||
|
title: '用户管理',
|
||||||
|
icon: UserOutlined,
|
||||||
|
path: '/system/user',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'role',
|
||||||
|
title: '角色管理',
|
||||||
|
icon: TeamOutlined,
|
||||||
|
path: '/system/role',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'menu',
|
||||||
|
title: '菜单管理',
|
||||||
|
icon: MenuOutlined,
|
||||||
|
path: '/system/menu',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
// 根据当前路由设置选中的菜单项
|
||||||
|
const updateSelectedMenu = () => {
|
||||||
|
const paths = route.path.split('/')
|
||||||
|
const currentPath = paths[paths.length - 1]
|
||||||
|
|
||||||
|
// 设置选中的菜单项
|
||||||
|
selectedKeys.value = [currentPath]
|
||||||
|
|
||||||
|
// 设置展开的子菜单
|
||||||
|
if (paths.length > 2) {
|
||||||
|
openKeys.value = [paths[1]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导航到指定路径
|
||||||
|
const navigateTo = (path: string) => {
|
||||||
|
router.push(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理退出登录
|
||||||
|
const handleLogout = () => {
|
||||||
|
localStorage.removeItem('token')
|
||||||
|
message.success('已退出登录')
|
||||||
|
router.push('/login')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听路由变化
|
||||||
|
watch(
|
||||||
|
() => route.path,
|
||||||
|
() => {
|
||||||
|
updateSelectedMenu()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
updateSelectedMenu()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.admin-layout {
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-sider {
|
||||||
|
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
|
||||||
|
z-index: 10;
|
||||||
|
height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-right-layout {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 64px;
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo img {
|
||||||
|
height: 32px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo h1 {
|
||||||
|
color: white;
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-header {
|
||||||
|
background: #fff;
|
||||||
|
padding: 0 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger {
|
||||||
|
font-size: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.3s;
|
||||||
|
padding: 0 24px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger:hover {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-dropdown {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 12px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-dropdown:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.025);
|
||||||
|
}
|
||||||
|
|
||||||
|
.username {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-content {
|
||||||
|
margin: 24px;
|
||||||
|
padding: 24px;
|
||||||
|
background: #fff;
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
position: relative;
|
||||||
|
height: 0; /* 让flex: 1生效 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 16px 50px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 路由过渡动画 */
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
<template>
|
||||||
|
<a-layout-footer class="footer">
|
||||||
|
<div class="footer-content">
|
||||||
|
<div class="copyright">
|
||||||
|
Admin Template ©{{ currentYear }} Created by Your Company
|
||||||
|
</div>
|
||||||
|
<div class="links">
|
||||||
|
<a href="#">帮助</a>
|
||||||
|
<a href="#">隐私</a>
|
||||||
|
<a href="#">条款</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-layout-footer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
|
||||||
|
const currentYear = ref(new Date().getFullYear())
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
background: #f0f2f5;
|
||||||
|
padding: 16px 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright {
|
||||||
|
color: rgba(0, 0, 0, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.links {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links a {
|
||||||
|
color: rgba(0, 0, 0, 0.45);
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links a:hover {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<a-layout-header class="header">
|
||||||
|
<div class="header-left">
|
||||||
|
<menu-unfold-outlined
|
||||||
|
v-if="collapsed"
|
||||||
|
class="trigger"
|
||||||
|
@click="() => (collapsed = !collapsed)"
|
||||||
|
/>
|
||||||
|
<menu-fold-outlined
|
||||||
|
v-else
|
||||||
|
class="trigger"
|
||||||
|
@click="() => (collapsed = !collapsed)"
|
||||||
|
/>
|
||||||
|
<span class="logo">Admin Template</span>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<a-space>
|
||||||
|
<a-badge count="5">
|
||||||
|
<a-button type="text">
|
||||||
|
<template #icon><BellOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-badge>
|
||||||
|
<a-dropdown>
|
||||||
|
<a-button type="text">
|
||||||
|
<template #icon><UserOutlined /></template>
|
||||||
|
Admin User
|
||||||
|
</a-button>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu>
|
||||||
|
<a-menu-item key="profile">
|
||||||
|
<user-outlined />
|
||||||
|
个人信息
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="settings">
|
||||||
|
<setting-outlined />
|
||||||
|
设置
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-divider />
|
||||||
|
<a-menu-item key="logout">
|
||||||
|
<logout-outlined />
|
||||||
|
退出登录
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
</a-layout-header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
MenuFoldOutlined,
|
||||||
|
MenuUnfoldOutlined,
|
||||||
|
BellOutlined,
|
||||||
|
UserOutlined,
|
||||||
|
SettingOutlined,
|
||||||
|
LogoutOutlined,
|
||||||
|
} from '@ant-design/icons-vue'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
collapsed: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
defineEmits(['update:collapsed'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.header {
|
||||||
|
padding: 0;
|
||||||
|
background: #fff;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
padding-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger {
|
||||||
|
padding: 0 24px;
|
||||||
|
font-size: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger:hover {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(0, 0, 0, 0.85);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
<template>
|
||||||
|
<a-layout-content class="main-content">
|
||||||
|
<div class="content-header">
|
||||||
|
<a-breadcrumb>
|
||||||
|
<a-breadcrumb-item>首页</a-breadcrumb-item>
|
||||||
|
<a-breadcrumb-item>系统管理</a-breadcrumb-item>
|
||||||
|
<a-breadcrumb-item>用户管理</a-breadcrumb-item>
|
||||||
|
</a-breadcrumb>
|
||||||
|
<div class="content-title">
|
||||||
|
<h2>用户管理</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content-container">
|
||||||
|
<router-view v-slot="{ Component }">
|
||||||
|
<transition name="fade" mode="out-in">
|
||||||
|
<component :is="Component" />
|
||||||
|
</transition>
|
||||||
|
</router-view>
|
||||||
|
</div>
|
||||||
|
</a-layout-content>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 组件逻辑将在后续添加
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.main-content {
|
||||||
|
margin: 24px;
|
||||||
|
padding: 24px;
|
||||||
|
background: #fff;
|
||||||
|
min-height: 280px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-title {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-title h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: rgba(0, 0, 0, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-container {
|
||||||
|
min-height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 过渡动画 */
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
<template>
|
||||||
|
<a-layout-sider
|
||||||
|
v-model:collapsed="collapsed"
|
||||||
|
:trigger="null"
|
||||||
|
collapsible
|
||||||
|
class="side-menu"
|
||||||
|
>
|
||||||
|
<div class="logo-container">
|
||||||
|
<img src="@/assets/logo.svg" alt="Logo" class="logo" />
|
||||||
|
<h1 v-if="!collapsed" class="title">Admin System</h1>
|
||||||
|
</div>
|
||||||
|
<a-menu
|
||||||
|
v-model:selectedKeys="selectedKeys"
|
||||||
|
v-model:openKeys="openKeys"
|
||||||
|
mode="inline"
|
||||||
|
theme="dark"
|
||||||
|
>
|
||||||
|
<a-menu-item key="dashboard">
|
||||||
|
<template #icon>
|
||||||
|
<dashboard-outlined />
|
||||||
|
</template>
|
||||||
|
<span>仪表盘</span>
|
||||||
|
</a-menu-item>
|
||||||
|
|
||||||
|
<a-sub-menu key="system">
|
||||||
|
<template #icon>
|
||||||
|
<setting-outlined />
|
||||||
|
</template>
|
||||||
|
<template #title>系统管理</template>
|
||||||
|
<a-menu-item key="user">
|
||||||
|
<template #icon>
|
||||||
|
<user-outlined />
|
||||||
|
</template>
|
||||||
|
<span>用户管理</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="role">
|
||||||
|
<template #icon>
|
||||||
|
<team-outlined />
|
||||||
|
</template>
|
||||||
|
<span>角色管理</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="menu">
|
||||||
|
<template #icon>
|
||||||
|
<menu-outlined />
|
||||||
|
</template>
|
||||||
|
<span>菜单管理</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-sub-menu>
|
||||||
|
|
||||||
|
<a-sub-menu key="components">
|
||||||
|
<template #icon>
|
||||||
|
<appstore-outlined />
|
||||||
|
</template>
|
||||||
|
<template #title>组件示例</template>
|
||||||
|
<a-menu-item key="table">
|
||||||
|
<template #icon>
|
||||||
|
<table-outlined />
|
||||||
|
</template>
|
||||||
|
<span>表格</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="form">
|
||||||
|
<template #icon>
|
||||||
|
<form-outlined />
|
||||||
|
</template>
|
||||||
|
<span>表单</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="chart">
|
||||||
|
<template #icon>
|
||||||
|
<bar-chart-outlined />
|
||||||
|
</template>
|
||||||
|
<span>图表</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-sub-menu>
|
||||||
|
</a-menu>
|
||||||
|
</a-layout-sider>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import {
|
||||||
|
DashboardOutlined,
|
||||||
|
SettingOutlined,
|
||||||
|
UserOutlined,
|
||||||
|
TeamOutlined,
|
||||||
|
MenuOutlined,
|
||||||
|
AppstoreOutlined,
|
||||||
|
TableOutlined,
|
||||||
|
FormOutlined,
|
||||||
|
BarChartOutlined,
|
||||||
|
} from '@ant-design/icons-vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
collapsed: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
defineEmits(['update:collapsed'])
|
||||||
|
|
||||||
|
const selectedKeys = ref(['dashboard'])
|
||||||
|
const openKeys = ref(['system'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.side-menu {
|
||||||
|
height: 100vh;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-container {
|
||||||
|
height: 64px;
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 32px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: white;
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import './assets/main.css'
|
import 'ant-design-vue/dist/reset.css';
|
||||||
|
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
|
import Antd from 'ant-design-vue';
|
||||||
|
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
|
|
@ -10,5 +11,6 @@ const app = createApp(App)
|
||||||
|
|
||||||
app.use(createPinia())
|
app.use(createPinia())
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
app.use(Antd);
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,77 @@
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
import HomeView from '../views/HomeView.vue'
|
import AdminLayout from '@/components/layout/AdminLayout.vue';
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'login',
|
||||||
|
component: () => import('@/views/Login.vue'),
|
||||||
|
meta: { requiresAuth: false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: AdminLayout,
|
||||||
|
redirect: '/dashboard',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'dashboard',
|
||||||
|
name: 'dashboard',
|
||||||
|
component: () => import('@/views/Dashboard.vue'),
|
||||||
|
meta: { title: '仪表盘', icon: 'dashboard' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'system',
|
||||||
|
name: 'system',
|
||||||
|
redirect: '/system/user',
|
||||||
|
meta: { title: '系统管理', icon: 'setting' },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'user',
|
||||||
|
name: 'user',
|
||||||
|
component: () => import('@/views/system/UserManagement.vue'),
|
||||||
|
meta: { title: '用户管理', icon: 'user' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'role',
|
||||||
|
name: 'role',
|
||||||
|
component: () => import('@/views/system/RoleManagement.vue'),
|
||||||
|
meta: { title: '角色管理', icon: 'team' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'menu',
|
||||||
|
name: 'menu',
|
||||||
|
component: () => import('@/views/system/MenuManagement.vue'),
|
||||||
|
meta: { title: '菜单管理', icon: 'menu' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// 404页面
|
||||||
|
{
|
||||||
|
path: '/:pathMatch(.*)*',
|
||||||
|
name: 'not-found',
|
||||||
|
component: () => import('@/views/NotFound.vue')
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
routes: [
|
routes,
|
||||||
{
|
});
|
||||||
path: '/',
|
|
||||||
name: 'home',
|
|
||||||
component: HomeView,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/about',
|
|
||||||
name: 'about',
|
|
||||||
// route level code-splitting
|
|
||||||
// this generates a separate chunk (About.[hash].js) for this route
|
|
||||||
// which is lazy-loaded when the route is visited.
|
|
||||||
component: () => import('../views/AboutView.vue'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router
|
// 路由守卫
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
// 这里可以添加身份验证逻辑
|
||||||
|
// 例如:检查用户是否已登录,如果未登录且访问需要认证的页面,则重定向到登录页
|
||||||
|
const requiresAuth = to.matched.some(record => record.meta.requiresAuth !== false);
|
||||||
|
const isAuthenticated = localStorage.getItem('token'); // 简单的身份验证检查
|
||||||
|
|
||||||
|
if (requiresAuth && !isAuthenticated) {
|
||||||
|
next('/login');
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="about">
|
|
||||||
<h1>This is an about page</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.about {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -0,0 +1,538 @@
|
||||||
|
<template>
|
||||||
|
<div class="dashboard">
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<!-- 统计卡片 -->
|
||||||
|
<a-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6" class="card-col">
|
||||||
|
<a-card class="statistic-card">
|
||||||
|
<template #title>
|
||||||
|
<div class="card-title">
|
||||||
|
<team-outlined class="card-icon user-icon" />
|
||||||
|
<span>用户总数</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="statistic-value">{{ statistics.userCount }}</div>
|
||||||
|
<div class="statistic-footer">
|
||||||
|
<span class="trend-text up">
|
||||||
|
<arrow-up-outlined /> {{ statistics.userIncrease }}%
|
||||||
|
</span>
|
||||||
|
<span class="trend-time">较上周</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<a-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6" class="card-col">
|
||||||
|
<a-card class="statistic-card">
|
||||||
|
<template #title>
|
||||||
|
<div class="card-title">
|
||||||
|
<interaction-outlined class="card-icon visit-icon" />
|
||||||
|
<span>访问量</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="statistic-value">{{ statistics.visitCount }}</div>
|
||||||
|
<div class="statistic-footer">
|
||||||
|
<span class="trend-text down">
|
||||||
|
<arrow-down-outlined /> {{ statistics.visitDecrease }}%
|
||||||
|
</span>
|
||||||
|
<span class="trend-time">较昨日</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<a-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6" class="card-col">
|
||||||
|
<a-card class="statistic-card">
|
||||||
|
<template #title>
|
||||||
|
<div class="card-title">
|
||||||
|
<file-text-outlined class="card-icon order-icon" />
|
||||||
|
<span>订单数</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="statistic-value">{{ statistics.orderCount }}</div>
|
||||||
|
<div class="statistic-footer">
|
||||||
|
<span class="trend-text up">
|
||||||
|
<arrow-up-outlined /> {{ statistics.orderIncrease }}%
|
||||||
|
</span>
|
||||||
|
<span class="trend-time">较上月</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<a-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6" class="card-col">
|
||||||
|
<a-card class="statistic-card">
|
||||||
|
<template #title>
|
||||||
|
<div class="card-title">
|
||||||
|
<dollar-outlined class="card-icon revenue-icon" />
|
||||||
|
<span>收入</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="statistic-value">¥{{ statistics.revenue }}</div>
|
||||||
|
<div class="statistic-footer">
|
||||||
|
<span class="trend-text up">
|
||||||
|
<arrow-up-outlined /> {{ statistics.revenueIncrease }}%
|
||||||
|
</span>
|
||||||
|
<span class="trend-time">较上月</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<a-row :gutter="24" style="margin-top: 24px">
|
||||||
|
<!-- 图表区域 -->
|
||||||
|
<a-col :xs="24" :sm="24" :md="24" :lg="16" :xl="16" class="chart-col">
|
||||||
|
<a-card title="访问量趋势" :bordered="false">
|
||||||
|
<div ref="visitChart" class="chart-container"></div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<a-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8" class="chart-col">
|
||||||
|
<a-card title="用户分布" :bordered="false">
|
||||||
|
<div ref="userChart" class="chart-container"></div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<a-row :gutter="24" style="margin-top: 24px">
|
||||||
|
<!-- 最近活动 -->
|
||||||
|
<a-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="activity-col">
|
||||||
|
<a-card title="最近活动" :bordered="false">
|
||||||
|
<a-list class="activity-list" :data-source="activities" :pagination="false">
|
||||||
|
<template #renderItem="{ item }">
|
||||||
|
<a-list-item>
|
||||||
|
<a-list-item-meta>
|
||||||
|
<template #avatar>
|
||||||
|
<a-avatar :style="{ backgroundColor: item.avatarColor }">
|
||||||
|
{{ item.avatar }}
|
||||||
|
</a-avatar>
|
||||||
|
</template>
|
||||||
|
<template #title>
|
||||||
|
<a href="#">{{ item.title }}</a>
|
||||||
|
</template>
|
||||||
|
<template #description>
|
||||||
|
<div>{{ item.description }}</div>
|
||||||
|
<div class="activity-time">{{ item.time }}</div>
|
||||||
|
</template>
|
||||||
|
</a-list-item-meta>
|
||||||
|
</a-list-item>
|
||||||
|
</template>
|
||||||
|
</a-list>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<!-- 快速操作 -->
|
||||||
|
<a-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="quick-col">
|
||||||
|
<a-card title="快速操作" :bordered="false">
|
||||||
|
<div class="quick-actions">
|
||||||
|
<a-button type="primary" @click="showMessage('添加用户')">
|
||||||
|
<template #icon><user-add-outlined /></template>
|
||||||
|
添加用户
|
||||||
|
</a-button>
|
||||||
|
<a-button @click="showMessage('发布公告')">
|
||||||
|
<template #icon><notification-outlined /></template>
|
||||||
|
发布公告
|
||||||
|
</a-button>
|
||||||
|
<a-button @click="showMessage('系统设置')">
|
||||||
|
<template #icon><setting-outlined /></template>
|
||||||
|
系统设置
|
||||||
|
</a-button>
|
||||||
|
<a-button @click="showMessage('数据备份')">
|
||||||
|
<template #icon><cloud-upload-outlined /></template>
|
||||||
|
数据备份
|
||||||
|
</a-button>
|
||||||
|
<a-button @click="showMessage('生成报表')">
|
||||||
|
<template #icon><file-pdf-outlined /></template>
|
||||||
|
生成报表
|
||||||
|
</a-button>
|
||||||
|
<a-button @click="showMessage('发送邮件')">
|
||||||
|
<template #icon><mail-outlined /></template>
|
||||||
|
发送邮件
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
import * as echarts from 'echarts/core'
|
||||||
|
import {
|
||||||
|
TitleComponent,
|
||||||
|
TooltipComponent,
|
||||||
|
LegendComponent,
|
||||||
|
GridComponent,
|
||||||
|
} from 'echarts/components'
|
||||||
|
import { LineChart, PieChart } from 'echarts/charts'
|
||||||
|
import { UniversalTransition } from 'echarts/features'
|
||||||
|
import { CanvasRenderer } from 'echarts/renderers'
|
||||||
|
import {
|
||||||
|
TeamOutlined,
|
||||||
|
InteractionOutlined,
|
||||||
|
FileTextOutlined,
|
||||||
|
DollarOutlined,
|
||||||
|
ArrowUpOutlined,
|
||||||
|
ArrowDownOutlined,
|
||||||
|
UserAddOutlined,
|
||||||
|
NotificationOutlined,
|
||||||
|
SettingOutlined,
|
||||||
|
CloudUploadOutlined,
|
||||||
|
FilePdfOutlined,
|
||||||
|
MailOutlined,
|
||||||
|
} from '@ant-design/icons-vue'
|
||||||
|
|
||||||
|
// 注册 ECharts 必需的组件
|
||||||
|
echarts.use([
|
||||||
|
TitleComponent,
|
||||||
|
TooltipComponent,
|
||||||
|
LegendComponent,
|
||||||
|
GridComponent,
|
||||||
|
LineChart,
|
||||||
|
PieChart,
|
||||||
|
CanvasRenderer,
|
||||||
|
UniversalTransition,
|
||||||
|
])
|
||||||
|
|
||||||
|
// 统计数据
|
||||||
|
const statistics = reactive({
|
||||||
|
userCount: '1,286',
|
||||||
|
userIncrease: 12.5,
|
||||||
|
visitCount: '8,846',
|
||||||
|
visitDecrease: 2.3,
|
||||||
|
orderCount: '1,286',
|
||||||
|
orderIncrease: 3.8,
|
||||||
|
revenue: '23,648',
|
||||||
|
revenueIncrease: 8.4,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 活动列表数据
|
||||||
|
const activities = ref([
|
||||||
|
{
|
||||||
|
avatar: '张',
|
||||||
|
avatarColor: '#1890ff',
|
||||||
|
title: '张三 添加了新用户',
|
||||||
|
description: '添加了用户"李四"到系统',
|
||||||
|
time: '刚刚',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
avatar: '王',
|
||||||
|
avatarColor: '#52c41a',
|
||||||
|
title: '王五 更新了系统设置',
|
||||||
|
description: '修改了系统邮件配置',
|
||||||
|
time: '2小时前',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
avatar: '赵',
|
||||||
|
avatarColor: '#faad14',
|
||||||
|
title: '赵六 发布了新公告',
|
||||||
|
description: '关于系统维护的公告',
|
||||||
|
time: '5小时前',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
avatar: '钱',
|
||||||
|
avatarColor: '#f5222d',
|
||||||
|
title: '钱七 删除了用户',
|
||||||
|
description: '删除了用户"测试账号"',
|
||||||
|
time: '1天前',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
// 图表引用
|
||||||
|
const visitChart = ref<HTMLElement | null>(null)
|
||||||
|
const userChart = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
|
// 图表实例
|
||||||
|
let visitChartInstance: echarts.ECharts | null = null
|
||||||
|
let userChartInstance: echarts.ECharts | null = null
|
||||||
|
|
||||||
|
// 初始化访问量趋势图表
|
||||||
|
const initVisitChart = () => {
|
||||||
|
if (visitChart.value) {
|
||||||
|
visitChartInstance = echarts.init(visitChart.value)
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: ['访问量', '用户量'],
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '3%',
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '访问量',
|
||||||
|
type: 'line',
|
||||||
|
data: [120, 132, 101, 134, 90, 230, 210],
|
||||||
|
smooth: true,
|
||||||
|
lineStyle: {
|
||||||
|
width: 3,
|
||||||
|
color: '#1890ff',
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(24,144,255,0.3)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(24,144,255,0)',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '用户量',
|
||||||
|
type: 'line',
|
||||||
|
data: [220, 182, 191, 234, 290, 330, 310],
|
||||||
|
smooth: true,
|
||||||
|
lineStyle: {
|
||||||
|
width: 3,
|
||||||
|
color: '#52c41a',
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(82,196,26,0.3)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(82,196,26,0)',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
visitChartInstance.setOption(option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化用户分布图表
|
||||||
|
const initUserChart = () => {
|
||||||
|
if (userChart.value) {
|
||||||
|
userChartInstance = echarts.init(userChart.value)
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{a} <br/>{b}: {c} ({d}%)',
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
orient: 'vertical',
|
||||||
|
right: 10,
|
||||||
|
top: 'center',
|
||||||
|
data: ['华东', '华南', '华北', '西南', '其他'],
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '用户分布',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['50%', '70%'],
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 10,
|
||||||
|
borderColor: '#fff',
|
||||||
|
borderWidth: 2,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
position: 'center',
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
fontSize: '18',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
data: [
|
||||||
|
{ value: 335, name: '华东' },
|
||||||
|
{ value: 310, name: '华南' },
|
||||||
|
{ value: 234, name: '华北' },
|
||||||
|
{ value: 135, name: '西南' },
|
||||||
|
{ value: 154, name: '其他' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
userChartInstance.setOption(option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理窗口大小变化
|
||||||
|
const handleResize = () => {
|
||||||
|
visitChartInstance?.resize()
|
||||||
|
userChartInstance?.resize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示消息
|
||||||
|
const showMessage = (action: string) => {
|
||||||
|
message.success(`你点击了"${action}"按钮`)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始化图表
|
||||||
|
initVisitChart()
|
||||||
|
initUserChart()
|
||||||
|
|
||||||
|
// 添加窗口大小变化监听
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 移除窗口大小变化监听
|
||||||
|
window.removeEventListener('resize', handleResize)
|
||||||
|
|
||||||
|
// 销毁图表实例
|
||||||
|
visitChartInstance?.dispose()
|
||||||
|
userChartInstance?.dispose()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dashboard {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-col {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statistic-card {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-icon {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visit-icon {
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-icon {
|
||||||
|
color: #faad14;
|
||||||
|
}
|
||||||
|
|
||||||
|
.revenue-icon {
|
||||||
|
color: #f5222d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statistic-value {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statistic-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(0, 0, 0, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trend-text {
|
||||||
|
margin-right: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trend-text.up {
|
||||||
|
color: #f5222d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trend-text.down {
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-col {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
height: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-col,
|
||||||
|
.quick-col {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-time {
|
||||||
|
color: rgba(0, 0, 0, 0.45);
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.quick-actions {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.quick-actions {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import TheWelcome from '../components/TheWelcome.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<main>
|
|
||||||
<TheWelcome />
|
|
||||||
</main>
|
|
||||||
</template>
|
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
<template>
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="login-form-wrapper">
|
||||||
|
<div class="login-header">
|
||||||
|
<img src="@/assets/logo.svg" alt="Logo" class="login-logo" />
|
||||||
|
<h1 class="login-title">Admin System</h1>
|
||||||
|
</div>
|
||||||
|
<a-form
|
||||||
|
:model="formState"
|
||||||
|
name="login"
|
||||||
|
class="login-form"
|
||||||
|
@finish="handleSubmit"
|
||||||
|
@finishFailed="handleFinishFailed"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
name="username"
|
||||||
|
:rules="[{ required: true, message: '请输入用户名!' }]"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="formState.username" size="large" placeholder="用户名">
|
||||||
|
<template #prefix>
|
||||||
|
<user-outlined class="site-form-item-icon" />
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
name="password"
|
||||||
|
:rules="[{ required: true, message: '请输入密码!' }]"
|
||||||
|
>
|
||||||
|
<a-input-password v-model:value="formState.password" size="large" placeholder="密码">
|
||||||
|
<template #prefix>
|
||||||
|
<lock-outlined class="site-form-item-icon" />
|
||||||
|
</template>
|
||||||
|
</a-input-password>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<a-row :gutter="8">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-checkbox v-model:checked="formState.remember">记住我</a-checkbox>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12" style="text-align: right">
|
||||||
|
<a class="login-form-forgot" href="">忘记密码</a>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
html-type="submit"
|
||||||
|
class="login-form-button"
|
||||||
|
size="large"
|
||||||
|
:loading="loading"
|
||||||
|
>
|
||||||
|
登录
|
||||||
|
</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
interface FormState {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
remember: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const formState = reactive<FormState>({
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
remember: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSubmit = (values: FormState) => {
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
// 模拟登录请求
|
||||||
|
setTimeout(() => {
|
||||||
|
if (values.username === 'admin' && values.password === 'admin') {
|
||||||
|
message.success('登录成功')
|
||||||
|
localStorage.setItem('token', 'demo-token')
|
||||||
|
router.push('/dashboard')
|
||||||
|
} else {
|
||||||
|
message.error('用户名或密码错误')
|
||||||
|
}
|
||||||
|
loading.value = false
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFinishFailed = (errorInfo: any) => {
|
||||||
|
console.log('Failed:', errorInfo)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.login-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
background: #f0f2f5;
|
||||||
|
background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center 110px;
|
||||||
|
background-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-wrapper {
|
||||||
|
width: 368px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-logo {
|
||||||
|
height: 44px;
|
||||||
|
margin-right: 8px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title {
|
||||||
|
font-size: 33px;
|
||||||
|
color: rgba(0, 0, 0, 0.85);
|
||||||
|
font-weight: 600;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
max-width: 368px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-forgot {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<template>
|
||||||
|
<div class="not-found">
|
||||||
|
<a-result
|
||||||
|
status="404"
|
||||||
|
title="404"
|
||||||
|
sub-title="抱歉,您访问的页面不存在。"
|
||||||
|
>
|
||||||
|
<template #extra>
|
||||||
|
<a-button type="primary" @click="goHome">返回首页</a-button>
|
||||||
|
</template>
|
||||||
|
</a-result>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const goHome = () => {
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.not-found {
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,389 @@
|
||||||
|
<template>
|
||||||
|
<div class="menu-management">
|
||||||
|
<div class="table-operations">
|
||||||
|
<a-space>
|
||||||
|
<a-button type="primary" @click="showAddMenuModal(null)">
|
||||||
|
<template #icon><plus-outlined /></template>
|
||||||
|
新增菜单
|
||||||
|
</a-button>
|
||||||
|
<a-button @click="expandAll">
|
||||||
|
<template #icon><folder-open-outlined /></template>
|
||||||
|
展开全部
|
||||||
|
</a-button>
|
||||||
|
<a-button @click="collapseAll">
|
||||||
|
<template #icon><folder-outlined /></template>
|
||||||
|
折叠全部
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="menus"
|
||||||
|
:loading="loading"
|
||||||
|
:pagination="false"
|
||||||
|
:expandable="{
|
||||||
|
defaultExpandAllRows: true,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'icon'">
|
||||||
|
<component :is="record.icon" />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'type'">
|
||||||
|
<a-tag :color="record.type === 1 ? 'blue' : record.type === 2 ? 'green' : 'orange'">
|
||||||
|
{{ record.type === 1 ? '目录' : record.type === 2 ? '菜单' : '按钮' }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'status'">
|
||||||
|
<a-tag :color="record.status === 1 ? 'success' : 'error'">
|
||||||
|
{{ record.status === 1 ? '启用' : '禁用' }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'action'">
|
||||||
|
<a-space>
|
||||||
|
<a @click="showAddMenuModal(record)" v-if="record.type !== 3">添加子项</a>
|
||||||
|
<a-divider type="vertical" v-if="record.type !== 3" />
|
||||||
|
<a @click="showEditMenuModal(record)">编辑</a>
|
||||||
|
<a-divider type="vertical" />
|
||||||
|
<a-popconfirm
|
||||||
|
title="确定要删除此菜单吗?"
|
||||||
|
ok-text="确定"
|
||||||
|
cancel-text="取消"
|
||||||
|
@confirm="handleDelete(record)"
|
||||||
|
>
|
||||||
|
<a class="danger-link">删除</a>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
|
||||||
|
<!-- 菜单表单模态框 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="menuModalVisible"
|
||||||
|
:title="modalTitle"
|
||||||
|
@ok="handleMenuModalOk"
|
||||||
|
@cancel="handleMenuModalCancel"
|
||||||
|
:confirmLoading="modalLoading"
|
||||||
|
width="700px"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
ref="menuForm"
|
||||||
|
:model="menuForm"
|
||||||
|
:rules="rules"
|
||||||
|
:label-col="{ span: 6 }"
|
||||||
|
:wrapper-col="{ span: 16 }"
|
||||||
|
>
|
||||||
|
<a-form-item label="上级菜单">
|
||||||
|
<a-tree-select
|
||||||
|
v-model:value="menuForm.parentId"
|
||||||
|
:tree-data="menuTreeData"
|
||||||
|
:field-names="{ children: 'children', label: 'title', value: 'key' }"
|
||||||
|
placeholder="请选择上级菜单"
|
||||||
|
allow-clear
|
||||||
|
tree-default-expand-all
|
||||||
|
:disabled="!!parentMenu"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="菜单类型" name="type">
|
||||||
|
<a-radio-group v-model:value="menuForm.type">
|
||||||
|
<a-radio :value="1">目录</a-radio>
|
||||||
|
<a-radio :value="2">菜单</a-radio>
|
||||||
|
<a-radio :value="3">按钮</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="菜单名称" name="title">
|
||||||
|
<a-input v-model:value="menuForm.title" placeholder="请输入菜单名称" />
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="menuForm.type !== 3">
|
||||||
|
<a-form-item label="图标" name="icon">
|
||||||
|
<a-input v-model:value="menuForm.icon" placeholder="请输入图标名称" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="路由地址" name="path" v-if="menuForm.type === 2">
|
||||||
|
<a-input v-model:value="menuForm.path" placeholder="请输入路由地址" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="组件路径" name="component" v-if="menuForm.type === 2">
|
||||||
|
<a-input v-model:value="menuForm.component" placeholder="请输入组件路径" />
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-form-item label="权限标识" name="permission">
|
||||||
|
<a-input v-model:value="menuForm.permission" placeholder="请输入权限标识" />
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<a-form-item label="排序" name="sort">
|
||||||
|
<a-input-number v-model:value="menuForm.sort" :min="0" style="width: 100%" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="状态" name="status">
|
||||||
|
<a-switch v-model:checked="menuForm.status" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed } from 'vue'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
import {
|
||||||
|
PlusOutlined,
|
||||||
|
FolderOpenOutlined,
|
||||||
|
FolderOutlined,
|
||||||
|
DashboardOutlined,
|
||||||
|
SettingOutlined,
|
||||||
|
TeamOutlined,
|
||||||
|
MenuOutlined,
|
||||||
|
} from '@ant-design/icons-vue'
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '菜单名称',
|
||||||
|
dataIndex: 'title',
|
||||||
|
key: 'title',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '图标',
|
||||||
|
dataIndex: 'icon',
|
||||||
|
key: 'icon',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '类型',
|
||||||
|
dataIndex: 'type',
|
||||||
|
key: 'type',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '权限标识',
|
||||||
|
dataIndex: 'permission',
|
||||||
|
key: 'permission',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '路由地址',
|
||||||
|
dataIndex: 'path',
|
||||||
|
key: 'path',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '排序',
|
||||||
|
dataIndex: 'sort',
|
||||||
|
key: 'sort',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
// 模拟菜单数据
|
||||||
|
const menus = ref([
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
title: '仪表盘',
|
||||||
|
icon: DashboardOutlined,
|
||||||
|
type: 2,
|
||||||
|
permission: 'dashboard',
|
||||||
|
path: '/dashboard',
|
||||||
|
component: '/views/Dashboard',
|
||||||
|
sort: 1,
|
||||||
|
status: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
title: '系统管理',
|
||||||
|
icon: SettingOutlined,
|
||||||
|
type: 1,
|
||||||
|
permission: 'system',
|
||||||
|
path: '/system',
|
||||||
|
sort: 2,
|
||||||
|
status: 1,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: '2-1',
|
||||||
|
title: '用户管理',
|
||||||
|
icon: TeamOutlined,
|
||||||
|
type: 2,
|
||||||
|
permission: 'system:user',
|
||||||
|
path: '/system/user',
|
||||||
|
component: '/views/system/UserManagement',
|
||||||
|
sort: 1,
|
||||||
|
status: 1,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: '2-1-1',
|
||||||
|
title: '查看用户',
|
||||||
|
type: 3,
|
||||||
|
permission: 'system:user:view',
|
||||||
|
sort: 1,
|
||||||
|
status: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2-1-2',
|
||||||
|
title: '新增用户',
|
||||||
|
type: 3,
|
||||||
|
permission: 'system:user:add',
|
||||||
|
sort: 2,
|
||||||
|
status: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2-2',
|
||||||
|
title: '菜单管理',
|
||||||
|
icon: MenuOutlined,
|
||||||
|
type: 2,
|
||||||
|
permission: 'system:menu',
|
||||||
|
path: '/system/menu',
|
||||||
|
component: '/views/system/MenuManagement',
|
||||||
|
sort: 2,
|
||||||
|
status: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
// 表格加载状态
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 菜单表单模态框配置
|
||||||
|
const menuModalVisible = ref(false)
|
||||||
|
const modalLoading = ref(false)
|
||||||
|
const modalTitle = ref('新增菜单')
|
||||||
|
const parentMenu = ref<any>(null)
|
||||||
|
|
||||||
|
// 菜单表单
|
||||||
|
const menuForm = reactive({
|
||||||
|
id: undefined,
|
||||||
|
parentId: undefined,
|
||||||
|
type: 1,
|
||||||
|
title: '',
|
||||||
|
icon: '',
|
||||||
|
path: '',
|
||||||
|
component: '',
|
||||||
|
permission: '',
|
||||||
|
sort: 0,
|
||||||
|
status: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const rules = {
|
||||||
|
title: [{ required: true, message: '请输入菜单名称' }],
|
||||||
|
type: [{ required: true, message: '请选择菜单类型' }],
|
||||||
|
permission: [{ required: true, message: '请输入权限标识', trigger: 'blur' }],
|
||||||
|
path: [{ required: true, message: '请输入路由地址', trigger: 'blur' }],
|
||||||
|
sort: [{ required: true, message: '请输入排序' }],
|
||||||
|
}
|
||||||
|
|
||||||
|
// 菜单树数据(用于选择上级菜单)
|
||||||
|
const menuTreeData = computed(() => {
|
||||||
|
const transform = (items: any[]): any[] => {
|
||||||
|
return items.map(item => ({
|
||||||
|
key: item.key,
|
||||||
|
title: item.title,
|
||||||
|
value: item.key,
|
||||||
|
disabled: item.type === 3,
|
||||||
|
children: item.children ? transform(item.children) : undefined,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return [{ key: '0', title: '顶级菜单', value: '0' }, ...transform(menus.value)]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 展开/折叠所有
|
||||||
|
const expandAll = () => {
|
||||||
|
// 实际项目中需要处理展开状态
|
||||||
|
message.success('已展开全部')
|
||||||
|
}
|
||||||
|
|
||||||
|
const collapseAll = () => {
|
||||||
|
// 实际项目中需要处理折叠状态
|
||||||
|
message.success('已折叠全部')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示新增菜单模态框
|
||||||
|
const showAddMenuModal = (record: any) => {
|
||||||
|
modalTitle.value = '新增菜单'
|
||||||
|
parentMenu.value = record
|
||||||
|
Object.assign(menuForm, {
|
||||||
|
id: undefined,
|
||||||
|
parentId: record ? record.key : '0',
|
||||||
|
type: record ? (record.type === 1 ? 2 : 3) : 1,
|
||||||
|
title: '',
|
||||||
|
icon: '',
|
||||||
|
path: '',
|
||||||
|
component: '',
|
||||||
|
permission: '',
|
||||||
|
sort: 0,
|
||||||
|
status: true,
|
||||||
|
})
|
||||||
|
menuModalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示编辑菜单模态框
|
||||||
|
const showEditMenuModal = (record: any) => {
|
||||||
|
modalTitle.value = '编辑菜单'
|
||||||
|
parentMenu.value = null
|
||||||
|
Object.assign(menuForm, {
|
||||||
|
id: record.key,
|
||||||
|
parentId: record.parentId || '0',
|
||||||
|
type: record.type,
|
||||||
|
title: record.title,
|
||||||
|
icon: record.icon,
|
||||||
|
path: record.path,
|
||||||
|
component: record.component,
|
||||||
|
permission: record.permission,
|
||||||
|
sort: record.sort,
|
||||||
|
status: record.status === 1,
|
||||||
|
})
|
||||||
|
menuModalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理菜单模态框确认
|
||||||
|
const handleMenuModalOk = () => {
|
||||||
|
modalLoading.value = true
|
||||||
|
// 模拟提交
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success(menuForm.id ? '编辑成功' : '添加成功')
|
||||||
|
menuModalVisible.value = false
|
||||||
|
modalLoading.value = false
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理菜单模态框取消
|
||||||
|
const handleMenuModalCancel = () => {
|
||||||
|
menuModalVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理删除菜单
|
||||||
|
const handleDelete = (record: any) => {
|
||||||
|
// 模拟删除
|
||||||
|
message.success('删除成功')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.menu-management {
|
||||||
|
padding: 24px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-operations {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-link {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-link:hover {
|
||||||
|
color: #ff7875;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,390 @@
|
||||||
|
<template>
|
||||||
|
<div class="role-management">
|
||||||
|
<div class="table-operations">
|
||||||
|
<a-space>
|
||||||
|
<a-button type="primary" @click="showAddRoleModal">
|
||||||
|
<template #icon><plus-outlined /></template>
|
||||||
|
新增角色
|
||||||
|
</a-button>
|
||||||
|
<a-button @click="handleBatchDelete" :disabled="!selectedRowKeys.length">
|
||||||
|
<template #icon><delete-outlined /></template>
|
||||||
|
批量删除
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="roles"
|
||||||
|
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
|
||||||
|
:loading="loading"
|
||||||
|
:pagination="pagination"
|
||||||
|
@change="handleTableChange"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'status'">
|
||||||
|
<a-tag :color="record.status === 1 ? 'success' : 'error'">
|
||||||
|
{{ record.status === 1 ? '启用' : '禁用' }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'action'">
|
||||||
|
<a-space>
|
||||||
|
<a @click="showEditRoleModal(record)">编辑</a>
|
||||||
|
<a-divider type="vertical" />
|
||||||
|
<a @click="showPermissionModal(record)">权限</a>
|
||||||
|
<a-divider type="vertical" />
|
||||||
|
<a-popconfirm
|
||||||
|
title="确定要删除此角色吗?"
|
||||||
|
ok-text="确定"
|
||||||
|
cancel-text="取消"
|
||||||
|
@confirm="handleDelete(record)"
|
||||||
|
>
|
||||||
|
<a class="danger-link">删除</a>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
|
||||||
|
<!-- 角色表单模态框 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="roleModalVisible"
|
||||||
|
:title="modalTitle"
|
||||||
|
@ok="handleRoleModalOk"
|
||||||
|
@cancel="handleRoleModalCancel"
|
||||||
|
:confirmLoading="modalLoading"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
ref="roleForm"
|
||||||
|
:model="roleForm"
|
||||||
|
:rules="rules"
|
||||||
|
:label-col="{ span: 6 }"
|
||||||
|
:wrapper-col="{ span: 16 }"
|
||||||
|
>
|
||||||
|
<a-form-item label="角色名称" name="name">
|
||||||
|
<a-input v-model:value="roleForm.name" placeholder="请输入角色名称" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="角色编码" name="code">
|
||||||
|
<a-input v-model:value="roleForm.code" placeholder="请输入角色编码" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="排序" name="sort">
|
||||||
|
<a-input-number v-model:value="roleForm.sort" :min="0" style="width: 100%" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="状态" name="status">
|
||||||
|
<a-switch v-model:checked="roleForm.status" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="备注" name="remark">
|
||||||
|
<a-textarea v-model:value="roleForm.remark" placeholder="请输入备注" :rows="4" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<!-- 权限分配模态框 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="permissionModalVisible"
|
||||||
|
title="分配权限"
|
||||||
|
@ok="handlePermissionModalOk"
|
||||||
|
@cancel="handlePermissionModalCancel"
|
||||||
|
:confirmLoading="permissionLoading"
|
||||||
|
width="600px"
|
||||||
|
>
|
||||||
|
<div v-if="currentRole">
|
||||||
|
<p>正在为角色 <strong>{{ currentRole.name }}</strong> 分配权限</p>
|
||||||
|
<a-tree
|
||||||
|
v-model:checkedKeys="checkedKeys"
|
||||||
|
:treeData="permissionTree"
|
||||||
|
checkable
|
||||||
|
:defaultExpandAll="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '角色名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '角色编码',
|
||||||
|
dataIndex: 'code',
|
||||||
|
key: 'code',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '排序',
|
||||||
|
dataIndex: 'sort',
|
||||||
|
key: 'sort',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
dataIndex: 'createTime',
|
||||||
|
key: 'createTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
// 模拟角色数据
|
||||||
|
const roles = ref([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '超级管理员',
|
||||||
|
code: 'ADMIN',
|
||||||
|
sort: 1,
|
||||||
|
status: 1,
|
||||||
|
remark: '系统最高权限',
|
||||||
|
createTime: '2023-01-01 12:00:00',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '普通用户',
|
||||||
|
code: 'USER',
|
||||||
|
sort: 2,
|
||||||
|
status: 1,
|
||||||
|
remark: '普通用户权限',
|
||||||
|
createTime: '2023-01-02 12:00:00',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '访客',
|
||||||
|
code: 'GUEST',
|
||||||
|
sort: 3,
|
||||||
|
status: 1,
|
||||||
|
remark: '访客权限',
|
||||||
|
createTime: '2023-01-03 12:00:00',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
// 表格加载状态
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 分页配置
|
||||||
|
const pagination = reactive({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 选中行配置
|
||||||
|
const selectedRowKeys = ref<number[]>([])
|
||||||
|
|
||||||
|
// 角色表单模态框配置
|
||||||
|
const roleModalVisible = ref(false)
|
||||||
|
const modalLoading = ref(false)
|
||||||
|
const modalTitle = ref('新增角色')
|
||||||
|
|
||||||
|
// 权限分配模态框配置
|
||||||
|
const permissionModalVisible = ref(false)
|
||||||
|
const permissionLoading = ref(false)
|
||||||
|
const currentRole = ref<any>(null)
|
||||||
|
const checkedKeys = ref<string[]>([])
|
||||||
|
|
||||||
|
// 角色表单
|
||||||
|
const roleForm = reactive({
|
||||||
|
id: undefined,
|
||||||
|
name: '',
|
||||||
|
code: '',
|
||||||
|
sort: 0,
|
||||||
|
status: true,
|
||||||
|
remark: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const rules = {
|
||||||
|
name: [{ required: true, message: '请输入角色名称' }],
|
||||||
|
code: [{ required: true, message: '请输入角色编码' }],
|
||||||
|
sort: [{ required: true, message: '请输入排序' }],
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟权限树数据
|
||||||
|
const permissionTree = [
|
||||||
|
{
|
||||||
|
title: '系统管理',
|
||||||
|
key: 'system',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: '用户管理',
|
||||||
|
key: 'user',
|
||||||
|
children: [
|
||||||
|
{ title: '查看用户', key: 'user:view' },
|
||||||
|
{ title: '新增用户', key: 'user:add' },
|
||||||
|
{ title: '编辑用户', key: 'user:edit' },
|
||||||
|
{ title: '删除用户', key: 'user:delete' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '角色管理',
|
||||||
|
key: 'role',
|
||||||
|
children: [
|
||||||
|
{ title: '查看角色', key: 'role:view' },
|
||||||
|
{ title: '新增角色', key: 'role:add' },
|
||||||
|
{ title: '编辑角色', key: 'role:edit' },
|
||||||
|
{ title: '删除角色', key: 'role:delete' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '菜单管理',
|
||||||
|
key: 'menu',
|
||||||
|
children: [
|
||||||
|
{ title: '查看菜单', key: 'menu:view' },
|
||||||
|
{ title: '新增菜单', key: 'menu:add' },
|
||||||
|
{ title: '编辑菜单', key: 'menu:edit' },
|
||||||
|
{ title: '删除菜单', key: 'menu:delete' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '内容管理',
|
||||||
|
key: 'content',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: '文章管理',
|
||||||
|
key: 'article',
|
||||||
|
children: [
|
||||||
|
{ title: '查看文章', key: 'article:view' },
|
||||||
|
{ title: '新增文章', key: 'article:add' },
|
||||||
|
{ title: '编辑文章', key: 'article:edit' },
|
||||||
|
{ title: '删除文章', key: 'article:delete' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
// 表格选择回调
|
||||||
|
const onSelectChange = (keys: number[]) => {
|
||||||
|
selectedRowKeys.value = keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格变化回调
|
||||||
|
const handleTableChange = (pag: any) => {
|
||||||
|
pagination.current = pag.current
|
||||||
|
pagination.pageSize = pag.pageSize
|
||||||
|
// 这里可以调用加载数据的方法
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示新增角色模态框
|
||||||
|
const showAddRoleModal = () => {
|
||||||
|
modalTitle.value = '新增角色'
|
||||||
|
Object.assign(roleForm, {
|
||||||
|
id: undefined,
|
||||||
|
name: '',
|
||||||
|
code: '',
|
||||||
|
sort: 0,
|
||||||
|
status: true,
|
||||||
|
remark: '',
|
||||||
|
})
|
||||||
|
roleModalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示编辑角色模态框
|
||||||
|
const showEditRoleModal = (record: any) => {
|
||||||
|
modalTitle.value = '编辑角色'
|
||||||
|
Object.assign(roleForm, {
|
||||||
|
...record,
|
||||||
|
status: record.status === 1,
|
||||||
|
})
|
||||||
|
roleModalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示权限分配模态框
|
||||||
|
const showPermissionModal = (record: any) => {
|
||||||
|
currentRole.value = record
|
||||||
|
// 模拟已有权限
|
||||||
|
if (record.id === 1) {
|
||||||
|
// 超级管理员拥有所有权限
|
||||||
|
checkedKeys.value = ['user:view', 'user:add', 'user:edit', 'user:delete',
|
||||||
|
'role:view', 'role:add', 'role:edit', 'role:delete',
|
||||||
|
'menu:view', 'menu:add', 'menu:edit', 'menu:delete',
|
||||||
|
'article:view', 'article:add', 'article:edit', 'article:delete']
|
||||||
|
} else if (record.id === 2) {
|
||||||
|
// 普通用户拥有部分权限
|
||||||
|
checkedKeys.value = ['user:view', 'role:view', 'menu:view', 'article:view', 'article:add', 'article:edit']
|
||||||
|
} else {
|
||||||
|
// 访客只有查看权限
|
||||||
|
checkedKeys.value = ['user:view', 'role:view', 'menu:view', 'article:view']
|
||||||
|
}
|
||||||
|
permissionModalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理角色模态框确认
|
||||||
|
const handleRoleModalOk = () => {
|
||||||
|
modalLoading.value = true
|
||||||
|
// 模拟提交
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success(roleForm.id ? '编辑成功' : '添加成功')
|
||||||
|
roleModalVisible.value = false
|
||||||
|
modalLoading.value = false
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理角色模态框取消
|
||||||
|
const handleRoleModalCancel = () => {
|
||||||
|
roleModalVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理权限模态框确认
|
||||||
|
const handlePermissionModalOk = () => {
|
||||||
|
permissionLoading.value = true
|
||||||
|
// 模拟提交
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success('权限分配成功')
|
||||||
|
permissionModalVisible.value = false
|
||||||
|
permissionLoading.value = false
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理权限模态框取消
|
||||||
|
const handlePermissionModalCancel = () => {
|
||||||
|
permissionModalVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理删除角色
|
||||||
|
const handleDelete = (record: any) => {
|
||||||
|
// 模拟删除
|
||||||
|
message.success('删除成功')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理批量删除
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
// 模拟批量删除
|
||||||
|
message.success(`删除了 ${selectedRowKeys.value.length} 条记录`)
|
||||||
|
selectedRowKeys.value = []
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.role-management {
|
||||||
|
padding: 24px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-operations {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-link {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-link:hover {
|
||||||
|
color: #ff7875;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,282 @@
|
||||||
|
<template>
|
||||||
|
<div class="user-management">
|
||||||
|
<div class="table-operations">
|
||||||
|
<a-space>
|
||||||
|
<a-button type="primary" @click="showAddUserModal">
|
||||||
|
<template #icon><plus-outlined /></template>
|
||||||
|
新增用户
|
||||||
|
</a-button>
|
||||||
|
<a-button @click="handleBatchDelete" :disabled="!selectedRowKeys.length">
|
||||||
|
<template #icon><delete-outlined /></template>
|
||||||
|
批量删除
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="users"
|
||||||
|
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
|
||||||
|
:loading="loading"
|
||||||
|
:pagination="pagination"
|
||||||
|
@change="handleTableChange"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'status'">
|
||||||
|
<a-tag :color="record.status === 1 ? 'success' : 'error'">
|
||||||
|
{{ record.status === 1 ? '启用' : '禁用' }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'action'">
|
||||||
|
<a-space>
|
||||||
|
<a @click="showEditUserModal(record)">编辑</a>
|
||||||
|
<a-divider type="vertical" />
|
||||||
|
<a-popconfirm
|
||||||
|
title="确定要删除此用户吗?"
|
||||||
|
ok-text="确定"
|
||||||
|
cancel-text="取消"
|
||||||
|
@confirm="handleDelete(record)"
|
||||||
|
>
|
||||||
|
<a class="danger-link">删除</a>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
|
||||||
|
<!-- 用户表单模态框 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="userModalVisible"
|
||||||
|
:title="modalTitle"
|
||||||
|
@ok="handleUserModalOk"
|
||||||
|
@cancel="handleUserModalCancel"
|
||||||
|
:confirmLoading="modalLoading"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
ref="userForm"
|
||||||
|
:model="userForm"
|
||||||
|
:rules="rules"
|
||||||
|
:label-col="{ span: 6 }"
|
||||||
|
:wrapper-col="{ span: 16 }"
|
||||||
|
>
|
||||||
|
<a-form-item label="用户名" name="username">
|
||||||
|
<a-input v-model:value="userForm.username" placeholder="请输入用户名" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
label="密码"
|
||||||
|
name="password"
|
||||||
|
:rules="[{ required: !userForm.id, message: '请输入密码' }]"
|
||||||
|
>
|
||||||
|
<a-input-password v-model:value="userForm.password" placeholder="请输入密码" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="昵称" name="nickname">
|
||||||
|
<a-input v-model:value="userForm.nickname" placeholder="请输入昵称" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="邮箱" name="email">
|
||||||
|
<a-input v-model:value="userForm.email" placeholder="请输入邮箱" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="手机号" name="phone">
|
||||||
|
<a-input v-model:value="userForm.phone" placeholder="请输入手机号" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="状态" name="status">
|
||||||
|
<a-switch v-model:checked="userForm.status" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '用户名',
|
||||||
|
dataIndex: 'username',
|
||||||
|
key: 'username',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '昵称',
|
||||||
|
dataIndex: 'nickname',
|
||||||
|
key: 'nickname',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '邮箱',
|
||||||
|
dataIndex: 'email',
|
||||||
|
key: 'email',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '手机号',
|
||||||
|
dataIndex: 'phone',
|
||||||
|
key: 'phone',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
dataIndex: 'createTime',
|
||||||
|
key: 'createTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
// 模拟用户数据
|
||||||
|
const users = ref([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
username: 'admin',
|
||||||
|
nickname: '管理员',
|
||||||
|
email: 'admin@example.com',
|
||||||
|
phone: '13800138000',
|
||||||
|
status: 1,
|
||||||
|
createTime: '2023-01-01 12:00:00',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
username: 'user',
|
||||||
|
nickname: '普通用户',
|
||||||
|
email: 'user@example.com',
|
||||||
|
phone: '13800138001',
|
||||||
|
status: 1,
|
||||||
|
createTime: '2023-01-02 12:00:00',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
// 表格加载状态
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 分页配置
|
||||||
|
const pagination = reactive({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 选中行配置
|
||||||
|
const selectedRowKeys = ref<number[]>([])
|
||||||
|
|
||||||
|
// 表单模态框配置
|
||||||
|
const userModalVisible = ref(false)
|
||||||
|
const modalLoading = ref(false)
|
||||||
|
const modalTitle = ref('新增用户')
|
||||||
|
|
||||||
|
// 用户表单
|
||||||
|
const userForm = reactive({
|
||||||
|
id: undefined,
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
nickname: '',
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
status: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const rules = {
|
||||||
|
username: [{ required: true, message: '请输入用户名' }],
|
||||||
|
nickname: [{ required: true, message: '请输入昵称' }],
|
||||||
|
email: [
|
||||||
|
{ required: true, message: '请输入邮箱' },
|
||||||
|
{ type: 'email', message: '请输入正确的邮箱格式' },
|
||||||
|
],
|
||||||
|
phone: [
|
||||||
|
{ required: true, message: '请输入手机号' },
|
||||||
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式' },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格选择回调
|
||||||
|
const onSelectChange = (keys: number[]) => {
|
||||||
|
selectedRowKeys.value = keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格变化回调
|
||||||
|
const handleTableChange = (pag: any) => {
|
||||||
|
pagination.current = pag.current
|
||||||
|
pagination.pageSize = pag.pageSize
|
||||||
|
// 这里可以调用加载数据的方法
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示新增用户模态框
|
||||||
|
const showAddUserModal = () => {
|
||||||
|
modalTitle.value = '新增用户'
|
||||||
|
Object.assign(userForm, {
|
||||||
|
id: undefined,
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
nickname: '',
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
status: true,
|
||||||
|
})
|
||||||
|
userModalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示编辑用户模态框
|
||||||
|
const showEditUserModal = (record: any) => {
|
||||||
|
modalTitle.value = '编辑用户'
|
||||||
|
Object.assign(userForm, {
|
||||||
|
...record,
|
||||||
|
password: '', // 编辑时不显示密码
|
||||||
|
})
|
||||||
|
userModalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理模态框确认
|
||||||
|
const handleUserModalOk = () => {
|
||||||
|
modalLoading.value = true
|
||||||
|
// 模拟提交
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success(userForm.id ? '编辑成功' : '添加成功')
|
||||||
|
userModalVisible.value = false
|
||||||
|
modalLoading.value = false
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理模态框取消
|
||||||
|
const handleUserModalCancel = () => {
|
||||||
|
userModalVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理删除用户
|
||||||
|
const handleDelete = (record: any) => {
|
||||||
|
// 模拟删除
|
||||||
|
message.success('删除成功')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理批量删除
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
// 模拟批量删除
|
||||||
|
message.success(`删除了 ${selectedRowKeys.value.length} 条记录`)
|
||||||
|
selectedRowKeys.value = []
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.user-management {
|
||||||
|
padding: 24px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-operations {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-link {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-link:hover {
|
||||||
|
color: #ff7875;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue