feat: 添加二维码登录功能并优化全屏播放器界面
添加了 qrcode 库支持扫码登录功能,包括相关API接口类型定义 和扫码登录流程实现。同时调整全屏播放器组件的样式布局, 优化歌词显示效果和封面旋转动画尺寸。
This commit is contained in:
parent
2db45f95c0
commit
6e8c97ff64
|
|
@ -15,12 +15,14 @@
|
|||
"axios": "^1.19.0",
|
||||
"dayjs": "^1.11.21",
|
||||
"naive-ui": "^2.44.1",
|
||||
"qrcode": "^1.5.4",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^5.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2",
|
||||
"@types/node": "^26.2.0",
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"typescript": "~5.6.2",
|
||||
"unplugin-auto-import": "^21.1.0",
|
||||
|
|
|
|||
226
pnpm-lock.yaml
226
pnpm-lock.yaml
|
|
@ -23,6 +23,9 @@ importers:
|
|||
naive-ui:
|
||||
specifier: ^2.44.1
|
||||
version: 2.44.1(vue@3.5.41(typescript@5.6.2))
|
||||
qrcode:
|
||||
specifier: ^1.5.4
|
||||
version: 1.5.4
|
||||
vue:
|
||||
specifier: ^3.5.13
|
||||
version: 3.5.41(typescript@5.6.2)
|
||||
|
|
@ -36,6 +39,9 @@ importers:
|
|||
'@types/node':
|
||||
specifier: ^26.2.0
|
||||
version: 26.2.0
|
||||
'@types/qrcode':
|
||||
specifier: ^1.5.6
|
||||
version: 1.5.6
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^5.2.1
|
||||
version: 5.2.1(vite@6.4.3(@types/node@26.2.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.6.2))
|
||||
|
|
@ -527,6 +533,9 @@ packages:
|
|||
'@types/node@26.2.0':
|
||||
resolution: {integrity: sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==}
|
||||
|
||||
'@types/qrcode@1.5.6':
|
||||
resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==}
|
||||
|
||||
'@vitejs/plugin-vue@5.2.1':
|
||||
resolution: {integrity: sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
|
|
@ -611,6 +620,14 @@ packages:
|
|||
alien-signals@0.2.0:
|
||||
resolution: {integrity: sha512-StlonZhBBrsPPwrDjiPAiVTf/rolxffLxVPT60Qv/t88BZ81BvUVzHgGqEFvJ1ii8HXtm1+zU2Icr59tfWEcag==}
|
||||
|
||||
ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ansi-styles@4.0.0:
|
||||
resolution: {integrity: sha512-8zjUtFJ3db/QoPXuuEMloS2AUf79/yeyttJ7Abr3hteopJu9HK8vsgGviGUMq+zyA6cZZO6gAyZoMTF6TgaEjA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ast-kit@2.1.2:
|
||||
resolution: {integrity: sha512-cl76xfBQM6pztbrFWRnxbrDm9EOqDr1BF6+qQnnDZG2Co2LjyUktkN9GTJfBAfdae+DbT2nJf2nCGAdDDN7W2g==}
|
||||
engines: {node: '>=20.18.0'}
|
||||
|
|
@ -645,10 +662,24 @@ 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'}
|
||||
|
||||
chokidar@5.0.0:
|
||||
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
|
||||
cliui@6.0.0:
|
||||
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
|
||||
|
||||
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'}
|
||||
|
|
@ -691,14 +722,24 @@ packages:
|
|||
supports-color:
|
||||
optional: true
|
||||
|
||||
decamelize@1.2.0:
|
||||
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
delayed-stream@1.0.0:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
dijkstrajs@1.0.3:
|
||||
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
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'}
|
||||
|
|
@ -749,6 +790,10 @@ packages:
|
|||
picomatch:
|
||||
optional: true
|
||||
|
||||
find-up@4.1.0:
|
||||
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
follow-redirects@1.16.0:
|
||||
resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
|
||||
engines: {node: '>=4.0'}
|
||||
|
|
@ -770,6 +815,10 @@ packages:
|
|||
function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
|
||||
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'}
|
||||
|
|
@ -809,6 +858,10 @@ packages:
|
|||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
is-fullwidth-code-point@3.0.0:
|
||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
js-tokens@10.0.0:
|
||||
resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==}
|
||||
|
||||
|
|
@ -826,6 +879,10 @@ packages:
|
|||
resolution: {integrity: sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
locate-path@5.0.0:
|
||||
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
lodash-es@4.18.1:
|
||||
resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==}
|
||||
|
||||
|
|
@ -885,9 +942,25 @@ packages:
|
|||
resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
|
||||
p-limit@2.2.0:
|
||||
resolution: {integrity: sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
p-locate@4.1.0:
|
||||
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
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==}
|
||||
|
||||
path-exists@4.0.0:
|
||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
pathe@2.0.3:
|
||||
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||
|
||||
|
|
@ -907,6 +980,10 @@ packages:
|
|||
pkg-types@2.3.1:
|
||||
resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==}
|
||||
|
||||
pngjs@5.0.0:
|
||||
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
postcss@8.5.26:
|
||||
resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
|
@ -915,6 +992,11 @@ packages:
|
|||
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
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==}
|
||||
|
||||
|
|
@ -922,6 +1004,13 @@ packages:
|
|||
resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==}
|
||||
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==}
|
||||
|
||||
rollup@4.62.4:
|
||||
resolution: {integrity: sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
|
|
@ -938,10 +1027,21 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
set-blocking@2.0.0:
|
||||
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
string-width@4.2.0:
|
||||
resolution: {integrity: sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
strip-ansi@6.0.0:
|
||||
resolution: {integrity: sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
strip-literal@4.0.0:
|
||||
resolution: {integrity: sha512-PaqAvfUZKBwc/SLmNZtHmzK+v19Z4O4eS3cKPeGvbIv/U3pnyEq4Tuw3/4v/FwfM8VQaEawsyCcOQ0P+kpwWWw==}
|
||||
|
||||
|
|
@ -1127,11 +1227,29 @@ 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==}
|
||||
|
||||
wrap-ansi@6.2.0:
|
||||
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
y18n@4.0.3:
|
||||
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
|
||||
|
||||
yaml@2.9.0:
|
||||
resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==}
|
||||
engines: {node: '>= 14.6'}
|
||||
hasBin: true
|
||||
|
||||
yargs-parser@18.1.3:
|
||||
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
yargs@15.3.1:
|
||||
resolution: {integrity: sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@babel/generator@8.0.0':
|
||||
|
|
@ -1428,6 +1546,10 @@ snapshots:
|
|||
dependencies:
|
||||
undici-types: 8.3.0
|
||||
|
||||
'@types/qrcode@1.5.6':
|
||||
dependencies:
|
||||
'@types/node': 26.2.0
|
||||
|
||||
'@vitejs/plugin-vue@5.2.1(vite@6.4.3(@types/node@26.2.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.6.2))':
|
||||
dependencies:
|
||||
vite: 6.4.3(@types/node@26.2.0)(yaml@2.9.0)
|
||||
|
|
@ -1550,6 +1672,12 @@ snapshots:
|
|||
|
||||
alien-signals@0.2.0: {}
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
ansi-styles@4.0.0:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
|
||||
ast-kit@2.1.2:
|
||||
dependencies:
|
||||
'@babel/parser': 7.29.8
|
||||
|
|
@ -1593,10 +1721,24 @@ snapshots:
|
|||
es-errors: 1.3.0
|
||||
function-bind: 1.1.2
|
||||
|
||||
camelcase@5.3.1: {}
|
||||
|
||||
chokidar@5.0.0:
|
||||
dependencies:
|
||||
readdirp: 5.1.1
|
||||
|
||||
cliui@6.0.0:
|
||||
dependencies:
|
||||
string-width: 4.2.0
|
||||
strip-ansi: 6.0.0
|
||||
wrap-ansi: 6.2.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
|
||||
|
|
@ -1628,14 +1770,20 @@ snapshots:
|
|||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
decamelize@1.2.0: {}
|
||||
|
||||
delayed-stream@1.0.0: {}
|
||||
|
||||
dijkstrajs@1.0.3: {}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-errors: 1.3.0
|
||||
gopd: 1.2.0
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
entities@7.0.1: {}
|
||||
|
||||
es-define-property@1.0.1: {}
|
||||
|
|
@ -1697,6 +1845,11 @@ snapshots:
|
|||
optionalDependencies:
|
||||
picomatch: 4.0.5
|
||||
|
||||
find-up@4.1.0:
|
||||
dependencies:
|
||||
locate-path: 5.0.0
|
||||
path-exists: 4.0.0
|
||||
|
||||
follow-redirects@1.16.0(debug@4.4.3):
|
||||
optionalDependencies:
|
||||
debug: 4.4.3
|
||||
|
|
@ -1714,6 +1867,8 @@ snapshots:
|
|||
|
||||
function-bind@1.1.2: {}
|
||||
|
||||
get-caller-file@2.0.5: {}
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
|
|
@ -1757,6 +1912,8 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
is-fullwidth-code-point@3.0.0: {}
|
||||
|
||||
js-tokens@10.0.0: {}
|
||||
|
||||
jsesc@3.1.0: {}
|
||||
|
|
@ -1769,6 +1926,10 @@ snapshots:
|
|||
pkg-types: 2.3.1
|
||||
quansync: 0.2.11
|
||||
|
||||
locate-path@5.0.0:
|
||||
dependencies:
|
||||
p-locate: 4.1.0
|
||||
|
||||
lodash-es@4.18.1: {}
|
||||
|
||||
lodash@4.18.1: {}
|
||||
|
|
@ -1836,8 +1997,20 @@ snapshots:
|
|||
|
||||
obug@2.1.4: {}
|
||||
|
||||
p-limit@2.2.0:
|
||||
dependencies:
|
||||
p-try: 2.2.0
|
||||
|
||||
p-locate@4.1.0:
|
||||
dependencies:
|
||||
p-limit: 2.2.0
|
||||
|
||||
p-try@2.2.0: {}
|
||||
|
||||
path-browserify@1.0.1: {}
|
||||
|
||||
path-exists@4.0.0: {}
|
||||
|
||||
pathe@2.0.3: {}
|
||||
|
||||
perfect-debounce@2.1.0: {}
|
||||
|
|
@ -1858,6 +2031,8 @@ snapshots:
|
|||
exsolve: 1.1.1
|
||||
pathe: 2.0.3
|
||||
|
||||
pngjs@5.0.0: {}
|
||||
|
||||
postcss@8.5.26:
|
||||
dependencies:
|
||||
nanoid: 3.3.18
|
||||
|
|
@ -1866,10 +2041,20 @@ snapshots:
|
|||
|
||||
proxy-from-env@2.1.0: {}
|
||||
|
||||
qrcode@1.5.4:
|
||||
dependencies:
|
||||
dijkstrajs: 1.0.3
|
||||
pngjs: 5.0.0
|
||||
yargs: 15.3.1
|
||||
|
||||
quansync@0.2.11: {}
|
||||
|
||||
readdirp@5.1.1: {}
|
||||
|
||||
require-directory@2.1.1: {}
|
||||
|
||||
require-main-filename@2.0.0: {}
|
||||
|
||||
rollup@4.62.4:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.9
|
||||
|
|
@ -1908,8 +2093,20 @@ snapshots:
|
|||
|
||||
semver@7.8.5: {}
|
||||
|
||||
set-blocking@2.0.0: {}
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
string-width@4.2.0:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
is-fullwidth-code-point: 3.0.0
|
||||
strip-ansi: 6.0.0
|
||||
|
||||
strip-ansi@6.0.0:
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
|
||||
strip-literal@4.0.0:
|
||||
dependencies:
|
||||
js-tokens: 10.0.0
|
||||
|
|
@ -2100,4 +2297,33 @@ snapshots:
|
|||
|
||||
webpack-virtual-modules@0.6.2: {}
|
||||
|
||||
which-module@2.0.1: {}
|
||||
|
||||
wrap-ansi@6.2.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.0.0
|
||||
string-width: 4.2.0
|
||||
strip-ansi: 6.0.0
|
||||
|
||||
y18n@4.0.3: {}
|
||||
|
||||
yaml@2.9.0: {}
|
||||
|
||||
yargs-parser@18.1.3:
|
||||
dependencies:
|
||||
camelcase: 5.3.1
|
||||
decamelize: 1.2.0
|
||||
|
||||
yargs@15.3.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.0
|
||||
which-module: 2.0.1
|
||||
y18n: 4.0.3
|
||||
yargs-parser: 18.1.3
|
||||
|
|
|
|||
|
|
@ -1,14 +1,37 @@
|
|||
import { get, post } from "@/utils/request"
|
||||
import Source from "@/types/global"
|
||||
|
||||
/** 支持扫码登录的平台 */
|
||||
export interface QrLoginSource {
|
||||
name: string
|
||||
source: string
|
||||
}
|
||||
|
||||
/** 扫码登录会话 */
|
||||
export interface QrLoginSession {
|
||||
source: string
|
||||
key: string
|
||||
url: string
|
||||
expires_at: number
|
||||
}
|
||||
|
||||
/** 扫码登录状态 */
|
||||
export interface QrLoginStatus {
|
||||
source: string
|
||||
key: string
|
||||
status: string
|
||||
message: string
|
||||
extra?: Record<string, unknown>
|
||||
}
|
||||
|
||||
const SystemApi = {
|
||||
/**
|
||||
* 获取当前系统加载的 Cookies
|
||||
* @description 读取并在 JSON 格式下返回当前系统已配置的各平台 Cookies。
|
||||
* @returns
|
||||
*/
|
||||
cookies: () => {
|
||||
return get<any>(`/api/v1/system/cookies`)
|
||||
cookies: (): Promise<Record<string, string>> => {
|
||||
return get<Record<string, string>>(`/api/v1/system/cookies`)
|
||||
},
|
||||
/**
|
||||
* 设置系统 Cookies
|
||||
|
|
@ -23,17 +46,18 @@ const SystemApi = {
|
|||
* @description 返回当前 API 支持创建二维码登录会话的平台列表。
|
||||
* @returns
|
||||
*/
|
||||
qrLoginSource: () => {
|
||||
return get<any>(`/api/v1/system/qr_login/sources`)
|
||||
qrLoginSource: (): Promise<QrLoginSource[]> => {
|
||||
return get<QrLoginSource[]>(`/api/v1/system/qr_login/sources`)
|
||||
},
|
||||
/**
|
||||
* 轮询扫码登录状态
|
||||
* @description 使用创建扫码登录会话返回的 key 轮询登录状态;成功时自动写入 cookies.json。
|
||||
* @param source 扫码登录平台
|
||||
* @param key 扫码登录会话 key
|
||||
* @returns
|
||||
*/
|
||||
loginStatus: (source: Source) => {
|
||||
return get<any>(`/api/v1/system/qr_login/${source}`)
|
||||
loginStatus: (source: Source, key: string): Promise<QrLoginStatus> => {
|
||||
return get<QrLoginStatus>(`/api/v1/system/qr_login/${source}?key=${encodeURIComponent(key)}`)
|
||||
},
|
||||
/**
|
||||
* 创建扫码登录会话
|
||||
|
|
@ -41,8 +65,8 @@ const SystemApi = {
|
|||
* @param source 扫码登录平台
|
||||
* @returns
|
||||
*/
|
||||
qrLogin: (source: Source) => {
|
||||
return post<any>(`/api/v1/system/qr_login/${source}`)
|
||||
qrLogin: (source: Source): Promise<QrLoginSession> => {
|
||||
return post<QrLoginSession>(`/api/v1/system/qr_login/${source}`)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<template>
|
||||
<Teleport to="body">
|
||||
<Transition name="fullscreen">
|
||||
<div v-if="show" class="fullscreen-player">
|
||||
<div class="fullscreen-bg" :style="current ? { background: coverBackground(current.cover) } : undefined" />
|
||||
|
|
@ -91,6 +92,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
@ -250,14 +252,16 @@ watch(currentLyricIndex, (newIndex) => {
|
|||
|
||||
.cover-rotating {
|
||||
position: relative;
|
||||
width: 360px;
|
||||
height: 360px;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background: var(--app-card);
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
||||
animation-duration: 20s;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cover-rotating.spinning {
|
||||
|
|
@ -331,8 +335,8 @@ watch(currentLyricIndex, (newIndex) => {
|
|||
}
|
||||
|
||||
.lyric-line {
|
||||
padding: 16px 0;
|
||||
font-size: 24px;
|
||||
padding: 12px 0;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
text-align: center;
|
||||
|
|
@ -340,7 +344,7 @@ watch(currentLyricIndex, (newIndex) => {
|
|||
}
|
||||
|
||||
.lyric-line.active {
|
||||
font-size: 32px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--app-text);
|
||||
text-shadow: 0 2px 8px rgba(109, 93, 246, 0.4);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,352 @@
|
|||
<template>
|
||||
<section class="setting-card">
|
||||
<div class="setting-head">
|
||||
<h3>扫码登录</h3>
|
||||
<p>选择平台生成二维码,用对应 App 扫码后自动写入平台 Cookies,可提升搜索与播放的可用性。</p>
|
||||
</div>
|
||||
|
||||
<div v-if="loadingSources" class="login-loading">
|
||||
<n-spin size="small" />
|
||||
</div>
|
||||
|
||||
<template v-else-if="sources.length > 0">
|
||||
<n-radio-group :value="selectedSource" @update:value="onSelectSource" class="login-sources">
|
||||
<n-radio-button
|
||||
v-for="item in sources"
|
||||
:key="item.source"
|
||||
:value="item.source"
|
||||
:label="item.name"
|
||||
/>
|
||||
</n-radio-group>
|
||||
|
||||
<div class="login-body">
|
||||
<div class="qr-box">
|
||||
<canvas ref="qrCanvasRef" class="qr-canvas" />
|
||||
<div v-if="status.type === 'success'" class="qr-mask success">登录成功</div>
|
||||
<div v-else-if="status.type === 'expired'" class="qr-mask expired">二维码已过期</div>
|
||||
<div v-else-if="status.type === 'error'" class="qr-mask error">生成失败</div>
|
||||
</div>
|
||||
|
||||
<div class="login-side">
|
||||
<p class="status-text" :class="status.type">{{ status.text }}</p>
|
||||
<n-button
|
||||
type="primary"
|
||||
round
|
||||
:loading="creating"
|
||||
:disabled="polling && status.type !== 'expired' && status.type !== 'error'"
|
||||
@click="startLogin"
|
||||
>
|
||||
{{ session ? "重新生成" : "生成二维码" }}
|
||||
</n-button>
|
||||
<p class="hint">请使用「{{ currentSourceName }}」App 扫码</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<n-empty v-else description="暂无支持扫码登录的平台" class="empty" />
|
||||
|
||||
<div v-if="cookies.length > 0" class="cookie-row">
|
||||
<span class="cookie-label">已配置 Cookies:</span>
|
||||
<n-tag v-for="name in cookies" :key="name" size="small" :bordered="false" type="success">
|
||||
{{ name }}
|
||||
</n-tag>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from "vue"
|
||||
import { NButton, NEmpty, NRadioButton, NRadioGroup, NSpin, NTag, useMessage } from "naive-ui"
|
||||
import QRCode from "qrcode"
|
||||
import SystemApi, {
|
||||
type QrLoginSession,
|
||||
type QrLoginSource,
|
||||
type QrLoginStatus,
|
||||
} from "@/api/system"
|
||||
import Source from "@/types/global"
|
||||
import { SOURCE_LABELS } from "@/stores/settings"
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
type StatusType = "waiting" | "scanned" | "success" | "expired" | "error"
|
||||
|
||||
const sources = ref<QrLoginSource[]>([])
|
||||
const loadingSources = ref(true)
|
||||
const selectedSource = ref<Source | null>(null)
|
||||
|
||||
const qrCanvasRef = ref<HTMLCanvasElement | null>(null)
|
||||
const session = ref<QrLoginSession | null>(null)
|
||||
const creating = ref(false)
|
||||
const polling = ref(false)
|
||||
const status = ref<{ type: StatusType; text: string }>({
|
||||
type: "waiting",
|
||||
text: "等待扫码",
|
||||
})
|
||||
|
||||
const cookies = ref<string[]>([])
|
||||
|
||||
let pollTimer: number | undefined
|
||||
let pollSeq = 0
|
||||
|
||||
const currentSourceName = computed(
|
||||
() => sources.value.find((item) => item.source === selectedSource.value)?.name ?? SOURCE_LABELS[selectedSource.value as Source] ?? "",
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const list = await SystemApi.qrLoginSource()
|
||||
sources.value = list
|
||||
if (list.length > 0) {
|
||||
selectedSource.value = list[0].source as Source
|
||||
}
|
||||
} catch {
|
||||
sources.value = []
|
||||
} finally {
|
||||
loadingSources.value = false
|
||||
}
|
||||
void refreshCookies()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopPolling()
|
||||
})
|
||||
|
||||
async function refreshCookies() {
|
||||
try {
|
||||
const data = await SystemApi.cookies()
|
||||
cookies.value = Object.entries(data ?? {})
|
||||
.filter(([, value]) => Boolean(value))
|
||||
.map(([key]) => SOURCE_LABELS[key as Source] ?? key)
|
||||
} catch {
|
||||
// 忽略:登录成功后再刷新,失败不影响页面
|
||||
}
|
||||
}
|
||||
|
||||
function onSelectSource(value: string | number | boolean) {
|
||||
selectedSource.value = value as Source
|
||||
stopPolling()
|
||||
session.value = null
|
||||
status.value = { type: "waiting", text: "等待扫码" }
|
||||
}
|
||||
|
||||
async function startLogin() {
|
||||
if (!selectedSource.value) return
|
||||
stopPolling()
|
||||
session.value = null
|
||||
status.value = { type: "waiting", text: "等待扫码" }
|
||||
creating.value = true
|
||||
try {
|
||||
const result = await SystemApi.qrLogin(selectedSource.value)
|
||||
session.value = result
|
||||
await renderQr(result.url)
|
||||
status.value = { type: "waiting", text: "等待扫码" }
|
||||
startPolling(result)
|
||||
} catch {
|
||||
status.value = { type: "error", text: "生成二维码失败,请重试" }
|
||||
} finally {
|
||||
creating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function renderQr(text: string) {
|
||||
if (!qrCanvasRef.value) return
|
||||
await QRCode.toCanvas(qrCanvasRef.value, text, {
|
||||
width: 216,
|
||||
margin: 2,
|
||||
errorCorrectionLevel: "M",
|
||||
color: { dark: "#000000", light: "#ffffff" },
|
||||
})
|
||||
}
|
||||
|
||||
function startPolling(result: QrLoginSession) {
|
||||
const seq = ++pollSeq
|
||||
polling.value = true
|
||||
pollTimer = window.setInterval(async () => {
|
||||
if (seq !== pollSeq) return
|
||||
try {
|
||||
const res = await SystemApi.loginStatus(result.source as Source, result.key)
|
||||
if (seq !== pollSeq) return
|
||||
applyStatus(res)
|
||||
if (status.value.type === "success") {
|
||||
stopPolling()
|
||||
message.success(`「${currentSourceName.value}」登录成功,Cookies 已保存`)
|
||||
void refreshCookies()
|
||||
} else if (status.value.type === "expired" || status.value.type === "error") {
|
||||
stopPolling()
|
||||
}
|
||||
} catch {
|
||||
if (seq !== pollSeq) return
|
||||
status.value = { type: "error", text: "查询登录状态失败,请重试" }
|
||||
stopPolling()
|
||||
}
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
function applyStatus(res: QrLoginStatus) {
|
||||
const code = String(res.extra?.code ?? "")
|
||||
const raw = res.status ?? ""
|
||||
if (raw.includes("success") || code === "803") {
|
||||
status.value = { type: "success", text: res.message || "登录成功" }
|
||||
} else if (raw.includes("expired") || code === "800") {
|
||||
status.value = { type: "expired", text: res.message || "二维码已过期" }
|
||||
} else if (raw.includes("scan") || code === "802") {
|
||||
status.value = { type: "scanned", text: res.message || "已扫码,请在手机上确认" }
|
||||
} else {
|
||||
status.value = { type: "waiting", text: res.message || "等待扫码" }
|
||||
}
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
pollSeq += 1
|
||||
polling.value = false
|
||||
if (pollTimer !== undefined) {
|
||||
window.clearInterval(pollTimer)
|
||||
pollTimer = undefined
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.setting-card {
|
||||
margin-bottom: 20px;
|
||||
padding: clamp(16px, 2vw, 22px);
|
||||
border: 1px solid var(--app-border);
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
|
||||
.setting-head {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.setting-head h3 {
|
||||
margin: 0 0 4px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.setting-head p {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: var(--app-text-muted);
|
||||
}
|
||||
|
||||
.login-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 24px 0;
|
||||
}
|
||||
|
||||
.login-sources {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.login-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: clamp(18px, 3vw, 28px);
|
||||
}
|
||||
|
||||
.qr-box {
|
||||
position: relative;
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
background: #fff;
|
||||
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.qr-canvas {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.qr-mask {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.qr-mask.success {
|
||||
background: rgba(34, 197, 94, 0.72);
|
||||
}
|
||||
|
||||
.qr-mask.expired {
|
||||
background: rgba(239, 68, 68, 0.72);
|
||||
}
|
||||
|
||||
.login-side {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 14px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-text.waiting {
|
||||
color: var(--app-text-secondary);
|
||||
}
|
||||
|
||||
.status-text.scanned {
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.status-text.success {
|
||||
color: var(--app-green);
|
||||
}
|
||||
|
||||
.status-text.expired,
|
||||
.status-text.error {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: var(--app-text-muted);
|
||||
}
|
||||
|
||||
.cookie-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 16px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid var(--app-border);
|
||||
}
|
||||
|
||||
.cookie-label {
|
||||
font-size: 12px;
|
||||
color: var(--app-text-muted);
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: 24px 0;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.login-body {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -20,6 +20,7 @@ declare module 'vue' {
|
|||
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||
PlayerBar: typeof import('./../components/PlayerBar.vue')['default']
|
||||
QrLogin: typeof import('./../components/QrLogin.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SongTable: typeof import('./../components/SongTable.vue')['default']
|
||||
|
|
|
|||
|
|
@ -45,11 +45,14 @@
|
|||
/>
|
||||
</n-radio-group>
|
||||
</section>
|
||||
|
||||
<QrLogin />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { NCheckboxGroup, NRadioGroup } from "naive-ui"
|
||||
import QrLogin from "@/components/QrLogin.vue"
|
||||
import { ALL_SOURCES, SOURCE_LABELS, setDefaultSource, setEnabledSources, settingsState } from "@/stores/settings"
|
||||
</script>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue