This commit is contained in:
parent
b694fb3461
commit
698ea070ae
210
pnpm-lock.yaml
210
pnpm-lock.yaml
|
|
@ -44,6 +44,9 @@ importers:
|
|||
pinia:
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3))
|
||||
qrcode:
|
||||
specifier: ^1.5.4
|
||||
version: 1.5.4
|
||||
vue:
|
||||
specifier: ^3.5.29
|
||||
version: 3.5.29(typescript@5.9.3)
|
||||
|
|
@ -1262,6 +1265,14 @@ packages:
|
|||
alien-signals@3.1.2:
|
||||
resolution: {integrity: sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==}
|
||||
|
||||
ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ansi-styles@6.2.3:
|
||||
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
|
||||
engines: {node: '>=12'}
|
||||
|
|
@ -1323,6 +1334,10 @@ packages:
|
|||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
camelcase@5.3.1:
|
||||
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
caniuse-lite@1.0.30001774:
|
||||
resolution: {integrity: sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==}
|
||||
|
||||
|
|
@ -1338,10 +1353,20 @@ packages:
|
|||
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
|
||||
cliui@6.0.0:
|
||||
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
|
||||
|
||||
codepage@1.15.0:
|
||||
resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
|
||||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
|
@ -1398,6 +1423,10 @@ packages:
|
|||
supports-color:
|
||||
optional: true
|
||||
|
||||
decamelize@1.2.0:
|
||||
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
deep-is@0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
|
||||
|
|
@ -1421,6 +1450,9 @@ packages:
|
|||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
dijkstrajs@1.0.3:
|
||||
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
|
||||
|
||||
dom7@3.0.0:
|
||||
resolution: {integrity: sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==}
|
||||
|
||||
|
|
@ -1436,6 +1468,9 @@ packages:
|
|||
peerDependencies:
|
||||
vue: ^3.3.0
|
||||
|
||||
emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
|
||||
entities@7.0.1:
|
||||
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
|
@ -1607,6 +1642,10 @@ packages:
|
|||
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
find-up@4.1.0:
|
||||
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
find-up@5.0.0:
|
||||
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -1647,6 +1686,10 @@ packages:
|
|||
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
get-caller-file@2.0.5:
|
||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||
engines: {node: 6.* || 8.* || >= 10.*}
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -1715,6 +1758,10 @@ packages:
|
|||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-fullwidth-code-point@3.0.0:
|
||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
is-glob@4.0.3:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -1803,6 +1850,10 @@ packages:
|
|||
resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
locate-path@5.0.0:
|
||||
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
locate-path@6.0.0:
|
||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -1966,14 +2017,26 @@ packages:
|
|||
oxlint-tsgolint:
|
||||
optional: true
|
||||
|
||||
p-limit@2.3.0:
|
||||
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
p-limit@3.1.0:
|
||||
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
p-locate@4.1.0:
|
||||
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
p-locate@5.0.0:
|
||||
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
p-try@2.2.0:
|
||||
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
path-browserify@1.0.1:
|
||||
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
||||
|
||||
|
|
@ -2025,6 +2088,10 @@ packages:
|
|||
pkg-types@2.3.0:
|
||||
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
|
||||
|
||||
pngjs@5.0.0:
|
||||
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
postcss-selector-parser@7.1.1:
|
||||
resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==}
|
||||
engines: {node: '>=4'}
|
||||
|
|
@ -2056,6 +2123,11 @@ packages:
|
|||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
qrcode@1.5.4:
|
||||
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
hasBin: true
|
||||
|
||||
quansync@0.2.11:
|
||||
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
|
||||
|
||||
|
|
@ -2074,6 +2146,13 @@ packages:
|
|||
resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
|
||||
require-directory@2.1.1:
|
||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
require-main-filename@2.0.0:
|
||||
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
|
||||
|
||||
reusify@1.1.0:
|
||||
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
|
||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||
|
|
@ -2113,6 +2192,9 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
set-blocking@2.0.0:
|
||||
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
@ -2156,6 +2238,14 @@ packages:
|
|||
ssr-window@3.0.0:
|
||||
resolution: {integrity: sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==}
|
||||
|
||||
string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
strip-literal@3.1.0:
|
||||
resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
|
||||
|
||||
|
|
@ -2382,6 +2472,9 @@ packages:
|
|||
webpack-virtual-modules@0.6.2:
|
||||
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
|
||||
|
||||
which-module@2.0.1:
|
||||
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
|
||||
|
||||
which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
|
@ -2407,6 +2500,10 @@ packages:
|
|||
resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
wrap-ansi@6.2.0:
|
||||
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
wsl-utils@0.1.0:
|
||||
resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -2420,6 +2517,9 @@ packages:
|
|||
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
y18n@4.0.3:
|
||||
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
|
||||
|
||||
yallist@3.1.1:
|
||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
||||
|
||||
|
|
@ -2428,6 +2528,14 @@ packages:
|
|||
engines: {node: '>= 14.6'}
|
||||
hasBin: true
|
||||
|
||||
yargs-parser@18.1.3:
|
||||
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
yargs@15.4.1:
|
||||
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
yocto-queue@0.1.0:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -3516,6 +3624,12 @@ snapshots:
|
|||
|
||||
alien-signals@3.1.2: {}
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
|
||||
ansi-styles@6.2.3: {}
|
||||
|
||||
ansis@4.2.0: {}
|
||||
|
|
@ -3575,6 +3689,8 @@ snapshots:
|
|||
es-errors: 1.3.0
|
||||
function-bind: 1.1.2
|
||||
|
||||
camelcase@5.3.1: {}
|
||||
|
||||
caniuse-lite@1.0.30001774: {}
|
||||
|
||||
cfb@1.2.2:
|
||||
|
|
@ -3590,8 +3706,20 @@ snapshots:
|
|||
dependencies:
|
||||
readdirp: 5.0.0
|
||||
|
||||
cliui@6.0.0:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 6.2.0
|
||||
|
||||
codepage@1.15.0: {}
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
|
|
@ -3633,6 +3761,8 @@ snapshots:
|
|||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
decamelize@1.2.0: {}
|
||||
|
||||
deep-is@0.1.4: {}
|
||||
|
||||
default-browser-id@5.0.1: {}
|
||||
|
|
@ -3649,6 +3779,8 @@ snapshots:
|
|||
detect-libc@2.1.2:
|
||||
optional: true
|
||||
|
||||
dijkstrajs@1.0.3: {}
|
||||
|
||||
dom7@3.0.0:
|
||||
dependencies:
|
||||
ssr-window: 3.0.0
|
||||
|
|
@ -3681,6 +3813,8 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
entities@7.0.1: {}
|
||||
|
||||
error-stack-parser-es@1.0.5: {}
|
||||
|
|
@ -3896,6 +4030,11 @@ snapshots:
|
|||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
|
||||
find-up@4.1.0:
|
||||
dependencies:
|
||||
locate-path: 5.0.0
|
||||
path-exists: 4.0.0
|
||||
|
||||
find-up@5.0.0:
|
||||
dependencies:
|
||||
locate-path: 6.0.0
|
||||
|
|
@ -3927,6 +4066,8 @@ snapshots:
|
|||
|
||||
gensync@1.0.0-beta.2: {}
|
||||
|
||||
get-caller-file@2.0.5: {}
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
|
|
@ -3987,6 +4128,8 @@ snapshots:
|
|||
|
||||
is-extglob@2.1.1: {}
|
||||
|
||||
is-fullwidth-code-point@3.0.0: {}
|
||||
|
||||
is-glob@4.0.3:
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
|
|
@ -4050,6 +4193,10 @@ snapshots:
|
|||
pkg-types: 2.3.0
|
||||
quansync: 0.2.11
|
||||
|
||||
locate-path@5.0.0:
|
||||
dependencies:
|
||||
p-locate: 4.1.0
|
||||
|
||||
locate-path@6.0.0:
|
||||
dependencies:
|
||||
p-locate: 5.0.0
|
||||
|
|
@ -4208,14 +4355,24 @@ snapshots:
|
|||
'@oxlint/binding-win32-ia32-msvc': 1.50.0
|
||||
'@oxlint/binding-win32-x64-msvc': 1.50.0
|
||||
|
||||
p-limit@2.3.0:
|
||||
dependencies:
|
||||
p-try: 2.2.0
|
||||
|
||||
p-limit@3.1.0:
|
||||
dependencies:
|
||||
yocto-queue: 0.1.0
|
||||
|
||||
p-locate@4.1.0:
|
||||
dependencies:
|
||||
p-limit: 2.3.0
|
||||
|
||||
p-locate@5.0.0:
|
||||
dependencies:
|
||||
p-limit: 3.1.0
|
||||
|
||||
p-try@2.2.0: {}
|
||||
|
||||
path-browserify@1.0.1: {}
|
||||
|
||||
path-exists@4.0.0: {}
|
||||
|
|
@ -4255,6 +4412,8 @@ snapshots:
|
|||
exsolve: 1.0.8
|
||||
pathe: 2.0.3
|
||||
|
||||
pngjs@5.0.0: {}
|
||||
|
||||
postcss-selector-parser@7.1.1:
|
||||
dependencies:
|
||||
cssesc: 3.0.0
|
||||
|
|
@ -4278,6 +4437,12 @@ snapshots:
|
|||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
qrcode@1.5.4:
|
||||
dependencies:
|
||||
dijkstrajs: 1.0.3
|
||||
pngjs: 5.0.0
|
||||
yargs: 15.4.1
|
||||
|
||||
quansync@0.2.11: {}
|
||||
|
||||
queue-microtask@1.2.3: {}
|
||||
|
|
@ -4291,6 +4456,10 @@ snapshots:
|
|||
|
||||
readdirp@5.0.0: {}
|
||||
|
||||
require-directory@2.1.1: {}
|
||||
|
||||
require-main-filename@2.0.0: {}
|
||||
|
||||
reusify@1.1.0: {}
|
||||
|
||||
rfdc@1.4.1: {}
|
||||
|
|
@ -4350,6 +4519,8 @@ snapshots:
|
|||
|
||||
semver@7.7.4: {}
|
||||
|
||||
set-blocking@2.0.0: {}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
shebang-regex: 3.0.0
|
||||
|
|
@ -4387,6 +4558,16 @@ snapshots:
|
|||
|
||||
ssr-window@3.0.0: {}
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
is-fullwidth-code-point: 3.0.0
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
|
||||
strip-literal@3.1.0:
|
||||
dependencies:
|
||||
js-tokens: 9.0.1
|
||||
|
|
@ -4635,6 +4816,8 @@ snapshots:
|
|||
|
||||
webpack-virtual-modules@0.6.2: {}
|
||||
|
||||
which-module@2.0.1: {}
|
||||
|
||||
which@2.0.2:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
|
@ -4651,6 +4834,12 @@ snapshots:
|
|||
|
||||
word@0.3.0: {}
|
||||
|
||||
wrap-ansi@6.2.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
wsl-utils@0.1.0:
|
||||
dependencies:
|
||||
is-wsl: 3.1.1
|
||||
|
|
@ -4667,8 +4856,29 @@ snapshots:
|
|||
|
||||
xml-name-validator@4.0.0: {}
|
||||
|
||||
y18n@4.0.3: {}
|
||||
|
||||
yallist@3.1.1: {}
|
||||
|
||||
yaml@2.8.2: {}
|
||||
|
||||
yargs-parser@18.1.3:
|
||||
dependencies:
|
||||
camelcase: 5.3.1
|
||||
decamelize: 1.2.0
|
||||
|
||||
yargs@15.4.1:
|
||||
dependencies:
|
||||
cliui: 6.0.0
|
||||
decamelize: 1.2.0
|
||||
find-up: 4.1.0
|
||||
get-caller-file: 2.0.5
|
||||
require-directory: 2.1.1
|
||||
require-main-filename: 2.0.0
|
||||
set-blocking: 2.0.0
|
||||
string-width: 4.2.3
|
||||
which-module: 2.0.1
|
||||
y18n: 4.0.3
|
||||
yargs-parser: 18.1.3
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
allowBuilds:
|
||||
'@parcel/watcher': set this to true or false
|
||||
es5-ext: set this to true or false
|
||||
esbuild: set this to true or false
|
||||
vue-demi: set this to true or false
|
||||
|
|
@ -30,7 +30,7 @@ export async function bindEmailApi(data: BindEmailParams): Promise<void> {
|
|||
|
||||
// 修改用户名
|
||||
export async function editUsernameApi(data: EditUsernameParams): Promise<void> {
|
||||
return request.post('/user/editUser', data);
|
||||
return request.post('/user/editUserName', data);
|
||||
}
|
||||
|
||||
// 验证码修改密码(统一接口)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
<template>
|
||||
<div class="breadcrumb-container">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item
|
||||
v-for="(item, index) in breadcrumbList"
|
||||
:key="index"
|
||||
:to="item.path"
|
||||
>
|
||||
{{ item.title }}
|
||||
</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useTabsStore } from '@/stores/modules/tabs'
|
||||
|
||||
interface BreadcrumbItem {
|
||||
title: string
|
||||
path: string
|
||||
}
|
||||
|
||||
const tabsStore = useTabsStore()
|
||||
|
||||
// 从 tabs store 获取当前激活标签页的面包屑
|
||||
const breadcrumbList = computed<BreadcrumbItem[]>(() => tabsStore.currentBreadcrumbs)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.breadcrumb-container {
|
||||
padding: 12px 16px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid var(--el-border-color-light);
|
||||
|
||||
:deep(.el-breadcrumb) {
|
||||
font-size: 14px;
|
||||
|
||||
.el-breadcrumb__item {
|
||||
.el-breadcrumb__inner {
|
||||
color: #606266;
|
||||
font-weight: normal;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
&.is-link {
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
.el-breadcrumb__inner {
|
||||
color: #909399;
|
||||
cursor: default;
|
||||
|
||||
&:hover {
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,116 +1,242 @@
|
|||
<template>
|
||||
<header>
|
||||
<div class="logo"></div>
|
||||
<header class="layout-header">
|
||||
<!-- 左侧:Logo 和系统名称 -->
|
||||
<div class="header-left">
|
||||
<div class="logo">
|
||||
<img src="@/assets/images/logo.webp" alt="logo" />
|
||||
<span class="system-name">Admin System</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:功能按钮和用户信息 -->
|
||||
<div class="header-right">
|
||||
<!-- 全屏 -->
|
||||
<el-tooltip :content="isFullscreen ? '退出全屏' : '全屏'">
|
||||
<el-button :icon="isFullscreen ? Crop : FullScreen" circle @click="toggleFullscreen" />
|
||||
</el-tooltip>
|
||||
|
||||
<!-- 语言选择 -->
|
||||
<LanguageSelect v-model="languageStore.lang" @change="languageStore.changeLang" />
|
||||
|
||||
<!-- 用户信息下拉菜单 -->
|
||||
<el-dropdown @command="handleCommand" class="user-dropdown">
|
||||
<div class="user-info">
|
||||
<LanguageSelect v-model="languageStore.lang" @change="languageStore.changeLang" />
|
||||
<el-dropdown @command="handleCommand">
|
||||
<el-button class="user-button">
|
||||
{{ activeUserName }}<el-icon class="el-icon--right">
|
||||
<ArrowDownBold />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-for="item in dropdownItems" :key="item.command" :command="item.command">
|
||||
{{ item.label }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-avatar :size="32" :src="userAvatar" class="user-avatar">
|
||||
<el-icon><User /></el-icon>
|
||||
</el-avatar>
|
||||
<span class="username">{{ activeUserName }}</span>
|
||||
<el-icon class="dropdown-icon"><ArrowDown /></el-icon>
|
||||
</div>
|
||||
</header>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="profile">
|
||||
<el-icon><User /></el-icon>
|
||||
个人中心
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="password">
|
||||
<el-icon><Lock /></el-icon>
|
||||
修改密码
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item divided command="logout">
|
||||
<el-icon><SwitchButton /></el-icon>
|
||||
退出登录
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ArrowDownBold } from '@element-plus/icons-vue'
|
||||
//引入语言选择组件和导入Pinia状态
|
||||
import LanguageSelect from '@/components/LanguageSelect/index.vue'
|
||||
import { useLanguageStore } from '@/stores/modules/language'
|
||||
|
||||
//引入store和storage
|
||||
import {
|
||||
Bell,
|
||||
Sunny,
|
||||
Moon,
|
||||
FullScreen,
|
||||
Crop,
|
||||
User,
|
||||
Lock,
|
||||
SwitchButton,
|
||||
ArrowDown
|
||||
} from '@element-plus/icons-vue'
|
||||
import { useUserStore } from '@/stores/modules/user'
|
||||
import { useLanguageStore } from '@/stores/modules/language'
|
||||
import LanguageSelect from '@/components/LanguageSelect/index.vue'
|
||||
import { getStorage } from '@/utils/storage'
|
||||
|
||||
//定义路由/Store实例
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const languageStore = useLanguageStore()
|
||||
//获取用户信息
|
||||
|
||||
// 用户信息
|
||||
const userInfo = getStorage<any>('userInfo')
|
||||
const activeUserName = ref(userInfo?.username || 'Admin')
|
||||
const userAvatar = ref(userInfo?.avatar || '')
|
||||
const unreadCount = ref(3) // 未读消息数量
|
||||
|
||||
//用户操作实例
|
||||
const activeUserName = ref(userInfo?.username || '')
|
||||
const activeItem = ref('currentUser')
|
||||
const dropdownItems = [
|
||||
// { label: '切换账户', command: 'account' },
|
||||
{ label: '修改密码', command: 'password' },
|
||||
{ label: '退出登录', command: 'logout' }
|
||||
]
|
||||
|
||||
|
||||
const handleAccount = async () => {
|
||||
userStore.switchAccount()
|
||||
await router.push('/user/login')
|
||||
// 全屏相关
|
||||
const isFullscreen = ref(false)
|
||||
const toggleFullscreen = () => {
|
||||
if (!document.fullscreenElement) {
|
||||
document.documentElement.requestFullscreen()
|
||||
isFullscreen.value = true
|
||||
} else {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen()
|
||||
isFullscreen.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleChangePassword = () => {
|
||||
router.push('/user/change-password')
|
||||
// 监听全屏变化
|
||||
onMounted(() => {
|
||||
document.addEventListener('fullscreenchange', () => {
|
||||
isFullscreen.value = !!document.fullscreenElement
|
||||
})
|
||||
})
|
||||
|
||||
// 消息通知
|
||||
const handleNotification = () => {
|
||||
ElMessage.info('暂无新消息')
|
||||
unreadCount.value = 0
|
||||
}
|
||||
|
||||
// 用户操作
|
||||
const handleCommand = (command: string) => {
|
||||
switch (command) {
|
||||
case 'profile':
|
||||
router.push('/profile/personal-center')
|
||||
break
|
||||
case 'password':
|
||||
router.push('/user/change-password')
|
||||
break
|
||||
case 'logout':
|
||||
handleLogout()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
const handleLogout = () => {
|
||||
// 示例:退出登录逻辑
|
||||
ElMessageBox.confirm('确定要退出登录吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
const deviceNo = getStorage('device_no') as string
|
||||
userStore.logout({ device_no: String(deviceNo) })
|
||||
}
|
||||
|
||||
// 处理菜单项点击
|
||||
const handleCommand = (command: string) => {
|
||||
activeItem.value = command
|
||||
// 不同指令的业务逻辑
|
||||
switch (command) {
|
||||
case 'currentUser':
|
||||
// 点击当前用户:无操作(仅选中)
|
||||
break
|
||||
case 'account':
|
||||
// 这里添加切换账户的逻辑(如弹窗/路由跳转)
|
||||
handleAccount()
|
||||
break
|
||||
case 'password':
|
||||
// 示例:跳转到修改密码页
|
||||
handleChangePassword()
|
||||
break
|
||||
case 'logout':
|
||||
// 示例:退出登录逻辑
|
||||
handleLogout()
|
||||
break
|
||||
}
|
||||
}).catch(() => {
|
||||
// 取消退出
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
header {
|
||||
height: 60px;
|
||||
border-bottom: solid 1px var(--el-menu-border-color);
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.layout-header {
|
||||
height: 60px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid var(--el-border-color-light);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
|
||||
.user-info {
|
||||
.el-select {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.el-dropdown {
|
||||
.user-button {
|
||||
border: none;
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
img {
|
||||
height: 32px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.system-name {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.notification-badge {
|
||||
:deep(.el-badge__content) {
|
||||
transform: translateY(-50%) translateX(50%);
|
||||
}
|
||||
}
|
||||
|
||||
.el-button {
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 8px;
|
||||
color: var(--el-text-color-regular);
|
||||
|
||||
&:hover {
|
||||
background: var(--el-fill-color-light);
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.user-dropdown {
|
||||
margin-left: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
transition: background 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: var(--el-fill-color-light);
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
background: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.username {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-primary);
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dropdown-icon {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 暗色模式适配
|
||||
:deep(.dark) {
|
||||
.layout-header {
|
||||
background: var(--el-bg-color);
|
||||
border-bottom-color: var(--el-border-color-darker);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,23 +1,89 @@
|
|||
<!-- src/components/layout/Layout.vue -->
|
||||
<template class="layout">
|
||||
<section class="layout">
|
||||
<!-- 左侧菜单:Sidebar -->
|
||||
<Sidebar />
|
||||
<template>
|
||||
<section class="layout">
|
||||
<!-- 顶部导航栏:固定在顶部 -->
|
||||
<Header />
|
||||
|
||||
<!-- 标红区域:MainContent(主内容区) -->
|
||||
<section class="main-container">
|
||||
<!-- Header -->
|
||||
<Header />
|
||||
<!-- 主体内容区域 -->
|
||||
<section class="main-container">
|
||||
<!-- 左侧侧边菜单:固定在左侧 -->
|
||||
<Sidebar />
|
||||
|
||||
<!-- 内容路由视图 -->
|
||||
<main>
|
||||
<router-view/>
|
||||
</main>
|
||||
</section>
|
||||
<!-- 右侧内容区:面包屑 + 页签 + 主体内容 -->
|
||||
<section class="content-container">
|
||||
<!-- 面包屑导航 -->
|
||||
<Breadcrumb />
|
||||
|
||||
<!-- 页签标签栏 -->
|
||||
<Tabs />
|
||||
|
||||
<!-- 主体内容区 -->
|
||||
<main class="main-content">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="fade-transform" mode="out-in">
|
||||
<KeepAlive>
|
||||
<component :is="Component" :key="route.path" />
|
||||
</KeepAlive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</main>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Sidebar from './Sidebar.vue'
|
||||
import Header from './Header.vue'
|
||||
import Sidebar from './Sidebar.vue'
|
||||
import Breadcrumb from './Breadcrumb.vue'
|
||||
import Tabs from './Tabs.vue'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.layout {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: var(--el-bg-color-page);
|
||||
|
||||
.main-container {
|
||||
display: flex;
|
||||
height: calc(100vh - 60px);
|
||||
margin-top: 60px;
|
||||
|
||||
.content-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
margin-left: 200px;
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
background: var(--el-bg-color-page);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 页面切换动画
|
||||
.fade-transform-enter-active,
|
||||
.fade-transform-leave-active {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.fade-transform-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
}
|
||||
|
||||
.fade-transform-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(-30px);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,34 +1,34 @@
|
|||
<template>
|
||||
<aside>
|
||||
<div class="sidebar-header">
|
||||
<div class="logo">
|
||||
<img src="@/assets/images/logo.webp" alt="logo" :router="true" />
|
||||
<span>Admin System</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="sidebar">
|
||||
<!-- 菜单区域 -->
|
||||
<el-menu class="menu" :unique-opened="true" :default-active="activeMenu" router>
|
||||
<el-menu
|
||||
class="sidebar-menu"
|
||||
:unique-opened="true"
|
||||
:default-active="activeMenu"
|
||||
router
|
||||
>
|
||||
<template v-for="menu in menuList" :key="menu.id">
|
||||
<!-- 无子菜单 -->
|
||||
<el-menu-item v-if="!menu.children || menu.children.length === 0" :index="menu.path">
|
||||
<el-icon>
|
||||
<el-icon v-if="menu.icon">
|
||||
<component :is="iconMap[menu.icon]" />
|
||||
</el-icon>
|
||||
<template #title>{{ menu.label }}</template>
|
||||
</el-menu-item>
|
||||
|
||||
<!-- 有子菜单 -->
|
||||
<el-sub-menu v-else :index="menu.path">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<el-icon v-if="menu.icon">
|
||||
<component :is="iconMap[menu.icon]" />
|
||||
</el-icon>
|
||||
<span>{{ menu.label }}</span>
|
||||
</template>
|
||||
<el-menu-item v-for="subMenu in menu.children" :key="subMenu.id" :index="subMenu.path">
|
||||
<!-- <el-icon v-if="subMenu.icon">-->
|
||||
<!-- <component :is="iconMap[subMenu.icon]" />-->
|
||||
<!-- </el-icon>-->
|
||||
<el-menu-item
|
||||
v-for="subMenu in menu.children"
|
||||
:key="subMenu.id"
|
||||
:index="subMenu.path"
|
||||
>
|
||||
<template #title>{{ subMenu.label }}</template>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
|
|
@ -48,25 +48,22 @@ import {
|
|||
WarningFilled,
|
||||
Tools,
|
||||
} from '@element-plus/icons-vue'
|
||||
|
||||
//获取格式化后的菜单列表
|
||||
import { useUserStore } from '@/stores/modules/user'
|
||||
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
const menuList = ref<any[]>([])
|
||||
|
||||
// 计算当前激活的菜单 index(取路由 fullPath 作为激活标识)
|
||||
// 计算当前激活的菜单 index
|
||||
const activeMenu = computed(() => {
|
||||
return route.fullPath // 直接使用完整路径,和菜单 path 保持一致
|
||||
})
|
||||
// 组件挂载时异步加载菜单
|
||||
onMounted(async () => {
|
||||
menuList.value = userStore.sideMenu || []
|
||||
console.log('menuList.value', menuList.value)
|
||||
return route.fullPath
|
||||
})
|
||||
|
||||
//图标映射表
|
||||
// 获取完整菜单列表
|
||||
const menuList = computed(() => {
|
||||
return userStore.sideMenu || []
|
||||
})
|
||||
|
||||
// 图标映射表
|
||||
const iconMap: Record<string, any> = {
|
||||
HomeFilled,
|
||||
List,
|
||||
|
|
@ -77,36 +74,64 @@ const iconMap: Record<string, any> = {
|
|||
WarningFilled,
|
||||
Tools,
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
aside {
|
||||
.sidebar-header {
|
||||
height: 60px;
|
||||
border-bottom: solid 1px var(--el-menu-border-color);
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 200px;
|
||||
background: var(--el-bg-color-page);
|
||||
border-right: 1px solid var(--el-border-color-light);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
z-index: 999;
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.sidebar-menu {
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
|
||||
img {
|
||||
width: 100px;
|
||||
:deep(.el-menu-item) {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
|
||||
&.is-active {
|
||||
background: var(--el-color-primary-light-9);
|
||||
color: var(--el-color-primary);
|
||||
border-right: 3px solid var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu) {
|
||||
.el-sub-menu__title {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
> .el-sub-menu__title {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
height: 100%;
|
||||
border: none;
|
||||
// 暗色模式适配
|
||||
:deep(.dark) {
|
||||
.sidebar {
|
||||
background: var(--el-bg-color);
|
||||
border-right-color: var(--el-border-color-darker);
|
||||
|
||||
:deep(.el-menu-item.is-active) {
|
||||
background-color: var(--el-menu-hover-bg-color);
|
||||
.sidebar-menu {
|
||||
:deep(.el-menu-item) {
|
||||
&.is-active {
|
||||
background: var(--el-color-primary-dark-2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,411 @@
|
|||
<template>
|
||||
<div class="tabs-container">
|
||||
<el-tabs
|
||||
v-model="tabsStore.activeTab"
|
||||
type="card"
|
||||
closable
|
||||
@tab-click="handleTabClick"
|
||||
@tab-remove="handleTabRemove"
|
||||
>
|
||||
<el-tab-pane
|
||||
v-for="(tab, index) in tabsStore.tabs"
|
||||
:key="tab.path"
|
||||
:label="tab.title"
|
||||
:name="tab.path"
|
||||
>
|
||||
<template #label>
|
||||
<div
|
||||
class="tab-label"
|
||||
:class="{ 'is-context-active': contextMenuPath === tab.path }"
|
||||
draggable="true"
|
||||
@dragstart="handleDragStart($event, index)"
|
||||
@dragover.prevent="handleDragOver($event, index)"
|
||||
@drop="handleDrop($event, index)"
|
||||
@dragend="handleDragEnd"
|
||||
@contextmenu.prevent="handleContextMenu($event, tab.path)"
|
||||
>
|
||||
<span class="tab-text">{{ tab.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!-- 右键菜单 -->
|
||||
<el-dropdown
|
||||
popper-class="el-droMenu"
|
||||
:popper-style="style"
|
||||
ref="contextMenuRef"
|
||||
trigger="contextmenu"
|
||||
@command="handleContextMenuCommand"
|
||||
@visible-change="handleMenuVisibleChange"
|
||||
>
|
||||
<span class="context-menu-trigger"></span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="refresh">刷新当前页</el-dropdown-item>
|
||||
<el-dropdown-item command="close">关闭当前</el-dropdown-item>
|
||||
<el-dropdown-item command="closeOther">关闭其他</el-dropdown-item>
|
||||
<el-dropdown-item command="closeLeft">关闭左侧</el-dropdown-item>
|
||||
<el-dropdown-item command="closeRight">关闭右侧</el-dropdown-item>
|
||||
<el-dropdown-item command="closeAll" divided>关闭全部</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useTabsStore } from '@/stores/modules/tabs'
|
||||
import type { TabItem } from '@/stores/modules/tabs'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const tabsStore = useTabsStore()
|
||||
const contextMenuRef = ref()
|
||||
|
||||
// 当前右键点击的页签路径
|
||||
let contextMenuPath = ''
|
||||
|
||||
// 拖拽相关
|
||||
let dragIndex: number | null = null
|
||||
let dropIndex: number | null = null
|
||||
|
||||
// 组件挂载后,检查是否需要跳转到保存的激活页面
|
||||
onMounted(() => {
|
||||
// 如果当前路由不是保存的激活页面,则跳转
|
||||
if (tabsStore.activeTab && tabsStore.activeTab !== route.path) {
|
||||
const targetTab = tabsStore.tabs.find(item => item.path === tabsStore.activeTab)
|
||||
if (targetTab) {
|
||||
router.replace({ path: targetTab.path, query: targetTab.query })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 监听路由变化,自动添加页签
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
(newPath) => {
|
||||
if (route.meta?.title && !route.meta?.isAuthPage) {
|
||||
tabsStore.addTab({
|
||||
title: route.meta.title as string,
|
||||
path: route.path,
|
||||
query: route.query
|
||||
})
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 页签点击
|
||||
const handleTabClick = (tab: any) => {
|
||||
const targetTab = tabsStore.tabs.find(item => item.path === tab.paneName)
|
||||
if (targetTab) {
|
||||
router.push({ path: targetTab.path, query: targetTab.query })
|
||||
}
|
||||
}
|
||||
|
||||
// 页签关闭
|
||||
const handleTabRemove = (path: string) => {
|
||||
tabsStore.removeTab(path)
|
||||
// 如果关闭的是当前页签,跳转到新的激活页签
|
||||
if (path === route.path && tabsStore.activeTab) {
|
||||
const targetTab = tabsStore.tabs.find(item => item.path === tabsStore.activeTab)
|
||||
if (targetTab) {
|
||||
router.push({ path: targetTab.path, query: targetTab.query })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 右键菜单命令处理
|
||||
const handleContextMenuCommand = (command: string) => {
|
||||
const currentPath = contextMenuPath || tabsStore.activeTab
|
||||
|
||||
switch (command) {
|
||||
case 'refresh':
|
||||
router.go(0)
|
||||
break
|
||||
case 'close':
|
||||
if (currentPath) {
|
||||
handleTabRemove(currentPath)
|
||||
}
|
||||
break
|
||||
case 'closeOther':
|
||||
if (currentPath) {
|
||||
tabsStore.closeOtherTabs(currentPath)
|
||||
}
|
||||
break
|
||||
case 'closeLeft':
|
||||
if (currentPath) {
|
||||
tabsStore.closeLeftTabs(currentPath)
|
||||
}
|
||||
break
|
||||
case 'closeRight':
|
||||
if (currentPath) {
|
||||
tabsStore.closeRightTabs(currentPath)
|
||||
}
|
||||
break
|
||||
case 'closeAll':
|
||||
tabsStore.closeAllTabs()
|
||||
router.push('/dashboard')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 处理右键菜单显示/隐藏
|
||||
const handleMenuVisibleChange = (visible: boolean) => {
|
||||
if (!visible) {
|
||||
contextMenuPath = ''
|
||||
}
|
||||
}
|
||||
const style = ref('')
|
||||
// 处理右键菜单点击
|
||||
const handleContextMenu = (e: MouseEvent, path: string) => {
|
||||
contextMenuPath = path
|
||||
// 右键时激活该标签并跳转
|
||||
tabsStore.setActiveTab(path)
|
||||
const targetTab = tabsStore.tabs.find(item => item.path === path)
|
||||
if (targetTab && path !== route.path) {
|
||||
router.push({ path: targetTab.path, query: targetTab.query })
|
||||
}
|
||||
// 手动触发 dropdown
|
||||
const dropdown = contextMenuRef.value
|
||||
if (dropdown) {
|
||||
// 获取 dropdown 实例
|
||||
const dropdownInstance = dropdown
|
||||
// 计算菜单位置
|
||||
const x = e.clientX
|
||||
const y = e.clientY
|
||||
// 使用 Element Plus 的内部方法显示菜单
|
||||
dropdownInstance?.handleOpen()
|
||||
// 设置菜单位置
|
||||
nextTick().then(() => {
|
||||
const menu = document.querySelector('.el-droMenu')
|
||||
if (menu) {
|
||||
style.value = `position: fixed; z-index: 9999; left: ${x}px; top: ${y}px;`
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 拖拽开始
|
||||
const handleDragStart = (e: DragEvent, index: number) => {
|
||||
dragIndex = index
|
||||
if (e.dataTransfer) {
|
||||
e.dataTransfer.effectAllowed = 'move'
|
||||
e.dataTransfer.setData('text/plain', index.toString())
|
||||
}
|
||||
// 添加拖拽样式
|
||||
const target = e.target as HTMLElement
|
||||
target.classList.add('dragging')
|
||||
}
|
||||
|
||||
// 拖拽经过
|
||||
const handleDragOver = (e: DragEvent, index: number) => {
|
||||
e.preventDefault()
|
||||
if (e.dataTransfer) {
|
||||
e.dataTransfer.dropEffect = 'move'
|
||||
}
|
||||
dropIndex = index
|
||||
}
|
||||
|
||||
// 拖拽结束
|
||||
const handleDragEnd = (e: DragEvent) => {
|
||||
const target = e.target as HTMLElement
|
||||
target.classList.remove('dragging')
|
||||
dragIndex = null
|
||||
dropIndex = null
|
||||
}
|
||||
|
||||
// 拖拽放置
|
||||
const handleDrop = (e: DragEvent, index: number) => {
|
||||
e.preventDefault()
|
||||
if (dragIndex === null || dragIndex === index) {
|
||||
return
|
||||
}
|
||||
|
||||
// 重新排序 tabs
|
||||
const newTabs = [...tabsStore.tabs]
|
||||
const [draggedTab] = newTabs.splice(dragIndex, 1)
|
||||
if (draggedTab) {
|
||||
newTabs.splice(index, 0, draggedTab)
|
||||
// 更新 store
|
||||
tabsStore.setTabs(newTabs)
|
||||
}
|
||||
|
||||
// 移除拖拽样式
|
||||
const target = e.target as HTMLElement
|
||||
target.classList.remove('dragging')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tabs-container {
|
||||
background: linear-gradient(180deg, #ffffff 0%, #fafbfc 100%);
|
||||
border-bottom: 1px solid var(--el-border-color-light);
|
||||
padding: 8px 20px 0;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
|
||||
:deep(.el-tabs) {
|
||||
.el-tabs__header {
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.el-tabs__nav {
|
||||
border: none !important;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.el-tabs__item {
|
||||
border: 1px solid var(--el-border-color-lighter) !important;
|
||||
border-bottom: none !important;
|
||||
margin-right: 0;
|
||||
border-radius: 8px 8px 0 0;
|
||||
background: linear-gradient(180deg, #f8f9fb 0%, #f0f2f5 100%);
|
||||
color: #5a5e66;
|
||||
height: 38px;
|
||||
line-height: 38px;
|
||||
padding: 0 18px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
// 顶部渐变装饰
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, var(--el-color-primary), var(--el-color-primary-light-3));
|
||||
transform: scaleX(0);
|
||||
transition: transform 0.3s ease;
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
|
||||
// 底部白色边框效果
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: #fff;
|
||||
transform: scaleX(0);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
background: linear-gradient(180deg, #fff 0%, #f5f7fa 100%);
|
||||
border-color: var(--el-color-primary-light-6) !important;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
transform: translateY(-2px);
|
||||
|
||||
&::before {
|
||||
transform: scaleX(0.7);
|
||||
}
|
||||
|
||||
&::after {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
|
||||
&.is-active,
|
||||
&.is-context-active {
|
||||
background: #fff;
|
||||
color: var(--el-color-primary);
|
||||
// border-bottom: 1px solid #fff !important;
|
||||
border-color: var(--el-color-primary-light-4) !important;
|
||||
box-shadow: 0 4px 16px rgba(var(--el-color-primary-rgb), 0.15);
|
||||
font-weight: 500;
|
||||
z-index: 2;
|
||||
|
||||
&::before {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
&::after {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 右键选中时的特殊样式
|
||||
&.is-context-active:not(.is-active) {
|
||||
background: linear-gradient(180deg, #fff 0%, #f0f7ff 100%);
|
||||
border-color: var(--el-color-primary-light-5) !important;
|
||||
}
|
||||
|
||||
// 关闭按钮样式
|
||||
.el-icon-close {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 12px;
|
||||
margin-left: 6px;
|
||||
vertical-align: middle;
|
||||
|
||||
&:hover {
|
||||
background: var(--el-color-danger);
|
||||
color: #fff;
|
||||
transform: scale(1.15) rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
// 拖拽样式
|
||||
.tab-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
gap: 6px;
|
||||
|
||||
.tab-text {
|
||||
display: inline-block;
|
||||
max-width: 150px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&.dragging {
|
||||
opacity: 0.5;
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs__nav-wrap::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// 左右滚动按钮样式
|
||||
.el-tabs__nav-prev,
|
||||
.el-tabs__nav-next {
|
||||
width: 32px;
|
||||
line-height: 38px;
|
||||
color: var(--el-text-color-secondary);
|
||||
transition: all 0.2s ease;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
background: var(--el-fill-color-light);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.context-menu-trigger {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { getStorage, setStorage, removeStorage } from '@/utils/storage'
|
||||
import { useUserStore } from './user'
|
||||
|
||||
export interface BreadcrumbItem {
|
||||
title: string
|
||||
path: string
|
||||
}
|
||||
|
||||
export interface TabItem {
|
||||
title: string
|
||||
path: string
|
||||
query?: any
|
||||
breadcrumbs?: BreadcrumbItem[]
|
||||
}
|
||||
|
||||
export const useTabsStore = defineStore('tabs', () => {
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 从本地存储恢复页签数据
|
||||
const savedTabs = getStorage('tabs')
|
||||
const savedActiveTab = getStorage('activeTab')
|
||||
|
||||
// 验证恢复的数据是否有效
|
||||
const tabs = ref<TabItem[]>(Array.isArray(savedTabs) && savedTabs.length > 0 ? savedTabs : [])
|
||||
const activeTab = ref<string>(
|
||||
typeof savedActiveTab === 'string' &&
|
||||
tabs.value.some(t => t.path === savedActiveTab)
|
||||
? savedActiveTab
|
||||
: '/dashboard'
|
||||
)
|
||||
|
||||
// 如果没有有效的标签页,初始化添加首页
|
||||
if (tabs.value.length === 0) {
|
||||
tabs.value.push({
|
||||
title: '首页',
|
||||
path: '/dashboard'
|
||||
})
|
||||
activeTab.value = '/dashboard'
|
||||
}
|
||||
|
||||
/**
|
||||
* 从菜单中查找当前路由的面包屑路径
|
||||
*/
|
||||
const findBreadcrumbPath = (path: string): BreadcrumbItem[] => {
|
||||
const list: BreadcrumbItem[] = []
|
||||
|
||||
// 添加首页
|
||||
list.push({
|
||||
title: '首页',
|
||||
path: '/dashboard'
|
||||
})
|
||||
|
||||
// 如果是首页,直接返回
|
||||
if (path === '/dashboard') {
|
||||
return list
|
||||
}
|
||||
|
||||
// 从菜单中查找当前路由的层级信息
|
||||
const findMenuPath = (menus: any[], targetPath: string, currentPath: any[] = []): any[] | null => {
|
||||
for (const menu of menus) {
|
||||
const menuPath = menu.path || menu.api || `/menu-${menu.id}`
|
||||
const newPath = [...currentPath, menu]
|
||||
|
||||
if (menuPath === targetPath) {
|
||||
return newPath
|
||||
}
|
||||
|
||||
// 兼容 children 和 son 两种子菜单字段名
|
||||
const subMenus = menu.children || menu.son
|
||||
if (subMenus && subMenus.length > 0) {
|
||||
const result = findMenuPath(subMenus, targetPath, newPath)
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const menuPath = findMenuPath(userStore.sideMenu, path)
|
||||
console.log('menuPath:', menuPath);
|
||||
|
||||
if (menuPath) {
|
||||
console.log('list:', menuPath)
|
||||
menuPath.forEach((menu: any, index: number) => {
|
||||
// 跳过第一个(首页已添加)
|
||||
if (index === 0 && menu.label === '首页') {
|
||||
return
|
||||
}
|
||||
list.push({
|
||||
title: menu.label,
|
||||
path: menu.path || menu.api || `/menu-${menu.id}`
|
||||
})
|
||||
})
|
||||
}
|
||||
;
|
||||
return list
|
||||
}
|
||||
|
||||
// 添加页签
|
||||
const addTab = (tab: TabItem) => {
|
||||
// 检查是否已存在
|
||||
const existIndex = tabs.value.findIndex(item => item.path === tab.path)
|
||||
// 计算面包屑路径
|
||||
const breadcrumbs = findBreadcrumbPath(tab.path)
|
||||
console.log('aaaaaaaaaaaaa:', breadcrumbs);
|
||||
|
||||
if (existIndex === -1) {
|
||||
// 新标签页,添加到列表
|
||||
tabs.value.push({ ...tab, breadcrumbs })
|
||||
} else {
|
||||
// 已存在的标签页,更新面包屑(确保数据是最新的)
|
||||
const existingTab = tabs.value[existIndex]
|
||||
if (existingTab) {
|
||||
existingTab.breadcrumbs = breadcrumbs
|
||||
}
|
||||
}
|
||||
activeTab.value = tab.path
|
||||
saveTabs()
|
||||
}
|
||||
|
||||
// 移除页签
|
||||
const removeTab = (path: string) => {
|
||||
const index = tabs.value.findIndex(item => item.path === path)
|
||||
if (index !== -1) {
|
||||
tabs.value.splice(index, 1)
|
||||
// 如果移除的是当前激活的页签,则激活前一个或后一个
|
||||
if (activeTab.value === path && tabs.value.length > 0) {
|
||||
const newIndex = Math.max(0, index - 1)
|
||||
const newTab = tabs.value[newIndex]
|
||||
if (newTab) {
|
||||
activeTab.value = newTab.path
|
||||
}
|
||||
}
|
||||
saveTabs()
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭其他页签
|
||||
const closeOtherTabs = (path: string) => {
|
||||
const currentTab = tabs.value.find(item => item.path === path)
|
||||
if (currentTab) {
|
||||
tabs.value = [currentTab]
|
||||
activeTab.value = path
|
||||
saveTabs()
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭所有页签
|
||||
const closeAllTabs = () => {
|
||||
// 保留首页
|
||||
tabs.value = [{
|
||||
title: '首页',
|
||||
path: '/dashboard'
|
||||
}]
|
||||
activeTab.value = '/dashboard'
|
||||
saveTabs()
|
||||
}
|
||||
|
||||
// 关闭左侧页签
|
||||
const closeLeftTabs = (path: string) => {
|
||||
const index = tabs.value.findIndex(item => item.path === path)
|
||||
if (index > 0) {
|
||||
tabs.value = tabs.value.slice(index)
|
||||
saveTabs()
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭右侧页签
|
||||
const closeRightTabs = (path: string) => {
|
||||
const index = tabs.value.findIndex(item => item.path === path)
|
||||
if (index !== -1 && index < tabs.value.length - 1) {
|
||||
tabs.value = tabs.value.slice(0, index + 1)
|
||||
saveTabs()
|
||||
}
|
||||
}
|
||||
|
||||
// 设置当前激活的页签
|
||||
const setActiveTab = (path: string) => {
|
||||
activeTab.value = path
|
||||
saveTabs()
|
||||
}
|
||||
|
||||
// 保存到本地存储
|
||||
const saveTabs = () => {
|
||||
setStorage('tabs', tabs.value)
|
||||
setStorage('activeTab', activeTab.value)
|
||||
}
|
||||
|
||||
// 清空页签
|
||||
const clearTabs = () => {
|
||||
tabs.value = []
|
||||
activeTab.value = ''
|
||||
removeStorage('tabs')
|
||||
removeStorage('activeTab')
|
||||
}
|
||||
|
||||
// 设置标签页列表(用于拖拽排序)
|
||||
const setTabs = (newTabs: TabItem[]) => {
|
||||
tabs.value = newTabs
|
||||
saveTabs()
|
||||
}
|
||||
|
||||
// 获取当前激活标签页的面包屑
|
||||
const currentBreadcrumbs = computed<BreadcrumbItem[]>(() => {
|
||||
const currentTab = tabs.value.find(item => item.path === activeTab.value)
|
||||
if (currentTab && currentTab.breadcrumbs) {
|
||||
return currentTab.breadcrumbs
|
||||
}
|
||||
// 默认返回首页
|
||||
return [{ title: '首页', path: '/dashboard' }]
|
||||
})
|
||||
|
||||
return {
|
||||
tabs,
|
||||
activeTab,
|
||||
currentBreadcrumbs,
|
||||
addTab,
|
||||
removeTab,
|
||||
closeOtherTabs,
|
||||
closeAllTabs,
|
||||
closeLeftTabs,
|
||||
closeRightTabs,
|
||||
setActiveTab,
|
||||
clearTabs,
|
||||
setTabs
|
||||
}
|
||||
})
|
||||
|
|
@ -317,9 +317,7 @@ const handleLanguageChange = async (val: number) => {
|
|||
await editLangTypeApi({ lang_type: val });
|
||||
userInfo.lang_type = val;
|
||||
languageStore.changeLang(val === 1 ? 'zh-CN' : 'en-US');
|
||||
ElMessage.success('语言切换成功');
|
||||
} catch (error) {
|
||||
ElMessage.error('语言切换失败');
|
||||
console.error('语言切换失败:', error);
|
||||
}
|
||||
};
|
||||
|
|
@ -331,7 +329,7 @@ const handleEditConfirm = async () => {
|
|||
await editFormRef.value.validate();
|
||||
|
||||
if (editField.value === 'username') {
|
||||
const params: EditUsernameParams = { username: editForm.value, user_id: userInfo.id };
|
||||
const params: EditUsernameParams = { username: editForm.value };
|
||||
await editUsernameApi(params);
|
||||
userInfo.username = editForm.value;
|
||||
}
|
||||
|
|
@ -345,7 +343,6 @@ const handleEditConfirm = async () => {
|
|||
await bindEmailApi(params);
|
||||
userInfo.email = editForm.value;
|
||||
}
|
||||
ElMessage.success('操作成功');
|
||||
dialogVisible.value = false;
|
||||
editFormRef.value.resetFields();
|
||||
} catch (err) {
|
||||
|
|
@ -424,7 +421,6 @@ const handlePasswordConfirm = async () => {
|
|||
};
|
||||
|
||||
await updatePwdByCaptchaApi(params);
|
||||
ElMessage.success('密码修改成功');
|
||||
passwordDialogVisible.value = false;
|
||||
passwordFormRef.value.resetFields();
|
||||
} catch (err) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue