style: 重写新页面

This commit is contained in:
ElyPrism 2026-08-02 21:48:01 +08:00
parent 4045f1ad3f
commit 22f8d7a95b
No known key found for this signature in database
23 changed files with 2826 additions and 3843 deletions

View File

@ -76,7 +76,7 @@
],
"dependencies": {
"@neteasecloudmusicapienhanced/unblockmusic-utils": "^0.3.4",
"axios": "^1.18.1",
"axios": "^1.19.0",
"crypto-js": "^4.2.0",
"dotenv": "^17.4.2",
"express": "^5.2.1",
@ -89,7 +89,7 @@
"safe-decode-uri-component": "^1.2.1",
"tunnel": "^0.0.6",
"xml2js": "^0.6.2",
"yargs": "^18.0.0"
"yargs": "^18.1.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.6",
@ -104,7 +104,7 @@
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-html": "^8.1.4",
"eslint-plugin-prettier": "^5.5.6",
"globals": "^17.7.0",
"globals": "^17.8.0",
"husky": "^9.1.7",
"intelli-espower-loader": "^1.1.0",
"lint-staged": "^16.4.0",

132
pnpm-lock.yaml generated
View File

@ -12,8 +12,8 @@ importers:
specifier: ^0.3.4
version: 0.3.4
axios:
specifier: ^1.18.1
version: 1.18.1
specifier: ^1.19.0
version: 1.19.0
crypto-js:
specifier: ^4.2.0
version: 4.2.0
@ -51,8 +51,8 @@ importers:
specifier: ^0.6.2
version: 0.6.2
yargs:
specifier: ^18.0.0
version: 18.0.0
specifier: ^18.1.0
version: 18.1.0
devDependencies:
'@eslint/eslintrc':
specifier: ^3.3.6
@ -91,8 +91,8 @@ importers:
specifier: ^5.5.6
version: 5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5))(eslint@9.39.5)(prettier@3.9.6)
globals:
specifier: ^17.7.0
version: 17.7.0
specifier: ^17.8.0
version: 17.8.0
husky:
specifier: ^9.1.7
version: 9.1.7
@ -271,8 +271,8 @@ packages:
'@types/express-fileupload@1.5.1':
resolution: {integrity: sha512-DllImBVI1lCyjl2klky/TEwk60mbNebgXv1669h66g9TfptWSrEFq5a/raHSutaFzjSm1tmn9ypdNfu4jPSixQ==}
'@types/express-serve-static-core@5.1.2':
resolution: {integrity: sha512-d3KvEXBSo/lOAMc2u6fkyDHBvetBHeqD7wm/AcXfLpSOQwlmG9D/aQ0SFswVjv05p7ullQS7Mjohj6/VdbZuTg==}
'@types/express-serve-static-core@5.1.3':
resolution: {integrity: sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==}
'@types/express@5.0.6':
resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==}
@ -386,8 +386,8 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
acorn@8.17.0:
resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==}
acorn@8.18.0:
resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==}
engines: {node: '>=0.4.0'}
hasBin: true
@ -485,8 +485,8 @@ packages:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
axios@1.18.1:
resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==}
axios@1.19.0:
resolution: {integrity: sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@ -517,14 +517,14 @@ packages:
resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==}
engines: {node: '>=18'}
brace-expansion@1.1.16:
resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==}
brace-expansion@1.1.18:
resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==}
brace-expansion@2.1.2:
resolution: {integrity: sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==}
brace-expansion@2.1.4:
resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==}
brace-expansion@5.0.8:
resolution: {integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==}
brace-expansion@5.0.9:
resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==}
engines: {node: 20 || >=22}
braces@3.0.3:
@ -1153,8 +1153,8 @@ packages:
flatstr@1.0.12:
resolution: {integrity: sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==}
flatted@3.4.3:
resolution: {integrity: sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==}
flatted@3.4.4:
resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==}
follow-redirects@1.16.0:
resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
@ -1262,8 +1262,8 @@ packages:
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
engines: {node: '>=18'}
globals@17.7.0:
resolution: {integrity: sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==}
globals@17.8.0:
resolution: {integrity: sha512-Zz/LMDZScFmkakeL2cTHzf+PbWKdpU3uclqkZT7TjDG58j5WPt0PpA+n9uPI24fZtlw07q0OtEi84K+umsRzqQ==}
engines: {node: '>=18'}
globalthis@1.0.4:
@ -1400,8 +1400,8 @@ packages:
resolution: {integrity: sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==}
engines: {node: '>=10'}
ip-address@10.2.0:
resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==}
ip-address@10.4.0:
resolution: {integrity: sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==}
engines: {node: '>= 12'}
ipaddr.js@1.9.1:
@ -1565,8 +1565,8 @@ packages:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
js-yaml@4.3.0:
resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==}
js-yaml@4.3.1:
resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==}
hasBin: true
jsesc@2.5.2:
@ -1699,8 +1699,8 @@ packages:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
minimatch@10.2.5:
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
minimatch@10.2.6:
resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==}
engines: {node: 18 || 20 || >=22}
minimatch@3.1.5:
@ -2158,8 +2158,8 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
sax@1.6.0:
resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==}
sax@1.6.1:
resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==}
engines: {node: '>=11.0.0'}
secure-json-parse@2.7.0:
@ -2400,8 +2400,8 @@ packages:
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
engines: {node: '>=6'}
tinyexec@1.2.4:
resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==}
tinyexec@1.3.0:
resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==}
engines: {node: '>=18'}
tinyglobby@0.2.17:
@ -2655,8 +2655,8 @@ packages:
resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==}
engines: {node: '>=12'}
yargs@18.0.0:
resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==}
yargs@18.1.0:
resolution: {integrity: sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==}
engines: {node: ^20.19.0 || ^22.12.0 || >=23}
yocto-queue@0.1.0:
@ -2718,7 +2718,7 @@ snapshots:
globals: 14.0.0
ignore: 5.3.2
import-fresh: 3.3.1
js-yaml: 4.3.0
js-yaml: 4.3.1
minimatch: 3.1.5
strip-json-comments: 3.1.1
transitivePeerDependencies:
@ -2775,7 +2775,7 @@ snapshots:
'@neteasecloudmusicapienhanced/unblockmusic-utils@0.3.4':
dependencies:
'@unblockneteasemusic/server': 0.28.0
axios: 1.18.1
axios: 1.19.0
dotenv: 17.4.2
express: 4.22.2
https: 1.0.0
@ -2831,7 +2831,7 @@ snapshots:
'@types/busboy': 1.5.4
'@types/express': 5.0.6
'@types/express-serve-static-core@5.1.2':
'@types/express-serve-static-core@5.1.3':
dependencies:
'@types/node': 25.9.2
'@types/qs': 6.15.1
@ -2841,7 +2841,7 @@ snapshots:
'@types/express@5.0.6':
dependencies:
'@types/body-parser': 1.19.6
'@types/express-serve-static-core': 5.1.2
'@types/express-serve-static-core': 5.1.3
'@types/serve-static': 2.2.0
'@types/http-errors@2.0.5': {}
@ -2934,7 +2934,7 @@ snapshots:
'@typescript-eslint/types': 8.65.0
'@typescript-eslint/visitor-keys': 8.65.0
debug: 4.4.3
minimatch: 10.2.5
minimatch: 10.2.6
semver: 7.8.5
tinyglobby: 0.2.17
ts-api-utils: 2.5.0(typescript@5.9.3)
@ -2976,13 +2976,13 @@ snapshots:
acorn-es7-plugin@1.1.7: {}
acorn-jsx@5.3.2(acorn@8.17.0):
acorn-jsx@5.3.2(acorn@8.18.0):
dependencies:
acorn: 8.17.0
acorn: 8.18.0
acorn@5.7.4: {}
acorn@8.17.0: {}
acorn@8.18.0: {}
agent-base@6.0.2:
dependencies:
@ -3072,7 +3072,7 @@ snapshots:
dependencies:
possible-typed-array-names: 1.1.0
axios@1.18.1:
axios@1.19.0:
dependencies:
follow-redirects: 1.16.0
form-data: 4.0.6
@ -3129,16 +3129,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
brace-expansion@1.1.16:
brace-expansion@1.1.18:
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
brace-expansion@2.1.2:
brace-expansion@2.1.4:
dependencies:
balanced-match: 1.0.2
brace-expansion@5.0.8:
brace-expansion@5.0.9:
dependencies:
balanced-match: 4.0.4
@ -3765,8 +3765,8 @@ snapshots:
espree@10.4.0:
dependencies:
acorn: 8.17.0
acorn-jsx: 5.3.2(acorn@8.17.0)
acorn: 8.18.0
acorn-jsx: 5.3.2(acorn@8.18.0)
eslint-visitor-keys: 4.2.1
esprima@2.7.3: {}
@ -3959,14 +3959,14 @@ snapshots:
flat-cache@4.0.1:
dependencies:
flatted: 3.4.3
flatted: 3.4.4
keyv: 4.5.4
flat@5.0.2: {}
flatstr@1.0.12: {}
flatted@3.4.3: {}
flatted@3.4.4: {}
follow-redirects@1.16.0: {}
@ -4085,7 +4085,7 @@ snapshots:
globals@14.0.0: {}
globals@17.7.0: {}
globals@17.8.0: {}
globalthis@1.0.4:
dependencies:
@ -4219,7 +4219,7 @@ snapshots:
from2: 2.3.0
p-is-promise: 3.0.0
ip-address@10.2.0: {}
ip-address@10.4.0: {}
ipaddr.js@1.9.1: {}
@ -4378,7 +4378,7 @@ snapshots:
joycon@3.1.1: {}
js-yaml@4.3.0:
js-yaml@4.3.1:
dependencies:
argparse: 2.0.1
@ -4418,7 +4418,7 @@ snapshots:
listr2: 9.0.5
picomatch: 4.0.5
string-argv: 0.3.2
tinyexec: 1.2.4
tinyexec: 1.3.0
yaml: 2.9.0
listr2@9.0.5:
@ -4498,17 +4498,17 @@ snapshots:
mimic-response@3.1.0: {}
minimatch@10.2.5:
minimatch@10.2.6:
dependencies:
brace-expansion: 5.0.8
brace-expansion: 5.0.9
minimatch@3.1.5:
dependencies:
brace-expansion: 1.1.16
brace-expansion: 1.1.18
minimatch@9.0.9:
dependencies:
brace-expansion: 2.1.2
brace-expansion: 2.1.4
minimist@1.2.8: {}
@ -4527,7 +4527,7 @@ snapshots:
glob: 10.5.0
he: 1.2.0
is-path-inside: 3.0.3
js-yaml: 4.3.0
js-yaml: 4.3.1
log-symbols: 4.1.0
minimatch: 9.0.9
ms: 2.1.3
@ -4602,7 +4602,7 @@ snapshots:
chokidar: 3.6.0
debug: 4.4.3(supports-color@5.5.0)
ignore-by-default: 1.0.1
minimatch: 10.2.5
minimatch: 10.2.6
pstree.remy: 1.1.8
semver: 7.8.5
simple-update-notifier: 2.0.0
@ -5073,7 +5073,7 @@ snapshots:
safer-buffer@2.1.2: {}
sax@1.6.0: {}
sax@1.6.1: {}
secure-json-parse@2.7.0: {}
@ -5233,7 +5233,7 @@ snapshots:
socks@2.8.9:
dependencies:
ip-address: 10.2.0
ip-address: 10.4.0
smart-buffer: 4.2.0
sonic-boom@1.4.1:
@ -5387,7 +5387,7 @@ snapshots:
inherits: 2.0.4
readable-stream: 3.6.2
tinyexec@1.2.4: {}
tinyexec@1.3.0: {}
tinyglobby@0.2.17:
dependencies:
@ -5620,7 +5620,7 @@ snapshots:
xml2js@0.6.2:
dependencies:
sax: 1.6.0
sax: 1.6.1
xmlbuilder: 11.0.1
xml@1.0.1: {}
@ -5687,12 +5687,12 @@ snapshots:
y18n: 5.0.8
yargs-parser: 21.1.1
yargs@18.0.0:
yargs@18.1.0:
dependencies:
cliui: 9.0.1
escalade: 3.2.0
get-caller-file: 2.0.5
string-width: 7.2.0
string-width: 8.2.2
y18n: 5.0.8
yargs-parser: 22.0.0

View File

@ -3,360 +3,229 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="docs/netease.png">
<link rel="stylesheet" href="/static/shell.css">
<title>API 调试界面</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-height: 100vh;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 12px;
padding: 32px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 24px;
}
form {
display: flex;
flex-direction: column;
gap: 16px;
margin-bottom: 24px;
}
.form-row {
display: flex;
gap: 12px;
align-items: center;
}
label {
font-size: 14px;
font-weight: 500;
color: #555;
min-width: 80px;
}
input, select {
padding: 10px 14px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
flex: 1;
outline: none;
}
input:focus, select:focus {
border-color: #333;
}
button {
background: #333;
color: white;
padding: 10px 24px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background 0.2s ease;
}
button:hover {
background: #555;
}
.data-result {
display: flex;
gap: 16px;
min-height: 400px;
}
.data-result > div {
flex: 1;
display: flex;
flex-direction: column;
}
.data-result label {
margin-bottom: 8px;
padding: 0;
}
textarea {
flex: 1;
padding: 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-family: 'Courier New', monospace;
font-size: 13px;
resize: vertical;
min-height: 350px;
outline: none;
}
textarea:focus {
border-color: #333;
}
/* ===== 自定义选项区域 ===== */
.section-container {
margin-top: 20px;
padding: 16px 20px;
background: #fafafa;
border: 1px solid #e8e8e8;
border-radius: 8px;
margin-top: 1.25rem;
padding: 1rem 1.25rem;
background: var(--gray-2);
border: 1px solid var(--border);
}
.section-container + .section-container {
margin-top: 12px;
}
.section-container + .section-container { margin-top: 0.75rem; }
.section-title {
font-size: 14px;
font-family: var(--font-mono);
font-size: 0.875rem;
font-weight: 600;
color: #555;
margin-bottom: 12px;
color: var(--foreground);
margin-bottom: 0.75rem;
display: flex;
align-items: center;
gap: 8px;
gap: 0.5rem;
}
.section-title .count-badge {
font-size: 11px;
background: #ddd;
color: #666;
padding: 1px 8px;
border-radius: 10px;
font-size: 0.6875rem;
background: var(--gray-4);
color: var(--muted-foreground);
padding: 0 0.5rem;
border-radius: 9999px;
font-weight: 400;
}
.dynamic-row {
display: flex;
gap: 8px;
margin-bottom: 8px;
gap: 0.5rem;
margin-bottom: 0.5rem;
align-items: center;
}
.dynamic-row .key-input {
flex: 0 0 200px;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 13px;
flex: 0 0 14rem;
padding: 0.5rem 0.75rem;
background: var(--gray-1);
border: 1px solid var(--input);
color: var(--foreground);
font-size: 0.8125rem;
font-family: var(--font-mono);
outline: none;
font-family: 'Courier New', monospace;
}
.dynamic-row .value-input {
flex: 1;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 13px;
padding: 0.5rem 0.75rem;
background: var(--gray-1);
border: 1px solid var(--input);
color: var(--foreground);
font-size: 0.8125rem;
outline: none;
}
.dynamic-row .key-input:focus,
.dynamic-row .value-input:focus {
border-color: #333;
}
.dynamic-row .value-input:focus { border-color: var(--ring); }
.remove-btn {
width: 28px;
height: 28px;
width: 1.75rem;
height: 1.75rem;
padding: 0;
background: #fff;
color: #999;
border: 1px solid #ddd;
border-radius: 6px;
background: var(--gray-2);
color: var(--muted-foreground);
border: 1px solid var(--border);
cursor: pointer;
font-size: 14px;
font-size: 0.875rem;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
transition: all var(--transition);
flex-shrink: 0;
}
.remove-btn:hover {
background: #fee;
color: #e33;
border-color: #fcc;
background: var(--destructive);
color: var(--gray-1);
border-color: var(--destructive);
}
.add-btn {
background: #fff;
color: #333;
border: 1px dashed #ccc;
padding: 6px 16px;
border-radius: 6px;
font-size: 13px;
background: transparent;
color: var(--foreground);
border: 1px dashed var(--border);
padding: 0.375rem 1rem;
font-family: var(--font-mono);
font-size: 0.8125rem;
cursor: pointer;
transition: all 0.2s;
margin-top: 4px;
transition: all var(--transition);
margin-top: 0.25rem;
}
.add-btn:hover {
border-color: #999;
background: #f5f5f5;
border-color: var(--foreground);
background: var(--gray-2);
}
/* ===== 参数编辑区 Tab ===== */
.param-container {
flex: 1;
display: flex;
flex-direction: column;
}
.param-container { flex: 1; display: flex; flex-direction: column; }
.param-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
margin-bottom: 0.5rem;
}
.param-header-title {
font-size: 14px;
font-family: var(--font-mono);
font-size: 0.875rem;
font-weight: 500;
color: #555;
color: var(--foreground);
}
.param-tabs {
display: flex;
gap: 0;
background: #f0f0f0;
border-radius: 6px;
background: var(--gray-2);
border: 1px solid var(--border);
padding: 2px;
}
.tab-btn {
padding: 4px 14px;
padding: 0.25rem 0.875rem;
border: none;
background: transparent;
color: #888;
font-size: 12px;
color: var(--muted-foreground);
font-family: var(--font-mono);
font-size: 0.75rem;
font-weight: 500;
cursor: pointer;
border-radius: 4px;
transition: all 0.2s;
transition: all var(--transition);
}
.tab-btn:hover {
color: #555;
}
.tab-btn:hover { color: var(--foreground); }
.tab-btn.active {
background: #fff;
color: #333;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
background: var(--foreground);
color: var(--background);
}
.param-panel { flex: 1; display: flex; flex-direction: column; }
.param-panel textarea { flex: 1; min-height: 21.875rem; }
.param-panel {
flex: 1;
display: flex;
flex-direction: column;
}
.param-panel textarea {
flex: 1;
padding: 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-family: 'Courier New', monospace;
font-size: 13px;
resize: vertical;
min-height: 350px;
outline: none;
}
.param-panel textarea:focus {
border-color: #333;
/* 双栏响应式 */
.data-result { display: flex; gap: 1rem; min-height: 25rem; }
.data-result > div { flex: 1; display: flex; flex-direction: column; }
.data-result textarea { flex: 1; min-height: 21.875rem; }
@media (max-width: 640px) {
.data-result { flex-direction: column; }
.dynamic-row .key-input { flex: 0 0 8rem; }
}
</style>
</head>
<body>
<div class="container">
<header class="shell-topbar">
<a class="brand" href="/"><img src="docs/netease.png" alt="logo">API 调试</a>
<nav>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/">返回首页</a>
</nav>
</header>
<main class="shell-main">
<div class="shell-container shell-narrow" style="max-width:none">
<header class="shell-header">
<h1>API 调试界面</h1>
<p class="sub">发送任意请求,支持自定义请求头、加密方式与 JSON / 键值对参数</p>
</header>
<section class="shell-card">
<form onsubmit="event.preventDefault(); sendRequest();">
<div class="form-row">
<label for="uri">URI</label>
<input type="text" id="uri" name="uri" value="/api/song/lyric/v1">
</div>
<div class="form-row">
<label for="method">请求方法</label>
<select id="method" name="method">
<option value="POST" selected>POST</option>
<option value="GET">GET</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
<option value="PATCH">PATCH</option>
</select>
</div>
<div class="form-row">
<label for="crypto">加密方式</label>
<select id="crypto" name="crypto">
<option value="weapi">weapi</option>
<option value="eapi">eapi</option>
<option value="api">api</option>
<option value="linuxapi">linuxapi</option>
<option value="xeapi">xeapi</option>
<option value="" selected>(默认)</option>
</select>
</div>
<div class="form-row">
<label for="timeout">超时(ms)</label>
<input type="number" id="timeout" name="timeout" value="30000" min="0" step="1000" placeholder="0 表示无超时">
</div>
<div class="form-row">
<label></label>
<button type="submit">发送请求</button>
</div>
<div class="shell-flex" style="margin-bottom:0.75rem">
<label for="uri" style="flex:0 0 5rem;margin:0">URI</label>
<input type="text" id="uri" name="uri" value="/api/song/lyric/v1">
</div>
<div class="shell-flex" style="margin-bottom:0.75rem">
<label for="method" style="flex:0 0 5rem;margin:0">请求方法</label>
<select id="method" name="method">
<option value="POST" selected>POST</option>
<option value="GET">GET</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
<option value="PATCH">PATCH</option>
</select>
</div>
<div class="shell-flex" style="margin-bottom:0.75rem">
<label for="crypto" style="flex:0 0 5rem;margin:0">加密方式</label>
<select id="crypto" name="crypto">
<option value="weapi">weapi</option>
<option value="eapi">eapi</option>
<option value="api">api</option>
<option value="linuxapi">linuxapi</option>
<option value="xeapi">xeapi</option>
<option value="" selected>(默认)</option>
</select>
</div>
<div class="shell-flex" style="margin-bottom:1rem">
<label for="timeout" style="flex:0 0 5rem;margin:0">超时(ms)</label>
<input type="number" id="timeout" name="timeout" value="30000" min="0" step="1000" placeholder="0 表示无超时">
</div>
<div class="shell-flex">
<span style="flex:0 0 5rem"></span>
<button type="submit" class="shell-btn shell-btn--default">发送请求</button>
</div>
</form>
</section>
<!-- ===== 自定义请求头 ===== -->
<div class="section-container" id="headersSection">
<div class="section-title">
请求头 (Headers)
<span class="count-badge" id="headerCount">4</span>
</div>
<div id="headersList"></div>
<button class="add-btn" onclick="addHeaderRow('', '')">+ 添加请求头</button>
<!-- ===== 自定义请求头 ===== -->
<section class="shell-card shell-mt-6">
<div class="section-title">
请求头 (Headers)
<span class="count-badge" id="headerCount">4</span>
</div>
<div id="headersList"></div>
<button class="add-btn" onclick="addHeaderRow('', '')">+ 添加请求头</button>
</section>
<section class="shell-card shell-mt-6">
<div class="data-result">
<div>
<label for="result">响应结果</label>
<textarea id="result" name="result" readonly></textarea>
<div>
<label for="result" style="font-family:var(--font-mono)">响应结果</label>
<textarea id="result" name="result" readonly></textarea>
</div>
<div class="param-container">
<div class="param-header">
<span class="param-header-title">请求参数</span>
<div class="param-tabs">
<button class="tab-btn active" data-mode="json" onclick="switchParamTab('json')">JSON</button>
<button class="tab-btn" data-mode="kv" onclick="switchParamTab('kv')">键值对</button>
</div>
</div>
<div class="param-container">
<div class="param-header">
<span class="param-header-title">请求参数</span>
<div class="param-tabs">
<button class="tab-btn active" data-mode="json" onclick="switchParamTab('json')">JSON</button>
<button class="tab-btn" data-mode="kv" onclick="switchParamTab('kv')">键值对</button>
</div>
</div>
<div class="param-panel" id="jsonPanel">
<textarea id="data" name="data">
<div class="param-panel" id="jsonPanel">
<textarea id="data" name="data">
{
"cp": false,
"id": "2058263032",
@ -368,17 +237,19 @@
"ytv": 0,
"yv": 0
}</textarea>
</div>
<div class="param-panel" id="kvPanel" style="display:none">
<div id="kvList"></div>
<button class="add-btn" onclick="addKvRow('', '')">+ 添加参数</button>
</div>
</div>
<div class="param-panel" id="kvPanel" style="display:none">
<div id="kvList"></div>
<button class="add-btn" onclick="addKvRow('', '')">+ 添加参数</button>
</div>
</div>
</div>
</section>
</div>
</main>
<script src="https://fastly.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
<script src="https://fastly.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
// ===== 动态行管理 =====
function escapeHtml(str) {
if (!str) return '';
@ -425,6 +296,12 @@
btn.parentElement.remove();
}
function updateCount() {
const count = document.querySelectorAll('#headersList .dynamic-row').length;
const badge = document.getElementById('headerCount');
if (badge) badge.textContent = count;
}
function collectHeaders() {
const headers = {};
document.querySelectorAll('#headersList .dynamic-row').forEach(function (row) {

View File

@ -3,468 +3,341 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="docs/netease.png" />
<link rel="stylesheet" href="/static/shell.css" />
<title>API 参数和返回内容解析</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-height: 100vh;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
background: white;
border-radius: 12px;
padding: 32px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 24px;
}
.form-group {
margin-bottom: 24px;
}
label {
display: block;
font-size: 14px;
font-weight: 500;
color: #555;
margin-bottom: 8px;
}
textarea {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-family: 'Courier New', monospace;
font-size: 13px;
resize: vertical;
min-height: 200px;
outline: none;
}
textarea:focus {
border-color: #333;
}
.radio-group {
display: flex;
gap: 24px;
margin-bottom: 24px;
}
.radio-item {
display: flex;
align-items: center;
gap: 8px;
}
.radio-item input[type="radio"] {
cursor: pointer;
}
.radio-item label {
margin: 0;
cursor: pointer;
font-size: 14px;
}
button {
background: #333;
color: white;
padding: 12px 28px;
border: none;
border-radius: 6px;
font-size: 15px;
font-weight: 500;
cursor: pointer;
transition: background 0.2s ease;
}
button + button {
margin-left: 12px;
}
button:hover {
background: #555;
}
.result-section {
margin-top: 24px;
}
.result-section label {
margin-bottom: 12px;
}
.decode-result {
white-space: pre-wrap;
word-break: break-all;
background: #f9f9f9;
padding: 16px;
border-radius: 6px;
border: 1px solid #eee;
min-height: 200px;
max-height: 400px;
overflow: auto;
font-family: 'Courier New', monospace;
font-size: 13px;
}
.decode-result .json-key { color: #881391; }
.decode-result .json-string { color: #1a7f37; }
.decode-result .json-number { color: #0550ae; }
.decode-result .json-boolean { color: #cf222e; }
.decode-result .json-null { color: #656d76; }
.example-section {
margin-top: 32px;
padding-top: 24px;
border-top: 1px solid #eee;
}
.example-section h2 {
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 16px;
}
.example-section img {
max-width: 100%;
height: auto;
border-radius: 6px;
margin-bottom: 16px;
border: 1px solid #eee;
}
/* new elements for multi-crypto support */
.crypto-bar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-bottom: 20px;
padding: 12px 16px;
background: #fafafa;
border-radius: 6px;
border: 1px solid #eee;
gap: 0.5rem;
margin-bottom: 1.25rem;
padding: 0.75rem 1rem;
background: var(--gray-2);
border: 1px solid var(--border);
}
.crypto-bar .bar-label {
font-size: 13px;
font-family: var(--font-mono);
font-size: 0.8125rem;
font-weight: 500;
color: #555;
margin-right: 8px;
color: var(--muted-foreground);
margin-right: 0.5rem;
}
.crypto-btn {
padding: 5px 14px;
border-radius: 4px;
border: 1px solid #ddd;
background: #fff;
padding: 0.3125rem 0.875rem;
border: 1px solid var(--border);
background: var(--gray-1);
cursor: pointer;
font-size: 13px;
font-family: var(--font-mono);
font-size: 0.8125rem;
font-weight: 500;
color: #666;
transition: all 0.15s ease;
color: var(--muted-foreground);
transition: all var(--transition);
}
.crypto-btn:hover {
border-color: #333;
color: #333;
background: #f5f5f5;
border-color: var(--foreground);
color: var(--foreground);
background: var(--gray-2);
}
.crypto-btn.active {
border-color: #333;
background: #333;
color: #fff;
border-color: var(--foreground);
background: var(--foreground);
color: var(--background);
}
.crypto-btn .badge {
display: inline-block;
font-size: 10px;
background: rgba(0,0,0,0.08);
border-radius: 4px;
padding: 0 5px;
margin-left: 3px;
font-size: 0.625rem;
background: color-mix(in srgb, var(--foreground) 12%, transparent);
border-radius: 2px;
padding: 0 0.3125rem;
margin-left: 0.1875rem;
color: inherit;
}
.crypto-btn.active .badge {
background: rgba(255,255,255,0.2);
background: color-mix(in srgb, var(--background) 15%, transparent);
}
.mode-row {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 20px;
gap: 1.25rem;
margin-bottom: 1.25rem;
flex-wrap: wrap;
}
.mode-row .mode-label {
font-size: 13px;
font-family: var(--font-mono);
font-size: 0.8125rem;
font-weight: 500;
color: #555;
color: var(--muted-foreground);
}
.mode-row .radio-group {
margin-bottom: 0;
display: flex;
gap: 1.5rem;
}
.radio-item {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
}
.radio-item input[type="radio"] { cursor: pointer; }
.radio-item label { margin: 0; cursor: pointer; }
.info-hint {
font-size: 12px;
color: #888;
margin-top: 6px;
padding: 6px 10px;
background: #fafafa;
border-radius: 4px;
border-left: 3px solid #999;
font-size: 0.75rem;
color: var(--muted-foreground);
margin-top: 0.375rem;
padding: 0.375rem 0.625rem;
background: var(--gray-2);
border-left: 3px solid var(--border);
}
.info-hint.warn {
border-left-color: #e67e22;
background: #fef9f0;
color: #d35400;
border-left-color: var(--warning);
background: color-mix(in srgb, var(--warning) 8%, var(--gray-2));
color: var(--warning);
}
.action-row {
display: flex;
gap: 8px;
gap: 0.5rem;
flex-wrap: wrap;
margin-bottom: 4px;
margin-bottom: 0.25rem;
}
.action-row button + button {
margin-left: 0;
}
button.btn-secondary {
background: #e8e8e8;
color: #444;
}
button.btn-secondary:hover {
background: #d5d5d5;
}
button.btn-secondary:disabled {
opacity: 0.4;
cursor: not-allowed;
pointer-events: none;
}
button.btn-success {
background: #4caf50;
}
button.btn-success:hover {
background: #43a047;
}
button.btn-success:disabled {
opacity: 0.4;
cursor: not-allowed;
pointer-events: none;
.decode-result {
white-space: pre-wrap;
word-break: break-all;
background: var(--gray-2);
padding: 1rem;
border: 1px solid var(--border);
min-height: 12.5rem;
max-height: 25rem;
overflow: auto;
font-family: var(--font-mono);
font-size: 0.8125rem;
}
/* JSON 语法高亮(深色适配) */
.decode-result .json-key { color: #79c0ff; }
.decode-result .json-string { color: #7ee787; }
.decode-result .json-number { color: #ffa657; }
.decode-result .json-boolean { color: #ff7b72; }
.decode-result .json-null { color: #8b949e; }
.result-section .section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.result-section .section-header label {
margin: 0;
margin-bottom: 0.625rem;
}
.result-section .section-header label { margin: 0; }
.copy-btn {
font-size: 12px;
padding: 3px 12px;
border-radius: 4px;
border: 1px solid #ddd;
background: #fff;
font-family: var(--font-mono);
font-size: 0.75rem;
padding: 0.1875rem 0.75rem;
border: 1px solid var(--border);
background: transparent;
cursor: pointer;
color: #666;
transition: all 0.15s;
color: var(--muted-foreground);
transition: all var(--transition);
}
.copy-btn:hover {
background: #333;
color: #fff;
border-color: #333;
background: var(--foreground);
color: var(--background);
border-color: var(--foreground);
}
.example-section {
margin-top: 2rem;
padding-top: 1.5rem;
border-top: 1px solid var(--border);
}
.example-section h2 {
font-family: var(--font-mono);
font-size: 1.125rem;
font-weight: 600;
margin-bottom: 1rem;
}
.example-section img {
max-width: 100%;
height: auto;
margin-bottom: 1rem;
border: 1px solid var(--border);
}
.example-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-top: 12px;
gap: 1rem;
margin-top: 0.75rem;
}
.example-card {
background: #fafafa;
border-radius: 6px;
padding: 16px;
border: 1px solid #eee;
background: var(--gray-2);
padding: 1rem;
border: 1px solid var(--border);
}
.example-card h3 {
font-size: 14px;
font-family: var(--font-mono);
font-size: 0.875rem;
font-weight: 600;
color: #555;
margin-bottom: 8px;
color: var(--foreground);
margin-bottom: 0.5rem;
}
.example-card pre {
font-family: 'Courier New', monospace;
font-size: 12px;
line-height: 1.5;
background: #fff;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
border: 1px solid #e8e8e8;
max-height: 120px;
overflow-y: auto;
margin-bottom: 8px;
background: var(--gray-1);
padding: 0.625rem;
overflow: auto;
border: 1px solid var(--border);
max-height: 7.5rem;
margin-bottom: 0.5rem;
font-size: 0.75rem;
}
.example-card .tag {
display: inline-block;
font-size: 10px;
font-family: var(--font-mono);
font-size: 0.625rem;
font-weight: 600;
padding: 2px 8px;
border-radius: 3px;
margin-bottom: 6px;
text-transform: uppercase;
padding: 0.125rem 0.5rem;
border: 1px solid var(--border);
margin-bottom: 0.375rem;
}
.tag-eapi { background: #e3f2fd; color: #1565c0; }
.tag-weapi { background: #fce4ec; color: #c62828; }
.tag-linuxapi { background: #e8f5e9; color: #2e7d32; }
.tag-xeapi { background: #f3e5f5; color: #7b1fa2; }
.tag-api { background: #fff3e0; color: #e65100; }
.tag-eapi { color: var(--info); border-color: color-mix(in srgb, var(--info) 50%, transparent); }
.tag-weapi { color: #ff7b72; border-color: color-mix(in srgb, #ff7b72 50%, transparent); }
.tag-linuxapi { color: var(--success); border-color: color-mix(in srgb, var(--success) 50%, transparent); }
.tag-xeapi { color: #d2a8ff; border-color: color-mix(in srgb, #d2a8ff 50%, transparent); }
.tag-api { color: var(--warning); border-color: color-mix(in srgb, var(--warning) 50%, transparent); }
@media (max-width: 640px) {
.example-grid { grid-template-columns: 1fr; }
.crypto-bar { gap: 4px; }
.crypto-btn { padding: 4px 10px; font-size: 12px; }
.crypto-bar { gap: 0.25rem; }
.crypto-btn { padding: 0.25rem 0.625rem; font-size: 0.75rem; }
}
</style>
</head>
<body>
<div id="app" class="container">
<h1>API 参数和返回内容解析</h1>
<header class="shell-topbar">
<a class="brand" href="/"><img src="docs/netease.png" alt="logo">API 解密</a>
<nav>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/api.html">API 调试</a>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/">返回首页</a>
</nav>
</header>
<!-- 加密方式选择 -->
<div class="crypto-bar">
<span class="bar-label">加密方式</span>
<button
v-for="c in cryptoList"
:key="c.value"
class="crypto-btn"
:class="{ active: crypto === c.value }"
@click="selectCrypto(c.value)"
>
{{ c.label }}
<span class="badge">{{ c.badge }}</span>
</button>
</div>
<main class="shell-main">
<div class="shell-container shell-narrow" style="max-width:56rem">
<div id="app">
<header class="shell-header">
<h1>API 参数和返回内容解析</h1>
<p class="sub">解密 eapi / weapi / linuxapi / xeapi 的请求与响应数据</p>
</header>
<div class="mode-row">
<span class="mode-label">数据类型</span>
<div class="radio-group">
<div class="radio-item">
<input
type="radio"
id="mode-req"
:value="true"
v-model="isReq"
:disabled="cryptoInfo.reqDisabled"
/>
<label for="mode-req">请求数据 request</label>
</div>
<div class="radio-item">
<input
type="radio"
id="mode-resp"
:value="false"
v-model="isReq"
:disabled="cryptoInfo.respDisabled"
/>
<label for="mode-resp">返回数据 response</label>
</div>
<!-- 加密方式选择 -->
<section class="shell-card">
<div class="crypto-bar">
<span class="bar-label">加密方式</span>
<button
v-for="c in cryptoList"
:key="c.value"
class="crypto-btn"
:class="{ active: crypto === c.value }"
@click="selectCrypto(c.value)"
>
{{ c.label }}
<span class="badge">{{ c.badge }}</span>
</button>
</div>
<div class="mode-row">
<span class="mode-label">数据类型</span>
<div class="radio-group">
<div class="radio-item">
<input
type="radio"
id="mode-req"
:value="true"
v-model="isReq"
:disabled="cryptoInfo.reqDisabled"
/>
<label for="mode-req">请求数据 request</label>
</div>
<div class="radio-item">
<input
type="radio"
id="mode-resp"
:value="false"
v-model="isReq"
:disabled="cryptoInfo.respDisabled"
/>
<label for="mode-resp">返回数据 response</label>
</div>
</div>
</div>
<div class="form-group">
<label for="dataInput">{{ cryptoInfo.inputLabel }}</label>
<textarea
id="dataInput"
v-model="encryptedData"
:rows="crypto === 'xeapi' ? 8 : 10"
:placeholder="cryptoInfo.placeholder"
></textarea>
<div class="info-hint" :class="{ warn: cryptoInfo.warning }">
{{ cryptoInfo.hint }}
</div>
</div>
<div class="action-row">
<button class="shell-btn shell-btn--default" @click="decrypt">解密</button>
<button class="shell-btn shell-btn--success" @click="sendToApi" :disabled="!canSend">
填入 API 调试
</button>
<button class="shell-btn shell-btn--outline" @click="loadExample">加载示例</button>
<button class="shell-btn shell-btn--outline" @click="clearAll">清空</button>
</div>
<div class="result-section shell-mt-4">
<div class="section-header">
<label>解密结果:</label>
<button
v-if="result && result !== '{}' && result !== 'null'"
class="copy-btn"
@click="copyResult"
>
复制
</button>
</div>
<pre class="decode-result" v-html="highlightedResult"></pre>
</div>
</section>
<section class="shell-card shell-mt-6">
<div class="example-section" style="margin-top:0;padding-top:0;border-top:none">
<h2>使用示例</h2>
<img src="/static/eapi_params.png" alt="请求示例" />
<img src="/static/eapi_response.png" alt="响应示例" />
<div class="example-grid">
<div class="example-card">
<span class="tag tag-eapi">eapi</span>
<h3>请求参数解密</h3>
<pre>输入 hex 字符串,解密后得到请求 URL 和参数</pre>
<button class="copy-btn" @click="loadExampleData('eapi_req')">加载示例</button>
</div>
<div class="example-card">
<span class="tag tag-linuxapi">linuxapi</span>
<h3>请求 eparams 解密</h3>
<pre>AES-ECB 解密 linuxapi 的 eparams 参数hex</pre>
<button class="copy-btn" @click="loadExampleData('linuxapi_req')">加载示例</button>
</div>
</div>
</div>
</section>
</div>
</div>
<div class="form-group">
<label for="dataInput">{{ cryptoInfo.inputLabel }}</label>
<textarea
id="dataInput"
v-model="encryptedData"
:rows="crypto === 'xeapi' ? 8 : 10"
:placeholder="cryptoInfo.placeholder"
></textarea>
<div class="info-hint" :class="{ warn: cryptoInfo.warning }">
{{ cryptoInfo.hint }}
</div>
</div>
<div class="action-row">
<button @click="decrypt">解密</button>
<button class="btn-success" @click="sendToApi" :disabled="!canSend">
填入 API 调试
</button>
<button class="btn-secondary" @click="loadExample">加载示例</button>
<button class="btn-secondary" @click="clearAll">清空</button>
</div>
<div class="result-section">
<div class="section-header">
<label>解密结果:</label>
<button
v-if="result && result !== '{}' && result !== 'null'"
class="copy-btn"
@click="copyResult"
>
复制
</button>
</div>
<pre class="decode-result" v-html="highlightedResult"></pre>
</div>
<div class="example-section">
<h2>使用示例</h2>
<img src="/static/eapi_params.png" alt="请求示例" />
<img src="/static/eapi_response.png" alt="响应示例" />
<div class="example-grid">
<div class="example-card">
<span class="tag tag-eapi">eapi</span>
<h3>请求参数解密</h3>
<pre>输入 hex 字符串,解密后得到请求 URL 和参数</pre>
<button class="copy-btn" @click="loadExampleData('eapi_req')">加载示例</button>
</div>
<div class="example-card">
<span class="tag tag-linuxapi">linuxapi</span>
<h3>请求 eparams 解密</h3>
<pre>AES-ECB 解密 linuxapi 的 eparams 参数hex</pre>
<button class="copy-btn" @click="loadExampleData('linuxapi_req')">加载示例</button>
</div>
</div>
</div>
</div>
</main>
<script src="https://fastly.jsdelivr.net/npm/axios"></script>
<script src="https://fastly.jsdelivr.net/npm/vue@3"></script>

View File

@ -1,221 +1,127 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/shell.css">
<title>听歌识曲 Demo</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-height: 100vh;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
border-radius: 12px;
padding: 32px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.subtitle {
font-size: 13px;
color: #666;
margin-bottom: 24px;
}
hr {
border: none;
border-top: 1px solid #eee;
margin: 20px 0;
}
p {
font-size: 14px;
color: #555;
line-height: 1.6;
margin-bottom: 12px;
}
a {
color: #333;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.section {
margin-bottom: 24px;
}
body { padding: 1.25rem; }
.subtitle { font-size: 0.8125rem; color: var(--muted-foreground); margin-bottom: 1.5rem; }
.subtitle a { color: var(--info); }
hr { border: none; border-top: 1px solid var(--border); margin: 1.25rem 0; }
p { font-size: 0.875rem; color: var(--muted-foreground); line-height: 1.6; margin-bottom: 0.75rem; }
a { color: var(--info); }
.section { margin-bottom: 1.5rem; }
.section h3 {
font-size: 16px;
font-family: var(--font-mono);
font-size: 1rem;
font-weight: 600;
color: #333;
margin-bottom: 12px;
margin-bottom: 0.75rem;
}
.control-group {
display: flex;
gap: 12px;
gap: 0.75rem;
flex-wrap: wrap;
align-items: center;
margin-bottom: 16px;
margin-bottom: 1rem;
}
button {
padding: 10px 20px;
background: #333;
color: white;
font-size: 14px;
font-weight: 500;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s ease;
}
button:hover {
background: #555;
}
button:disabled {
background: #999;
cursor: not-allowed;
}
input[type="file"] {
padding: 10px 14px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
padding: 0.625rem 0.875rem;
border: 1px solid var(--input);
border-radius: var(--radius);
font-size: 0.875rem;
background: var(--gray-1);
color: var(--foreground);
}
.checkbox-group {
display: flex;
align-items: center;
gap: 8px;
}
.checkbox-group input[type="checkbox"] {
cursor: pointer;
}
.checkbox-group label {
margin: 0;
cursor: pointer;
font-size: 14px;
color: #555;
}
audio {
width: 100%;
margin-bottom: 16px;
}
canvas {
width: 100%;
height: 0;
transition: all linear 0.1s;
background: #f9f9f9;
border-radius: 6px;
transition: height linear 0.1s;
background: var(--gray-2);
border: 1px solid var(--border);
}
.canvas-active {
height: 15vh;
}
.canvas-active { height: 15vh; }
pre {
font-family: 'Courier New', monospace;
font-size: 13px;
color: #666;
white-space: pre-wrap;
word-wrap: break-word;
max-height: 400px;
max-height: 25rem;
overflow-y: auto;
padding: 16px;
background: #f9f9f9;
border-radius: 6px;
border: 1px solid #eee;
}
pre a { color: var(--info); }
.warning {
padding: 12px 16px;
background: #fef3c7;
border-radius: 6px;
font-size: 14px;
color: #92400e;
margin-bottom: 16px;
padding: 0.75rem 1rem;
border: 1px solid var(--warning);
background: color-mix(in srgb, var(--warning) 8%, transparent);
font-size: 0.875rem;
color: var(--warning);
margin-bottom: 1rem;
}
.warning a { color: inherit; }
.warning strong { font-weight: 600; }
ul.steps { padding-left: 1.25rem; font-size: 0.875rem; color: var(--muted-foreground); }
</style>
</head>
<body>
<div class="container">
<h1>听歌识曲 Demo</h1>
<p class="subtitle">Credit: <a href="https://github.com/mos9527/ncm-afp" target="_blank">https://github.com/mos9527/ncm-afp</a></p>
<header class="shell-topbar">
<a class="brand" href="/"><img src="/docs/netease.png" alt="logo">听歌识曲</a>
<nav>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/">返回首页</a>
</nav>
</header>
<hr>
<main class="shell-main">
<div class="shell-container shell-narrow" style="max-width:50rem">
<header class="shell-header">
<h1>听歌识曲 Demo</h1>
<p class="subtitle">Credit: <a href="https://github.com/mos9527/ncm-afp" target="_blank">https://github.com/mos9527/ncm-afp</a></p>
</header>
<div class="warning">
<strong>免责声明:</strong>本站点使用网易云音乐官方音频识别API逆向自 <a href="https://fn.music.163.com/g/chrome-extension-home-page-beta/" target="_blank">Chrome 扩展页面</a>),不鼓励版权侵犯或知识产权盗窃。
</div>
<hr>
<div class="section">
<h3>使用说明</h3>
<p>在使用本站点之前,您可能需要先访问以下链接:</p>
<p><a href="https://cors-anywhere.herokuapp.com/corsdemo" target="_blank">https://cors-anywhere.herokuapp.com/corsdemo</a></p>
<p>由于网易云音乐API没有CORS头这是解决此限制的必要步骤。</p>
</div>
<div class="section">
<h3>使用方法</h3>
<ul style="padding-left: 20px; font-size: 14px; color: #555;">
<li>通过"选择文件"选择您的音频文件</li>
<li>点击"识别"按钮并等待结果</li>
</ul>
</div>
<hr>
<audio id="audio" controls autoplay></audio>
<canvas id="canvas"></canvas>
<div class="control-group">
<button id="invoke">识别</button>
<input type="file" name="picker" accept="*" id="file">
</div>
<div class="control-group">
<div class="checkbox-group">
<input type="checkbox" name="use-mic" id="usemic">
<label for="usemic">混合麦克风输入</label>
<div class="warning">
<strong>免责声明:</strong>本站点使用网易云音乐官方音频识别API逆向自 <a href="https://fn.music.163.com/g/chrome-extension-home-page-beta/" target="_blank">Chrome 扩展页面</a>),不鼓励版权侵犯或知识产权盗窃。
</div>
<section class="shell-card">
<div class="section">
<h3>使用说明</h3>
<p>在使用本站点之前,您可能需要先访问以下链接:</p>
<p><a href="https://cors-anywhere.herokuapp.com/corsdemo" target="_blank">https://cors-anywhere.herokuapp.com/corsdemo</a></p>
<p>由于网易云音乐API没有CORS头这是解决此限制的必要步骤。</p>
</div>
<div class="section">
<h3>使用方法</h3>
<ul class="steps">
<li>通过"选择文件"选择您的音频文件</li>
<li>点击"识别"按钮并等待结果</li>
</ul>
</div>
<hr>
<audio id="audio" controls autoplay></audio>
<canvas id="canvas"></canvas>
<div class="control-group shell-mt-3">
<button id="invoke" class="shell-btn shell-btn--default">识别</button>
<input type="file" name="picker" accept="*" id="file">
</div>
<div class="control-group">
<div class="checkbox-group">
<input type="checkbox" name="use-mic" id="usemic">
<label for="usemic">混合麦克风输入</label>
</div>
</div>
<hr>
<h3 style="font-family:var(--font-mono);font-size:1rem;font-weight:600;margin-bottom:0.75rem">日志</h3>
<pre id="logs"></pre>
</section>
</div>
<hr>
<h3 style="font-size: 16px; font-weight: 600; color: #333; margin-bottom: 12px;">日志</h3>
<pre id="logs"></pre>
</div>
</main>
</body>
<script src="./afp.wasm.js"></script>
@ -348,7 +254,7 @@
canvasCtx.fillStyle = 'rgba(0,0,0,0)';
canvasCtx.fillRect(0, 0, w, h);
if (audioBuffer) {
canvasCtx.fillStyle = 'black';
canvasCtx.fillStyle = '#e4e4e4'; /* 深色主题下用前景色绘制波形 */
for (var x = 0; x < w * bufferHealth; x++) {
var y = audioBuffer[Math.ceil((x / w) * audioBuffer.length)]
var z = Math.abs(y) * h / 2

View File

@ -4,120 +4,73 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="docs/netease.png" />
<link rel="stylesheet" href="/static/shell.css" />
<title>更新头像</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-height: 100vh;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
padding: 1.25rem;
}
.container {
background: white;
border-radius: 12px;
padding: 40px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
max-width: 400px;
width: 100%;
text-align: center;
}
h1 {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.subtitle {
font-size: 14px;
color: #666;
margin-bottom: 32px;
}
.shell-header { text-align: center; }
.avatar-wrapper {
position: relative;
width: 160px;
height: 160px;
margin: 0 auto 32px;
margin: 0 auto 2rem;
}
.avatar {
width: 100%;
height: 100%;
border-radius: 50%;
border-radius: 9999px;
object-fit: cover;
border: 3px solid #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border: 3px solid var(--gray-5);
background: var(--gray-4);
}
.avatar-wrapper.loading .avatar {
opacity: 0.5;
}
.avatar-wrapper.loading .avatar { opacity: 0.5; }
.loading-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 50%;
background: rgba(255, 255, 255, 0.9);
border-radius: 9999px;
background: color-mix(in srgb, var(--background) 90%, transparent);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.2s ease;
pointer-events: none;
}
.avatar-wrapper.loading .loading-overlay {
opacity: 1;
}
.avatar-wrapper.loading .loading-overlay { opacity: 1; }
.spinner {
width: 40px;
height: 40px;
border: 3px solid #e0e0e0;
border-top-color: #333;
border-radius: 50%;
width: 2.5rem;
height: 2.5rem;
border: 3px solid var(--gray-4);
border-top-color: var(--foreground);
border-radius: 9999px;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
to { transform: rotate(360deg); }
}
.upload-btn {
display: inline-block;
position: relative;
padding: 12px 28px;
background: #333;
color: white;
font-size: 15px;
padding: 0.75rem 1.75rem;
background: var(--primary);
color: var(--primary-foreground);
font-family: var(--font-mono);
font-size: 0.9375rem;
font-weight: 500;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s ease;
transition: opacity var(--transition);
border: none;
}
.upload-btn:hover {
background: #555;
}
.upload-btn:hover { opacity: 0.85; }
.upload-btn input[type="file"] {
position: absolute;
top: 0;
@ -127,67 +80,20 @@
opacity: 0;
cursor: pointer;
}
.login-link {
display: block;
margin-top: 24px;
color: #666;
font-size: 14px;
text-decoration: none;
}
.login-link:hover {
color: #333;
text-decoration: underline;
}
.toast {
position: fixed;
bottom: 24px;
left: 50%;
transform: translateX(-50%) translateY(100px);
padding: 12px 20px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
color: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
opacity: 0;
transition: all 0.3s ease;
z-index: 1000;
max-width: 90%;
text-align: center;
}
.toast.show {
transform: translateX(-50%) translateY(0);
opacity: 1;
}
.toast.success {
background: #10b981;
}
.toast.error {
background: #ef4444;
}
.toast.info {
background: #3b82f6;
}
.hint {
margin-top: 20px;
font-size: 12px;
color: #999;
margin-top: 1.25rem;
font-size: 0.75rem;
color: var(--muted-foreground);
}
</style>
</head>
<body>
<div class="container">
<h1>更新头像</h1>
<p class="subtitle">选择一张图片作为您的新头像</p>
<div class="shell-card shell-container shell-narrow" style="text-align:center;padding:2.5rem">
<header class="shell-header">
<h1>更新头像</h1>
<p class="sub">选择一张图片作为您的新头像</p>
</header>
<div class="avatar-wrapper" id="avatarWrapper">
<img id="avatar" class="avatar" src="" alt="头像" />
@ -196,16 +102,24 @@
</div>
</div>
<label class="upload-btn">
选择图片
<input id="file" type="file" accept="image/*" />
</label>
<div class="shell-flex shell-flex--center">
<label class="upload-btn">
选择图片
<input id="file" type="file" accept="image/*" />
</label>
</div>
<a href="/qrlogin-nocookie.html" class="login-link">
还没有登录?点击登录
</a>
<div class="shell-flex shell-flex--center shell-mt-4">
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/qrlogin-nocookie.html">
还没有登录?点击登录
</a>
</div>
<p class="hint">支持 JPG、PNG 格式,建议尺寸 200x200</p>
<div class="shell-flex shell-flex--center shell-mt-4">
<a class="shell-btn shell-btn--outline shell-btn--sm" href="/">返回首页</a>
</div>
</div>
<div id="toast" class="toast"></div>

View File

@ -3,276 +3,184 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="docs/netease.png" />
<link rel="stylesheet" href="/static/shell.css" />
<title>云盘上传</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-height: 100vh;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
border-radius: 12px;
padding: 32px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 24px;
}
.login-link {
display: block;
margin-bottom: 24px;
color: #666;
font-size: 14px;
text-decoration: none;
}
.login-link:hover {
color: #333;
text-decoration: underline;
}
.mode-section {
margin-bottom: 24px;
padding: 16px;
background: #f9f9f9;
border-radius: 8px;
margin-bottom: 1.5rem;
padding: 1rem;
background: var(--gray-2);
border: 1px solid var(--border);
}
.mode-section label {
display: block;
font-size: 14px;
font-size: 0.875rem;
font-weight: 500;
color: #333;
margin-bottom: 12px;
color: var(--foreground);
margin-bottom: 0.75rem;
}
.mode-options {
display: flex;
gap: 16px;
gap: 1rem;
flex-wrap: wrap;
}
.mode-option {
display: flex;
align-items: flex-start;
gap: 8px;
gap: 0.5rem;
cursor: pointer;
}
.mode-option input[type="radio"] {
margin-top: 3px;
}
.mode-option input[type="radio"] { margin-top: 3px; }
.mode-option-text {
display: flex;
flex-direction: column;
}
.mode-option-title {
font-size: 14px;
color: #333;
font-size: 0.875rem;
color: var(--foreground);
}
.mode-option-desc {
font-size: 12px;
color: #999;
font-size: 0.75rem;
color: var(--muted-foreground);
margin-top: 2px;
}
.mode-option input[type="radio"]:checked + .mode-option-text .mode-option-title {
color: #333;
font-weight: 500;
}
.upload-section {
margin-bottom: 32px;
}
.upload-section { margin-bottom: 2rem; }
.upload-btn {
display: inline-block;
padding: 12px 28px;
background: #333;
color: white;
font-size: 15px;
padding: 0.75rem 1.75rem;
background: var(--primary);
color: var(--primary-foreground);
font-family: var(--font-mono);
font-size: 0.9375rem;
font-weight: 500;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s ease;
transition: opacity var(--transition);
border: none;
}
.upload-btn:hover {
background: #555;
}
.upload-btn input[type="file"] {
display: none;
}
.upload-btn:hover { opacity: 0.85; }
.upload-btn input[type="file"] { display: none; }
.upload-btn.disabled {
background: #ccc;
opacity: 0.4;
cursor: not-allowed;
pointer-events: none;
}
.songs-list {
list-style: none;
}
.songs-list { list-style: none; }
.song-item {
padding: 12px 16px;
border-bottom: 1px solid #eee;
font-size: 14px;
color: #333;
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--border);
font-size: 0.875rem;
color: var(--foreground);
font-family: var(--font-mono);
}
.song-item:last-child {
border-bottom: none;
}
.song-item:last-child { border-bottom: none; }
.empty-state {
text-align: center;
padding: 40px 20px;
color: #999;
font-size: 14px;
padding: 2.5rem 1.25rem;
color: var(--muted-foreground);
font-size: 0.875rem;
}
.loading {
text-align: center;
padding: 20px;
color: #666;
}
.progress-section {
margin-bottom: 24px;
display: none;
}
.progress-section.active {
display: block;
padding: 1.25rem;
color: var(--muted-foreground);
}
.progress-section { margin-bottom: 1.5rem; display: none; }
.progress-section.active { display: block; }
.progress-item {
margin-bottom: 12px;
padding: 12px;
background: #f9f9f9;
border-radius: 6px;
margin-bottom: 0.75rem;
padding: 0.75rem;
background: var(--gray-2);
border: 1px solid var(--border);
}
.progress-item .name {
font-size: 14px;
color: #333;
margin-bottom: 8px;
font-family: var(--font-mono);
font-size: 0.875rem;
color: var(--foreground);
margin-bottom: 0.5rem;
word-break: break-all;
}
.progress-item .status {
font-size: 12px;
color: #666;
margin-bottom: 6px;
}
.progress-bar {
height: 6px;
background: #e0e0e0;
border-radius: 3px;
overflow: hidden;
}
.progress-bar .fill {
height: 100%;
background: #333;
border-radius: 3px;
transition: width 0.3s ease;
width: 0%;
}
.progress-item.success .fill {
background: #4caf50;
}
.progress-item.error .fill {
background: #f44336;
}
.progress-item.error .status {
color: #f44336;
font-size: 0.75rem;
color: var(--muted-foreground);
margin-bottom: 0.375rem;
}
.info-text {
font-size: 12px;
color: #999;
margin-top: 8px;
font-size: 0.75rem;
color: var(--muted-foreground);
margin-top: 0.5rem;
}
.warning-text {
font-size: 12px;
color: #e65100;
margin-top: 8px;
font-size: 0.75rem;
color: var(--warning);
margin-top: 0.5rem;
}
</style>
</head>
<body>
<div class="container">
<h1>云盘上传</h1>
<a href="/qrlogin-nocookie.html" class="login-link">还没登录?点击登录</a>
<header class="shell-topbar">
<a class="brand" href="/"><img src="docs/netease.png" alt="logo">云盘上传</a>
<nav>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/qrlogin-nocookie.html">登录</a>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/">返回首页</a>
</nav>
</header>
<div class="mode-section">
<label>上传模式</label>
<div class="mode-options">
<label class="mode-option">
<input type="radio" name="uploadMode" value="direct" checked />
<span class="mode-option-text">
<span class="mode-option-title">客户端直传</span>
<span class="mode-option-desc">文件直接上传到云存储,支持大文件,适合 Vercel 等平台</span>
</span>
</label>
<label class="mode-option">
<input type="radio" name="uploadMode" value="proxy" />
<span class="mode-option-text">
<span class="mode-option-title">后端代理</span>
<span class="mode-option-desc">文件通过服务器转发,更简洁,需要服务器支持大文件</span>
</span>
</label>
</div>
<main class="shell-main">
<div class="shell-container shell-narrow" style="max-width:50rem">
<header class="shell-header">
<h1>云盘上传</h1>
<p class="sub">上传本地音频文件到网易云音乐云盘</p>
</header>
<section class="shell-card">
<div class="mode-section">
<label>上传模式</label>
<div class="mode-options">
<label class="mode-option">
<input type="radio" name="uploadMode" value="direct" checked />
<span class="mode-option-text">
<span class="mode-option-title">客户端直传</span>
<span class="mode-option-desc">文件直接上传到云存储,支持大文件,适合 Vercel 等平台</span>
</span>
</label>
<label class="mode-option">
<input type="radio" name="uploadMode" value="proxy" />
<span class="mode-option-text">
<span class="mode-option-title">后端代理</span>
<span class="mode-option-desc">文件通过服务器转发,更简洁,需要服务器支持大文件</span>
</span>
</label>
</div>
</div>
<div class="upload-section">
<label class="upload-btn" id="uploadBtn">
选择文件(支持多选)
<input id="file" type="file" multiple accept="audio/*" />
</label>
<p class="info-text" id="modeInfo">支持大文件上传,文件将直接传输到云存储服务器</p>
</div>
<div id="progressSection" class="progress-section"></div>
<div id="app">
<div v-if="loading" class="loading">加载中...</div>
<ul v-else-if="songs.length > 0" class="songs-list">
<li v-for="(item, index) in songs" :key="index" class="song-item">
{{ item.songName }}
</li>
</ul>
<div v-else class="empty-state">暂无云盘歌曲</div>
</div>
</section>
</div>
<div class="upload-section">
<label class="upload-btn" id="uploadBtn">
选择文件(支持多选)
<input id="file" type="file" multiple accept="audio/*" />
</label>
<p class="info-text" id="modeInfo">支持大文件上传,文件将直接传输到云存储服务器</p>
</div>
<div id="progressSection" class="progress-section"></div>
<div id="app">
<div v-if="loading" class="loading">加载中...</div>
<ul v-else-if="songs.length > 0" class="songs-list">
<li v-for="(item, index) in songs" :key="index" class="song-item">
{{ item.songName }}
</li>
</ul>
<div v-else class="empty-state">暂无云盘歌曲</div>
</div>
</div>
</main>
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
<script src="https://fastly.jsdelivr.net/npm/vue@3"></script>

View File

@ -4,196 +4,184 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="icon" href="docs/netease.png">
<link rel="stylesheet" href="/static/shell.css">
<title>免费听 — 网易云音乐 API Enhanced</title>
<style>
:root {
--fg: #333;
--muted: #666;
--border: #ddd;
--bg: #f5f5f5;
--panel: #ffffff;
--accent: #333;
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: var(--fg); background: var(--bg); line-height: 1.6; }
.container { max-width: 800px; margin: 40px auto; padding: 0 20px; }
@media (max-width: 480px) {
.container { margin: 20px auto; padding: 0 16px; }
header.site-header h1 { font-size: 22px; }
.block { padding: 16px; }
}
header.site-header { margin-bottom: 24px; }
header.site-header h1 { font-size: 24px; font-weight: 600; margin: 0; }
.sub { margin-top: 4px; color: var(--muted); font-size: 14px; }
.block { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 20px; margin-bottom: 16px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); }
.block h3 { margin: 0 0 4px; font-size: 15px; font-weight: 600; }
.block .desc { margin: 0 0 12px; color: var(--muted); font-size: 13px; }
pre { margin: 0; background: #f9f9f9; border: 1px solid var(--border); border-radius: 6px; padding: 12px; overflow-x: auto; white-space: pre-wrap; word-break: break-all; font-family: 'Courier New', monospace; font-size: 13px; max-height: 200px; overflow: auto; }
pre.data { max-height: 360px; }
.row { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
button { padding: 8px 18px; border-radius: 8px; border: 1px solid var(--border); font-size: 14px; font-weight: 500; cursor: pointer; background: var(--panel); color: var(--fg); transition: all .15s; }
button:hover { background: #eee; }
button.primary { background: var(--fg); color: var(--panel); border-color: var(--fg); }
button.primary:hover { opacity: .85; }
button:disabled { opacity: .4; cursor: not-allowed; }
button.danger { background: #a11; color: #fff; border-color: #a11; }
button.danger:hover { opacity: .85; }
.tag { display: inline-block; padding: 2px 10px; border-radius: 20px; font-size: 12px; font-weight: 500; border: 1px solid var(--border); }
.tag.ok { background: #e6f7e6; color: #1a7a1a; border-color: #b7e6b7; }
.tag.no { background: #ffe6e6; color: #a11; border-color: #f5c6c6; }
.tag.busy { background: #fff3cd; color: #856404; border-color: #ffeeba; }
.flex { display: flex; align-items: center; gap: 10px; }
body { padding: 1.25rem; }
.block { margin-bottom: 1rem; }
.block h3 { font-size: 0.9375rem; font-weight: 600; margin: 0; }
.block .desc { margin: 0.25rem 0 0.75rem; color: var(--muted-foreground); font-size: 0.8125rem; }
pre.data { max-height: 22.5rem; }
.flex { display: flex; align-items: center; gap: 0.625rem; }
.flex.sb { justify-content: space-between; }
.step { display: flex; gap: 12px; align-items: flex-start; }
.step .num { width: 26px; height: 26px; border-radius: 50%; background: var(--fg); color: var(--panel); display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 600; flex-shrink: 0; margin-top: 1px; }
.step .num.done { background: #1a7a1a; }
.step { display: flex; gap: 0.75rem; align-items: flex-start; }
.step .num {
width: 1.625rem;
height: 1.625rem;
border-radius: 9999px;
background: var(--foreground);
color: var(--background);
display: flex;
align-items: center;
justify-content: center;
font-family: var(--font-mono);
font-size: 0.8125rem;
font-weight: 600;
flex-shrink: 0;
margin-top: 1px;
}
.step .num.done { background: var(--success); }
.step .body { flex: 1; min-width: 0; }
input[type=text] { width: 100%; padding: 8px 12px; border-radius: 6px; border: 1px solid var(--border); font-size: 14px; font-family: inherit; margin-top: 6px; }
input[type=text]:focus { outline: 2px solid var(--fg); outline-offset: -1px; border-color: transparent; }
footer.site-footer { margin-top: 32px; padding-top: 12px; border-top: 1px solid var(--border); color: var(--muted); text-align: center; font-size: 13px; }
footer.site-footer a { color: var(--fg); text-decoration: none; }
footer.site-footer a:hover { border-bottom: 1px dotted var(--fg); }
.info-grid { display: grid; grid-template-columns: 80px 1fr; gap: 4px 12px; font-size: 13px; margin-top: 8px; }
.info-grid .lbl { color: var(--muted); }
.info-grid { display: grid; grid-template-columns: 5rem 1fr; gap: 0.25rem 0.75rem; font-size: 0.8125rem; margin-top: 0.5rem; }
.info-grid .lbl { color: var(--muted-foreground); }
/* ---- 剩余时长计时器 ---- */
.timer-wrap { text-align: center; padding: 12px 0 4px; }
.timer-wrap { text-align: center; padding: 0.75rem 0 0.25rem; }
.timer-row { display: flex; align-items: baseline; justify-content: center; gap: 2px; font-variant-numeric: tabular-nums; }
.timer-num { font-size: 36px; font-weight: 700; letter-spacing: 1px; line-height: 1.2; font-family: 'SF Mono', 'Courier New', monospace; min-width: 2ch; text-align: center; }
.timer-label { font-size: 13px; color: var(--muted); margin-right: 12px; }
.timer-num { font-size: 2.25rem; font-weight: 700; letter-spacing: 1px; line-height: 1.2; font-family: var(--font-mono); min-width: 2ch; text-align: center; }
.timer-label { font-size: 0.8125rem; color: var(--muted-foreground); margin-right: 0.75rem; }
.timer-label:last-child { margin-right: 0; }
.timer-sep { font-size: 28px; font-weight: 300; color: #bbb; margin: 0 2px; }
.timer-sep { font-size: 1.75rem; font-weight: 300; color: var(--gray-5); margin: 0 2px; }
.timer-unit { display: flex; flex-direction: column; align-items: center; }
</style>
</head>
<body>
<main class="container">
<header class="site-header">
<h1>🎧 免费听 · 看广告获取时长</h1>
<p class="sub">模拟 App 内看激励视频广告 → 领取免费听权益的完整流程</p>
</header>
<header class="shell-topbar">
<a class="brand" href="/"><img src="docs/netease.png" alt="logo">免费听</a>
<nav>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/">返回首页</a>
</nav>
</header>
<!-- 步骤 1: 注册 Token -->
<div class="block">
<div class="step">
<div class="num" id="s1num">1</div>
<div class="body">
<div class="flex sb">
<h3>注册反作弊 Token</h3>
<span id="s1tag" class="tag no">未开始</span>
</div>
<p class="desc">调用易盾接口获取实时 token广告接口需要此 token 才能正常响应</p>
<pre id="s1pre">(尚未执行)</pre>
<div class="row">
<button id="step1" class="primary">① 注册 Token</button>
<main class="shell-main">
<div class="shell-container shell-narrow" style="max-width:48rem">
<header class="shell-header">
<h1>🎧 免费听 · 看广告获取时长</h1>
<p class="sub">模拟 App 内看激励视频广告 → 领取免费听权益的完整流程</p>
</header>
<!-- 步骤 1: 注册 Token -->
<section class="shell-card block">
<div class="step">
<div class="num" id="s1num">1</div>
<div class="body">
<div class="flex sb">
<h3>注册反作弊 Token</h3>
<span id="s1tag" class="tag no">未开始</span>
</div>
<p class="desc">调用易盾接口获取实时 token广告接口需要此 token 才能正常响应</p>
<pre id="s1pre">(尚未执行)</pre>
<div class="shell-flex shell-mt-3">
<button id="step1" class="shell-btn shell-btn--default">① 注册 Token</button>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 步骤 2: 获取广告 -->
<div class="block">
<div class="step">
<div class="num" id="s2num">2</div>
<div class="body">
<div class="flex sb">
<h3>获取广告 &amp; 提取 reqId</h3>
<span id="s2tag" class="tag no">未开始</span>
</div>
<p class="desc">请求广告位,从返回的 extJson.contextInfo.req_id 提取用于领取权益的标识</p>
<pre id="s2pre" class="data">(尚未执行)</pre>
<div class="row">
<button id="step2" class="primary" disabled>② 获取广告</button>
<button id="step2b" disabled>换一个广告位</button>
</div>
<div class="info-grid" id="s2info" style="display:none">
<div class="lbl">广告 ID</div><div id="s2adId">-</div>
<div class="lbl">reqId</div><div id="s2reqId">-</div>
<div class="lbl">文案</div><div id="s2text">-</div>
<!-- 步骤 2: 获取广告 -->
<section class="shell-card block">
<div class="step">
<div class="num" id="s2num">2</div>
<div class="body">
<div class="flex sb">
<h3>获取广告 &amp; 提取 reqId</h3>
<span id="s2tag" class="tag no">未开始</span>
</div>
<p class="desc">请求广告位,从返回的 extJson.contextInfo.req_id 提取用于领取权益的标识</p>
<pre id="s2pre" class="data">(尚未执行)</pre>
<div class="shell-flex shell-mt-3">
<button id="step2" class="shell-btn shell-btn--default" disabled>② 获取广告</button>
<button id="step2b" class="shell-btn shell-btn--outline" disabled>换一个广告位</button>
</div>
<div class="info-grid" id="s2info" style="display:none">
<div class="lbl">广告 ID</div><div id="s2adId">-</div>
<div class="lbl">reqId</div><div id="s2reqId">-</div>
<div class="lbl">文案</div><div id="s2text">-</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 步骤 3: 领取权益 -->
<div class="block">
<div class="step">
<div class="num" id="s3num">3</div>
<div class="body">
<div class="flex sb">
<h3>领取免费听权益</h3>
<span id="s3tag" class="tag no">未开始</span>
</div>
<p class="desc">传入 reqId 及相关参数,领取免费听权益(模拟点击广告后的领取请求)</p>
<pre id="s3pre" class="data">(尚未执行)</pre>
<div class="row">
<button id="step3" class="primary" disabled>③ 领取权益</button>
</div>
<div class="info-grid" id="s3info" style="display:none">
<div class="lbl">结果</div><div id="s3result">-</div>
<!-- 步骤 3: 领取权益 -->
<section class="shell-card block">
<div class="step">
<div class="num" id="s3num">3</div>
<div class="body">
<div class="flex sb">
<h3>领取免费听权益</h3>
<span id="s3tag" class="tag no">未开始</span>
</div>
<p class="desc">传入 reqId 及相关参数,领取免费听权益(模拟点击广告后的领取请求)</p>
<pre id="s3pre" class="data">(尚未执行)</pre>
<div class="shell-flex shell-mt-3">
<button id="step3" class="shell-btn shell-btn--default" disabled>③ 领取权益</button>
</div>
<div class="info-grid" id="s3info" style="display:none">
<div class="lbl">结果</div><div id="s3result">-</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 一键全流程 -->
<div class="block">
<div class="flex sb">
<h3>一键运行</h3>
<span id="allTag" class="tag no">就绪</span>
</div>
<p class="desc" style="margin-top:4px">按顺序自动执行以上三个步骤</p>
<div class="row">
<button id="runAll" class="primary">▶ 一键获取免费听时长</button>
<button id="clearAll" class="danger">✕ 清空</button>
</div>
</div>
<!-- 一键全流程 -->
<section class="shell-card block">
<div class="flex sb">
<h3>一键运行</h3>
<span id="allTag" class="tag no">就绪</span>
</div>
<p class="desc" style="margin-top:4px">按顺序自动执行以上三个步骤</p>
<div class="shell-flex shell-mt-3">
<button id="runAll" class="shell-btn shell-btn--default">▶ 一键获取免费听时长</button>
<button id="clearAll" class="shell-btn shell-btn--danger">✕ 清空</button>
</div>
</section>
<!-- 剩余时长计时器 -->
<div class="block" id="rightsBlock">
<div class="flex sb">
<h3 id="rightsTitleDisplay">🕐 免费听时长剩余</h3>
<span id="rightsTag" class="tag no">未查询</span>
</div>
<p class="desc" id="rightsSubDesc">查询你的免费听权益剩余时长</p>
<div class="timer-wrap">
<div class="timer-row" id="timerRow">
<div class="timer-unit">
<span class="timer-num" id="timerDays">--</span>
<span class="timer-label"></span>
</div>
<span class="timer-sep">:</span>
<div class="timer-unit">
<span class="timer-num" id="timerHours">--</span>
<span class="timer-label"></span>
</div>
<span class="timer-sep">:</span>
<div class="timer-unit">
<span class="timer-num" id="timerMinutes">--</span>
<span class="timer-label"></span>
</div>
<span class="timer-sep">:</span>
<div class="timer-unit">
<span class="timer-num" id="timerSeconds">--</span>
<span class="timer-label"></span>
<!-- 剩余时长计时器 -->
<section class="shell-card block">
<div class="flex sb">
<h3 id="rightsTitleDisplay">🕐 免费听时长剩余</h3>
<span id="rightsTag" class="tag no">未查询</span>
</div>
<p class="desc" id="rightsSubDesc">查询你的免费听权益剩余时长</p>
<div class="timer-wrap">
<div class="timer-row" id="timerRow">
<div class="timer-unit">
<span class="timer-num" id="timerDays">--</span>
<span class="timer-label"></span>
</div>
<span class="timer-sep">:</span>
<div class="timer-unit">
<span class="timer-num" id="timerHours">--</span>
<span class="timer-label"></span>
</div>
<span class="timer-sep">:</span>
<div class="timer-unit">
<span class="timer-num" id="timerMinutes">--</span>
<span class="timer-label"></span>
</div>
<span class="timer-sep">:</span>
<div class="timer-unit">
<span class="timer-num" id="timerSeconds">--</span>
<span class="timer-label"></span>
</div>
</div>
</div>
</div>
<div class="row" style="justify-content:center">
<button id="queryRightsBtn" class="primary">⟳ 查询剩余时长</button>
<button id="claim30Btn">领取30分钟</button>
</div>
<div class="info-grid" id="rightsInfo" style="display:none">
<div class="lbl">状态</div><div id="rightsStatus">-</div>
<div class="lbl">结束时间</div><div id="rightsEndTimeDisplay">-</div>
<div class="lbl">今日已覆盖</div><div id="rightsCoverToday">-</div>
</div>
</div>
<div class="shell-flex shell-flex--center shell-mt-3">
<button id="queryRightsBtn" class="shell-btn shell-btn--default">⟳ 查询剩余时长</button>
<button id="claim30Btn" class="shell-btn shell-btn--outline">领取30分钟</button>
</div>
<div class="info-grid" id="rightsInfo" style="display:none">
<div class="lbl">状态</div><div id="rightsStatus">-</div>
<div class="lbl">结束时间</div><div id="rightsEndTimeDisplay">-</div>
<div class="lbl">今日已覆盖</div><div id="rightsCoverToday">-</div>
</div>
</section>
<footer class="site-footer">
<a href="/">← 返回首页</a>
</footer>
<footer class="shell-footer">
<a href="/">← 返回首页</a>
</footer>
</div>
</main>
<script>

View File

@ -3,12 +3,31 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/static/shell.css" />
<title>home</title>
<style>
body {
display: flex;
align-items: center;
justify-content: center;
padding: 1.25rem;
}
.shell-header { text-align: center; }
</style>
</head>
<body>
<div>
<a href="/qrlogin-nocookie.html"> 如果没登录,请先登录 </a>
<div class="shell-card shell-container shell-narrow" style="text-align:center;padding:2.5rem">
<header class="shell-header">
<h1>首页数据</h1>
<p class="sub">获取网易云音乐首页推荐板块数据</p>
</header>
<div class="shell-flex shell-flex--center">
<a class="shell-btn shell-btn--outline shell-btn--sm" href="/qrlogin-nocookie.html">
如果没登录,请先登录
</a>
</div>
</div>
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
<script>

View File

@ -5,108 +5,85 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="icon" href="docs/netease.png">
<link rel="stylesheet" href="/static/shell.css">
<title>网易云音乐 API Enhanced</title>
<style>
:root {
--fg: #333;
--muted: #666;
--border: #ddd;
--bg: #f5f5f5;
--panel: #ffffff;
--accent: #333;
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: var(--fg); background: var(--bg); line-height: 1.6; }
.container { max-width: 960px; margin: 40px auto; padding: 0 20px; }
@media (max-width: 480px) {
.container { margin: 20px auto; padding: 0 16px; }
header.site-header h1 { font-size: 22px; }
.block { padding: 16px; }
}
header.site-header { margin-bottom: 24px; }
header.site-header h1 { font-size: 28px; font-weight: 600; margin: 0; }
.badge { display: inline-block; margin-left: 8px; padding: 4px 10px; border: 1px solid var(--border); border-radius: 12px; font-size: 12px; color: var(--muted); }
.sub { margin-top: 8px; color: var(--muted); font-size: 14px; }
.block { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 20px; margin-bottom: 16px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); }
.block h2 { margin: 0 0 12px; font-size: 18px; font-weight: 600; }
.kvs { display: grid; grid-template-columns: 100px 1fr; gap: 8px 12px; align-items: start; }
.kvs div:first-child { color: var(--muted); flex-shrink: 0; }
.kvs div:last-child { word-break: break-all; overflow-wrap: anywhere; min-width: 0; overflow: hidden; }
@media (max-width: 480px) {
.kvs { grid-template-columns: 1fr; gap: 4px 12px; }
.kvs div:first-child { font-weight: 500; }
}
ul.links { list-style: none; padding: 0; margin: 0; }
ul.links li { margin: 8px 0; }
ul.links a { color: var(--fg); text-decoration: none; border-bottom: 1px dotted var(--border); transition: all 0.2s ease; }
ul.links a:hover { color: var(--accent); border-bottom-color: var(--accent); }
pre { margin: 0; background: #f9f9f9; border: 1px solid var(--border); border-radius: 6px; padding: 12px; overflow-x: auto; white-space: pre-wrap; word-break: break-all; }
code { font-family: 'Courier New', monospace; font-size: 13px; }
@media (max-width: 480px) {
code { font-size: 12px; }
}
footer.site-footer { margin-top: 24px; padding-top: 12px; border-top: 1px solid var(--border); color: var(--muted); text-align: center; }
footer.site-footer a { color: var(--fg); text-decoration: none; transition: color 0.2s ease; }
footer.site-footer a:hover { color: var(--accent); }
.kvs { display: grid; grid-template-columns: 7rem 1fr; gap: 0.375rem 0.75rem; align-items: start; font-size: 0.875rem; }
.kvs .k { color: var(--muted-foreground); }
.kvs .v { word-break: break-all; overflow-wrap: anywhere; min-width: 0; font-family: var(--font-mono); font-size: 0.8125rem; }
ul.links { list-style: none; }
ul.links li { margin: 0.625rem 0; }
ul.links a { display: flex; justify-content: space-between; align-items: center; gap: 0.75rem; border-bottom: 1px solid var(--gray-4); padding-bottom: 0.375rem; }
ul.links a:hover { text-decoration: none; border-bottom-color: var(--foreground); }
ul.links code { color: var(--muted-foreground); font-size: 0.8125rem; }
.chips { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.75rem; }
.chips a { border: 1px solid var(--border); padding: 0.25rem 0.625rem; font-size: 0.8125rem; color: var(--foreground); }
.chips a:hover { background: var(--foreground); color: var(--background); text-decoration: none; }
</style>
</head>
<body>
<main class="container">
<header class="site-header">
<h1>网易云音乐 API Enhanced <span id="api-version" class="badge"></span></h1>
<p class="sub">🔍 A revival project for NeteaseCloudMusicApi Node.js Api Services || 网易云音乐 API 备份 + 增强 || 本项目自原版v4.28.0版本后开始自行维护</p>
</header>
<header class="shell-topbar">
<a class="brand" href="/"><img src="docs/netease.png" alt="logo">网易云音乐 API Enhanced</a>
<nav>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/docs" target="_blank">文档</a>
<a class="shell-btn shell-btn--default shell-btn--sm" href="/api.html">API 调试</a>
</nav>
</header>
<section class="block">
<h2>状态</h2>
<div class="kvs">
<div>Base URL</div><div id="base-url"></div>
<div>当前页</div><div id="current-url"></div>
<main class="shell-main">
<div class="shell-container">
<header class="shell-header">
<h1>网易云音乐 API Enhanced <span id="api-version" class="shell-badge shell-badge--primary"></span></h1>
<p class="sub">🔍 A revival project for NeteaseCloudMusicApi Node.js Api Services || 网易云音乐 API 备份 + 增强 || 本项目自原版v4.28.0版本后开始自行维护</p>
</header>
<div class="shell-grid">
<section class="shell-card" style="border:0;">
<h3>状态</h3>
<div class="kvs">
<div class="k">Base URL</div><div class="v" id="base-url"></div>
<div class="k">当前页</div><div class="v" id="current-url"></div>
</div>
</section>
<section class="shell-card" style="border:0;">
<h3>常用接口</h3>
<ul class="links">
<li><a href="/search?keywords=妖精小姐的魔法邀约">搜索音乐<code>GET /search</code></a></li>
<li><a href="/song/detail?ids=2756058128">获取音乐详情<code>GET /song/detail</code></a></li>
<li><a href="/comment/music?id=2756058128&amp;limit=1">获取音乐评论<code>GET /comment/music</code></a></li>
<li><a href="/song/url/v1?id=2756058128&amp;level=exhigh">获取音乐播放链接<code>GET /song/url/v1</code></a></li>
</ul>
</section>
</div>
</section>
<section class="block">
<h2>文档</h2>
<p><a href="/docs" target="_blank">查看在线文档</a></p>
</section>
<section class="block">
<h2>常用接口</h2>
<ul class="links">
<li><a href="/search?keywords=妖精小姐的魔法邀约">搜索音乐: <code>GET /search</code></a></li>
<li><a href="/song/detail?ids=2756058128">获取音乐详情: <code>GET /song/detail</code></a></li>
<li><a href="/comment/music?id=2756058128&limit=1">获取音乐评论: <code>GET /comment/music</code></a></li>
<li><a href="/song/url/v1?id=2756058128&level=exhigh">获取音乐播放链接: <code>GET /song/url/v1</code></a></li>
</ul>
</section>
<section class="block">
<h2>调试部分</h2>
<pre><code>curl -s {origin}/inner/version
<section class="shell-card shell-mt-6">
<h3>调试部分</h3>
<pre><code>curl -s {origin}/inner/version
curl -s {origin}/search?keywords=网易云</code></pre>
<div style="margin-top:10px; line-height:2;">
<a href="/api.html">交互式调试</a> ·
<a href="/qrlogin.html">二维码登录示例</a> ·
<a href="/unblock_test.html">解灰测试</a> ·
<a href="/audio_match_demo/index.html">听歌识曲 Demo</a> ·
<a href="/cloud.html">云盘上传</a> ·
<a href="/playlist_import.html">歌单导入</a> ·
<a href="/api_decrypt.html">API 解密</a> ·
<a href="/listen_together_host.html">一起听示例</a> ·
<a href="/playlist_cover_update.html">更新歌单封面示例</a> ·
<a href="/avatar_update.html">头像更新示例</a> ·
<a href="/scrobble.html">听歌打卡示例</a> ·
<a href="/yidun.html">获取 CheckToken</a> ·
<a href="/free_listen.html">免费听权益</a> ·
<a href="/ugc.html">云小编任务中心</a> ·
<a href="/ugc_lottery.html">云小编每日抽奖</a>
</div>
</section>
<div class="chips">
<a href="/api.html">交互式调试</a>
<a href="/qrlogin.html">二维码登录示例</a>
<a href="/unblock_test.html">解灰测试</a>
<a href="/audio_match_demo/index.html">听歌识曲 Demo</a>
<a href="/cloud.html">云盘上传</a>
<a href="/playlist_import.html">歌单导入</a>
<a href="/api_decrypt.html">API 解密</a>
<a href="/listen_together_host.html">一起听示例</a>
<a href="/playlist_cover_update.html">更新歌单封面示例</a>
<a href="/avatar_update.html">头像更新示例</a>
<a href="/scrobble.html">听歌打卡示例</a>
<a href="/yidun.html">获取 CheckToken</a>
<a href="/free_listen.html">免费听权益</a>
<a href="/ugc.html">云小编任务中心</a>
<a href="/ugc_lottery.html">云小编每日抽奖</a>
</div>
</section>
<footer class="site-footer">
<a href="https://github.com/neteasecloudmusicapienhanced/api-enhanced" target="_blank">GitHub</a>
</footer>
<footer class="shell-footer">
<a href="https://github.com/neteasecloudmusicapienhanced/api-enhanced" target="_blank">GitHub</a>
</footer>
</div>
</main>
<script>

View File

@ -3,317 +3,246 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="docs/netease.png" />
<link rel="stylesheet" href="/static/shell.css" />
<title>一起听 - 主机模式</title>
<script src="https://unpkg.com/petite-vue"></script>
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-height: 100vh;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
background: white;
border-radius: 12px;
padding: 32px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 16px;
}
.login-link {
display: block;
margin-bottom: 24px;
color: #666;
font-size: 14px;
text-decoration: none;
}
.login-link:hover {
color: #333;
text-decoration: underline;
}
body { padding: 1.25rem; }
.message {
padding: 12px 16px;
background: #f9f9f9;
border-radius: 6px;
margin-bottom: 20px;
font-size: 14px;
color: #555;
padding: 0.75rem 1rem;
background: var(--gray-2);
border: 1px solid var(--border);
border-left: 3px solid var(--info);
margin-bottom: 1.25rem;
font-size: 0.875rem;
color: var(--foreground);
font-family: var(--font-mono);
}
.audio-player {
width: 100%;
margin-bottom: 20px;
}
.btn {
background: #333;
color: white;
padding: 10px 20px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background 0.2s ease;
margin-right: 8px;
margin-bottom: 8px;
}
.btn:hover {
background: #555;
}
.audio-player { width: 100%; margin-bottom: 1.25rem; }
.section {
margin-bottom: 24px;
padding: 20px;
background: #f9f9f9;
border-radius: 6px;
margin-bottom: 1.5rem;
padding: 1.25rem;
background: var(--gray-2);
border: 1px solid var(--border);
}
.section h3 {
font-size: 16px;
font-family: var(--font-mono);
font-size: 1rem;
font-weight: 600;
color: #333;
margin-bottom: 12px;
margin-bottom: 0.75rem;
}
input[type="text"], input[type="number"] {
padding: 10px 14px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
outline: none;
width: 200px;
.section h4 {
font-family: var(--font-mono);
font-size: 0.875rem;
font-weight: 600;
margin: 1rem 0 0.5rem;
}
input:focus {
border-color: #333;
}
.input-group {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
gap: 0.75rem;
margin-bottom: 0.75rem;
}
.input-group label {
font-size: 14px;
color: #555;
min-width: 80px;
font-size: 0.875rem;
color: var(--muted-foreground);
min-width: 5rem;
margin: 0;
}
.share-link {
padding: 12px;
background: #f0f0f0;
border-radius: 6px;
font-size: 13px;
color: #666;
padding: 0.75rem;
background: var(--gray-1);
border: 1px solid var(--border);
font-family: var(--font-mono);
font-size: 0.8125rem;
color: var(--muted-foreground);
word-break: break-all;
margin-bottom: 12px;
margin-bottom: 0.75rem;
}
.user-list {
list-style: none;
max-height: 200px;
max-height: 12.5rem;
overflow-y: auto;
}
.user-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px;
border-bottom: 1px solid #eee;
gap: 0.75rem;
padding: 0.625rem;
border-bottom: 1px solid var(--border);
}
.user-item:last-child {
border-bottom: none;
}
.user-item:last-child { border-bottom: none; }
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
width: 2.5rem;
height: 2.5rem;
border-radius: 9999px;
object-fit: cover;
background: var(--gray-4);
}
.user-name {
font-size: 14px;
color: #333;
font-size: 0.875rem;
color: var(--foreground);
}
.track-list {
list-style: none;
max-height: 300px;
max-height: 18.75rem;
overflow-y: auto;
}
.track-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px;
border-bottom: 1px solid #eee;
gap: 0.75rem;
padding: 0.625rem;
border-bottom: 1px solid var(--border);
cursor: pointer;
transition: background 0.2s ease;
transition: background var(--transition);
}
.track-item:hover {
background: #f5f5f5;
}
.track-item:last-child {
border-bottom: none;
}
.track-item:hover { background: var(--gray-4); }
.track-item:last-child { border-bottom: none; }
.track-cover {
width: 40px;
height: 40px;
border-radius: 4px;
width: 2.5rem;
height: 2.5rem;
border-radius: 2px;
object-fit: cover;
background: var(--gray-4);
}
.track-name {
font-size: 14px;
color: #333;
font-size: 0.875rem;
color: var(--foreground);
}
details {
cursor: pointer;
}
details { cursor: pointer; }
details summary {
font-size: 14px;
font-family: var(--font-mono);
font-size: 0.875rem;
font-weight: 500;
color: #555;
padding: 8px 0;
color: var(--muted-foreground);
padding: 0.5rem 0;
list-style: none;
}
details summary:hover {
color: #333;
}
details[open] summary {
margin-bottom: 12px;
}
details summary::before { content: '▸ '; }
details[open] summary::before { content: '▾ '; }
details summary:hover { color: var(--foreground); }
details[open] summary { margin-bottom: 0.75rem; }
.control-buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
gap: 0.5rem;
}
.account-info {
font-family: var(--font-mono);
font-size: 0.875rem;
color: var(--foreground);
}
</style>
</head>
<body class="container">
<a href="/qrlogin.html" class="login-link">还没登录?点击登录</a>
<body>
<header class="shell-topbar">
<a class="brand" href="/"><img src="docs/netease.png" alt="logo">一起听 · 主机模式</a>
<nav>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/qrlogin.html">登录</a>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/">返回首页</a>
</nav>
</header>
<h1>一起听 - 主机模式</h1>
<main class="shell-main">
<div class="shell-container shell-narrow" style="max-width:56rem">
<header class="shell-header">
<h1>一起听 - 主机模式</h1>
<p class="sub">创建房间并同步播放,与好友一起听歌</p>
</header>
<div class="message">消息: {{message}}</div>
<div class="message">消息: {{message}}</div>
<audio id="player" class="audio-player" autoplay controls></audio>
<audio id="player" class="audio-player" autoplay controls></audio>
<div v-if="!account.login">
<button class="btn" @click="login">获取登录状态</button>
</div>
<div v-if="!account.login">
<button class="shell-btn shell-btn--default" @click="login">获取登录状态</button>
</div>
<div class="section">
<h3>账号信息</h3>
<div>当前登录账号: {{account.nickname}}</div>
</div>
<section class="section">
<h3>账号信息</h3>
<div class="account-info">当前登录账号: {{account.nickname}}</div>
</section>
<div v-if="account.login" class="section">
<h3>房间管理</h3>
<div class="control-buttons">
<button v-if="!roomInfo.roomId" class="btn" @click="createRoom">创建房间</button>
<section class="section" v-if="account.login">
<h3>房间管理</h3>
<div class="control-buttons">
<button v-if="!roomInfo.roomId" class="shell-btn shell-btn--default" @click="createRoom">创建房间</button>
</div>
<details>
<summary>加入房间</summary>
<div class="input-group">
<label>房间ID:</label>
<input v-model="roomInfo.roomId" type="text" />
</div>
<div class="input-group">
<label>邀请者ID:</label>
<input v-model="roomInfo.inviterId" type="text" />
</div>
<button class="shell-btn shell-btn--outline" @click="joinRoom">加入房间</button>
</details>
<div v-if="roomInfo.roomId" style="margin-top:1rem">
<h4>分享链接</h4>
<div class="share-link">
https://st.music.163.com/listen-together/share/?songId=1372188635&roomId={{roomInfo.roomId}}&inviterId={{roomInfo.inviterId}}
</div>
<div class="control-buttons">
<button class="shell-btn shell-btn--outline" @click="refreshRoom">刷新房间状态</button>
<button class="shell-btn shell-btn--danger" @click="closeRoom">关闭房间</button>
</div>
<h4>在线用户</h4>
<ul class="user-list">
<li v-for="user in roomInfo.roomUsers" :key="user.userId" class="user-item">
<img :src="user.avatarUrl" class="user-avatar" alt="avatar" />
<span class="user-name">{{user.nickname}}</span>
</li>
</ul>
</div>
</section>
<section class="section">
<h3>播放控制</h3>
<div class="control-buttons">
<button class="shell-btn shell-btn--outline" @click="playTrack">播放</button>
<button class="shell-btn shell-btn--outline" @click="pauseTrack">暂停</button>
<button class="shell-btn shell-btn--outline" @click="seekTrack">同步进度</button>
</div>
</section>
<details>
<summary>播放列表</summary>
<section class="section">
<div class="input-group">
<label>歌单ID:</label>
<input v-model="playlistInfo.playlistId" type="text" />
</div>
<button class="shell-btn shell-btn--outline" @click="loadPlaylist">加载歌单</button>
<div style="margin-top:0.75rem; font-size:0.875rem; color:var(--muted-foreground)">
歌单名称: {{playlistInfo.playlistName}}
</div>
<h4>歌单内容</h4>
<ul class="track-list">
<li
@click="gotoTrack(track.id)"
v-for="track in playlistInfo.playlistTracks"
:key="track.id"
class="track-item"
>
<img :src="track.al.picUrl" class="track-cover" alt="cover" />
<span class="track-name">{{track.name}}</span>
</li>
</ul>
</section>
</details>
</div>
<details>
<summary>加入房间</summary>
<div class="input-group">
<label>房间ID:</label>
<input v-model="roomInfo.roomId" type="text" />
</div>
<div class="input-group">
<label>邀请者ID:</label>
<input v-model="roomInfo.inviterId" type="text" />
</div>
<button class="btn" @click="joinRoom">加入房间</button>
</details>
<div v-if="roomInfo.roomId" style="margin-top: 16px;">
<h4>分享链接</h4>
<div class="share-link">
https://st.music.163.com/listen-together/share/?songId=1372188635&roomId={{roomInfo.roomId}}&inviterId={{roomInfo.inviterId}}
</div>
<button class="btn" @click="refreshRoom">刷新房间状态</button>
<button class="btn" @click="closeRoom">关闭房间</button>
<h4 style="margin-top: 16px;">在线用户</h4>
<ul class="user-list">
<li v-for="user in roomInfo.roomUsers" :key="user.userId" class="user-item">
<img :src="user.avatarUrl" class="user-avatar" alt="avatar" />
<span class="user-name">{{user.nickname}}</span>
</li>
</ul>
</div>
</div>
<div class="section">
<h3>播放控制</h3>
<div class="control-buttons">
<button class="btn" @click="playTrack">播放</button>
<button class="btn" @click="pauseTrack">暂停</button>
<button class="btn" @click="seekTrack">同步进度</button>
</div>
</div>
<details>
<summary>播放列表</summary>
<div class="section">
<div class="input-group">
<label>歌单ID:</label>
<input v-model="playlistInfo.playlistId" type="text" />
</div>
<button class="btn" @click="loadPlaylist">加载歌单</button>
<div style="margin-top: 12px; font-size: 14px; color: #555;">
歌单名称: {{playlistInfo.playlistName}}
</div>
<h4 style="margin-top: 16px;">歌单内容</h4>
<ul class="track-list">
<li
@click="gotoTrack(track.id)"
v-for="track in playlistInfo.playlistTracks"
:key="track.id"
class="track-item"
>
<img :src="track.al.picUrl" class="track-cover" alt="cover" />
<span class="track-name">{{track.name}}</span>
</li>
</ul>
</div>
</details>
</main>
</body>
<script>

View File

@ -4,137 +4,45 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="docs/netease.png" />
<link rel="stylesheet" href="/static/shell.css" />
<title>登录</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-height: 100vh;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: white;
border-radius: 12px;
padding: 40px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
max-width: 400px;
width: 100%;
text-align: center;
}
h1 {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.subtitle {
font-size: 14px;
color: #666;
margin-bottom: 32px;
}
.form-group {
margin-bottom: 20px;
text-align: left;
}
label {
display: block;
font-size: 14px;
font-weight: 500;
color: #555;
margin-bottom: 8px;
}
input {
width: 100%;
padding: 12px 14px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 15px;
outline: none;
}
input:focus {
border-color: #333;
}
.btn {
width: 100%;
padding: 14px;
background: #333;
color: white;
font-size: 15px;
font-weight: 500;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s ease;
}
.btn:hover {
background: #555;
}
.btn:disabled {
background: #999;
cursor: not-allowed;
}
.result {
margin-top: 24px;
padding: 16px;
background: #f9f9f9;
border-radius: 6px;
font-size: 13px;
color: #666;
text-align: left;
white-space: pre-wrap;
word-break: break-all;
}
.error {
color: #ef4444;
background: #fef2f2;
}
.success {
color: #10b981;
background: #f0fdf4;
padding: 1.25rem;
}
.shell-header { text-align: center; }
</style>
</head>
<body>
<div class="container">
<h1>登录</h1>
<p class="subtitle">使用手机号和密码登录网易云音乐</p>
<div class="shell-card shell-container shell-narrow" style="text-align:center;padding:2.5rem">
<header class="shell-header">
<h1>登录</h1>
<p class="sub">使用手机号和密码登录网易云音乐</p>
</header>
<div class="form-group">
<div class="form-group" style="text-align:left">
<label for="phone">手机号</label>
<input type="tel" id="phone" placeholder="请输入手机号" />
</div>
<div class="form-group">
<div class="form-group" style="text-align:left">
<label for="password">密码</label>
<input type="password" id="password" placeholder="请输入密码" />
</div>
<button id="loginBtn" class="btn" onclick="handleLogin()">登录</button>
<button id="loginBtn" class="shell-btn shell-btn--default shell-btn--block" onclick="handleLogin()">登录</button>
<div id="result" class="result" style="display: none;"></div>
<div class="shell-flex shell-flex--center shell-mt-4">
<a class="shell-btn shell-btn--outline shell-btn--sm" href="/qrlogin.html">改用二维码登录</a>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/">返回首页</a>
</div>
</div>
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>

View File

@ -4,182 +4,81 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="docs/netease.png" />
<link rel="stylesheet" href="/static/shell.css" />
<title>歌单封面上传</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-height: 100vh;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 500px;
margin: 0 auto;
background: white;
border-radius: 12px;
padding: 32px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
text-align: center;
}
h1 {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.subtitle {
font-size: 14px;
color: #666;
margin-bottom: 32px;
}
.login-link {
display: block;
margin-bottom: 24px;
color: #666;
font-size: 14px;
text-decoration: none;
}
.login-link:hover {
color: #333;
text-decoration: underline;
}
body { padding: 1.25rem; display: flex; align-items: flex-start; justify-content: center; }
.shell-header { text-align: center; }
.cover-wrapper {
position: relative;
width: 180px;
height: 180px;
margin: 0 auto 24px;
margin: 0 auto 1.5rem;
}
.cover {
width: 100%;
height: 100%;
border-radius: 50%;
border-radius: 2px;
object-fit: cover;
border: 4px solid #fff;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border: 1px solid var(--border);
background: var(--gray-4);
}
.form-group {
margin-bottom: 20px;
text-align: left;
}
label {
display: block;
font-size: 14px;
font-weight: 500;
color: #555;
margin-bottom: 8px;
}
input[type="text"] {
width: 100%;
padding: 10px 14px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
outline: none;
}
input[type="text"]:focus {
border-color: #333;
}
.upload-btn {
display: inline-block;
padding: 12px 28px;
background: #333;
color: white;
font-size: 15px;
font-weight: 500;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s ease;
border: none;
}
.upload-btn:hover {
background: #555;
}
.upload-btn input[type="file"] {
display: none;
}
.loading {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 50%;
background: rgba(255, 255, 255, 0.9);
background: color-mix(in srgb, var(--background) 85%, transparent);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.2s ease;
pointer-events: none;
}
.loading.active {
opacity: 1;
}
.loading.active { opacity: 1; }
.spinner {
width: 48px;
height: 48px;
border: 4px solid #e0e0e0;
border-top-color: #333;
border-radius: 50%;
width: 3rem;
height: 3rem;
border: 4px solid var(--gray-4);
border-top-color: var(--foreground);
border-radius: 9999px;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
to { transform: rotate(360deg); }
}
.result {
margin-top: 20px;
padding: 12px 16px;
border-radius: 6px;
font-size: 14px;
text-align: left;
}
.result.success {
background: #d1fae5;
color: #065f46;
}
.result.error {
background: #fee2e2;
color: #991b1b;
.upload-btn {
display: inline-block;
padding: 0.75rem 1.75rem;
background: var(--primary);
color: var(--primary-foreground);
font-family: var(--font-mono);
font-size: 0.9375rem;
font-weight: 500;
cursor: pointer;
transition: opacity var(--transition);
border: none;
}
.upload-btn:hover { opacity: 0.85; }
.upload-btn input[type="file"] { display: none; }
</style>
</head>
<body>
<div class="container">
<h1>歌单封面上传</h1>
<p class="subtitle">上传自定义歌单封面图片</p>
<div class="shell-card shell-container shell-narrow" style="text-align:center;padding:2.5rem">
<header class="shell-header">
<h1>歌单封面上传</h1>
<p class="sub">上传自定义歌单封面图片</p>
</header>
<a href="/qrlogin-nocookie.html" class="login-link">还没登录?点击登录</a>
<div class="shell-flex shell-flex--center shell-mb-4">
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/qrlogin-nocookie.html">还没登录?点击登录</a>
</div>
<div class="form-group">
<div class="form-group" style="text-align:left">
<label for="playlistId">歌单 ID</label>
<input type="text" id="playlistId" placeholder="请输入歌单ID" />
</div>
@ -191,12 +90,18 @@
</div>
</div>
<label class="upload-btn">
选择封面图片
<input id="file" type="file" name="filename" accept="image/*" />
</label>
<div class="shell-flex shell-flex--center">
<label class="upload-btn">
选择封面图片
<input id="file" type="file" name="filename" accept="image/*" />
</label>
</div>
<div id="result" class="result" style="display: none;"></div>
<div class="shell-flex shell-flex--center shell-mt-4">
<a class="shell-btn shell-btn--outline shell-btn--sm" href="/">返回首页</a>
</div>
</div>
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>

View File

@ -3,266 +3,125 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="docs/netease.png">
<link rel="stylesheet" href="/static/shell.css">
<title>歌单导入工具</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-height: 100vh;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
background: white;
border-radius: 12px;
padding: 32px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.subtitle {
font-size: 14px;
color: #666;
margin-bottom: 24px;
font-size: 0.875rem;
color: var(--muted-foreground);
margin-bottom: 1.5rem;
}
.tabs {
display: flex;
gap: 4px;
margin-bottom: 24px;
border-bottom: 1px solid #eee;
}
.tab-btn {
padding: 12px 24px;
background: transparent;
border: none;
border-bottom: 2px solid transparent;
font-size: 14px;
font-weight: 500;
color: #666;
cursor: pointer;
transition: all 0.2s ease;
}
.tab-btn:hover {
color: #333;
}
.tab-btn.active {
color: #333;
border-bottom-color: #333;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
font-size: 14px;
font-weight: 500;
color: #555;
margin-bottom: 8px;
}
input[type="text"], textarea {
width: 100%;
padding: 10px 14px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
outline: none;
font-family: inherit;
}
input[type="text"]:focus, textarea:focus {
border-color: #333;
}
textarea {
min-height: 120px;
resize: vertical;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 16px;
}
table th, table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #eee;
}
table th {
font-size: 14px;
font-weight: 600;
color: #555;
background: #f9f9f9;
}
table td input {
width: 100%;
}
.btn {
padding: 10px 20px;
background: #333;
color: white;
font-size: 14px;
font-weight: 500;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s ease;
}
.btn:hover {
background: #555;
}
.btn-secondary {
background: #666;
}
.btn-secondary:hover {
background: #888;
}
.input-group {
display: flex;
gap: 8px;
margin-bottom: 12px;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.input-group input {
flex: 1;
}
.input-group input { flex: 1; }
.checkbox-group {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 24px;
gap: 0.5rem;
margin-bottom: 1.5rem;
}
.checkbox-group input[type="checkbox"] {
cursor: pointer;
}
.checkbox-group label {
margin: 0;
cursor: pointer;
color: var(--foreground);
}
input:disabled {
background: #f5f5f5;
cursor: not-allowed;
}
.removeLinkButton { flex-shrink: 0; }
</style>
</head>
<body>
<div class="container">
<h1>歌单导入工具</h1>
<p class="subtitle">请选择一种导入方式并填写相关信息</p>
<header class="shell-topbar">
<a class="brand" href="/"><img src="docs/netease.png" alt="logo">歌单导入</a>
<nav>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/">返回首页</a>
</nav>
</header>
<ul class="tabs" id="importTabs" role="tablist">
<li role="presentation">
<button class="tab-btn active" id="metadata-tab" data-bs-toggle="tab" data-bs-target="#metadata" type="button" role="tab" aria-controls="metadata" aria-selected="true">元数据导入</button>
</li>
<li role="presentation">
<button class="tab-btn" id="text-tab" data-bs-toggle="tab" data-bs-target="#text" type="button" role="tab" aria-controls="text" aria-selected="false">文字导入</button>
</li>
<li role="presentation">
<button class="tab-btn" id="link-tab" data-bs-toggle="tab" data-bs-target="#link" type="button" role="tab" aria-controls="link" aria-selected="false">链接导入</button>
</li>
</ul>
<main class="shell-main">
<div class="shell-container shell-narrow" style="max-width:56rem">
<header class="shell-header">
<h1>歌单导入工具</h1>
<p class="subtitle">请选择一种导入方式并填写相关信息</p>
</header>
<div class="tab-content active" id="importTabContent">
<!-- 元数据导入 -->
<div class="tab-content active" id="metadata" role="tabpanel" aria-labelledby="metadata-tab">
<table>
<thead>
<tr>
<th style="width: 33%">歌曲名称</th>
<th style="width: 33%">艺术家</th>
<th style="width: 33%">专辑</th>
</tr>
</thead>
<tbody id="metadataTableBody">
<tr>
<td><input type="text" name="name[]" placeholder="歌曲名称"></td>
<td><input type="text" name="artist[]" placeholder="艺术家"></td>
<td><input type="text" name="album[]" placeholder="专辑"></td>
</tr>
</tbody>
</table>
<button type="button" class="btn btn-secondary" id="addMetadataRow">增加歌曲</button>
</div>
<!-- 文字导入 -->
<div class="tab-content" id="text" role="tabpanel" aria-labelledby="text-tab">
<div class="form-group">
<label for="textInput">文字内容</label>
<textarea id="textInput" name="text" rows="5" placeholder="请输入歌曲信息,每行一首歌曲"></textarea>
</div>
<div class="form-group">
<label for="playlistNameInput">歌单名称</label>
<input type="text" id="playlistNameInput" name="playlistName" placeholder="请输入歌单名">
</div>
</div>
<!-- 链接导入 -->
<div class="tab-content" id="link" role="tabpanel" aria-labelledby="link-tab">
<div class="form-group">
<label>链接列表</label>
<div id="linkInputsContainer">
<div class="input-group">
<input type="text" id="linkInput0" name="linkInput0" placeholder="请输入链接">
<button type="button" class="btn btn-secondary removeLinkButton" data-index="0">×</button>
</div>
</div>
<button type="button" class="btn btn-secondary" id="addLinkButton" style="margin-top: 8px;">增加链接</button>
</div>
<div class="form-group" style="margin-top: 20px;">
<label for="playlistNameLinkInput">歌单名称</label>
<input type="text" id="playlistNameLinkInput" name="playlistName" placeholder="请输入歌单名">
</div>
</div>
</div>
<section class="shell-card">
<ul class="tabs" id="importTabs" role="tablist">
<li role="presentation" style="list-style:none">
<button class="tab-btn active" id="metadata-tab" data-bs-toggle="tab" data-bs-target="#metadata" type="button" role="tab" aria-controls="metadata" aria-selected="true">元数据导入</button>
</li>
<li role="presentation" style="list-style:none">
<button class="tab-btn" id="text-tab" data-bs-toggle="tab" data-bs-target="#text" type="button" role="tab" aria-controls="text" aria-selected="false">文字导入</button>
</li>
<li role="presentation" style="list-style:none">
<button class="tab-btn" id="link-tab" data-bs-toggle="tab" data-bs-target="#link" type="button" role="tab" aria-controls="link" aria-selected="false">链接导入</button>
</li>
</ul>
<div class="checkbox-group">
<input type="checkbox" value="" id="importStarCheckbox">
<label for="importStarCheckbox">
导入"我喜欢的音乐"
</label>
</div>
<div class="tab-content active" id="importTabContent">
<!-- 元数据导入 -->
<div class="tab-content active" id="metadata" role="tabpanel" aria-labelledby="metadata-tab">
<table>
<thead>
<tr>
<th style="width: 33%">歌曲名称</th>
<th style="width: 33%">艺术家</th>
<th style="width: 33%">专辑</th>
</tr>
</thead>
<tbody id="metadataTableBody">
<tr>
<td><input type="text" name="name[]" placeholder="歌曲名称"></td>
<td><input type="text" name="artist[]" placeholder="艺术家"></td>
<td><input type="text" name="album[]" placeholder="专辑"></td>
</tr>
</tbody>
</table>
<button type="button" class="shell-btn shell-btn--outline" id="addMetadataRow">增加歌曲</button>
</div>
<!-- 文字导入 -->
<div class="tab-content" id="text" role="tabpanel" aria-labelledby="text-tab">
<div class="form-group">
<label for="textInput">文字内容</label>
<textarea id="textInput" name="text" rows="5" placeholder="请输入歌曲信息,每行一首歌曲"></textarea>
</div>
<div class="form-group">
<label for="playlistNameInput">歌单名称</label>
<input type="text" id="playlistNameInput" name="playlistName" placeholder="请输入歌单名">
</div>
</div>
<!-- 链接导入 -->
<div class="tab-content" id="link" role="tabpanel" aria-labelledby="link-tab">
<div class="form-group">
<label>链接列表</label>
<div id="linkInputsContainer">
<div class="input-group">
<input type="text" id="linkInput0" name="linkInput0" placeholder="请输入链接">
<button type="button" class="shell-btn shell-btn--outline shell-btn--sm removeLinkButton" data-index="0">×</button>
</div>
</div>
<button type="button" class="shell-btn shell-btn--outline" id="addLinkButton">增加链接</button>
</div>
<div class="form-group" style="margin-top:1.25rem">
<label for="playlistNameLinkInput">歌单名称</label>
<input type="text" id="playlistNameLinkInput" name="playlistName" placeholder="请输入歌单名">
</div>
</div>
</div>
<button type="submit" class="btn" id="submitBtn">导入歌曲</button>
</div>
<div class="checkbox-group">
<input type="checkbox" value="" id="importStarCheckbox">
<label for="importStarCheckbox">
导入"我喜欢的音乐"
</label>
</div>
<button type="submit" class="shell-btn shell-btn--default" id="submitBtn">导入歌曲</button>
</section>
</div>
</main>
<script src="https://fastly.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
@ -291,11 +150,11 @@
newInput.id = `linkInput${newIndex}`;
newInput.name = `linkInput${newIndex}`;
newInput.placeholder = '请输入链接';
newInput.style.cssText = 'flex: 1; padding: 10px 14px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; outline: none;';
newInput.style.cssText = 'flex: 1; padding: 10px 14px; border: 1px solid #4d4d4d; border-radius: 0; font-size: 14px; outline: none; background:#171717; color:#e4e4e4;';
var removeButton = document.createElement('button');
removeButton.type = 'button';
removeButton.className = 'btn btn-secondary';
removeButton.className = 'shell-btn shell-btn--outline shell-btn--sm';
removeButton.textContent = '×';
removeButton.dataset.index = newIndex.toString();
removeButton.addEventListener('click', function() {
@ -317,9 +176,9 @@
var newRow = document.createElement('tr');
newRow.innerHTML = `
<td><input type="text" name="name[]" placeholder="歌曲名称" style="width: 100%; padding: 10px 14px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; outline: none;"></td>
<td><input type="text" name="artist[]" placeholder="艺术家" style="width: 100%; padding: 10px 14px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; outline: none;"></td>
<td><input type="text" name="album[]" placeholder="专辑" style="width: 100%; padding: 10px 14px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; outline: none;"></td>
<td><input type="text" name="name[]" placeholder="歌曲名称"></td>
<td><input type="text" name="artist[]" placeholder="艺术家"></td>
<td><input type="text" name="album[]" placeholder="专辑"></td>
`;
container.appendChild(newRow);

View File

@ -4,110 +4,67 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="docs/netease.png" />
<link rel="stylesheet" href="/static/shell.css" />
<title>二维码登录</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-height: 100vh;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
padding: 1.25rem;
}
.container {
background: white;
border-radius: 12px;
padding: 48px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
max-width: 450px;
width: 100%;
text-align: center;
}
h1 {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.subtitle {
font-size: 14px;
color: #666;
margin-bottom: 32px;
}
.qr-wrapper {
display: inline-block;
padding: 16px;
background: #f9f9f9;
border-radius: 8px;
margin-bottom: 24px;
padding: 1rem;
background: var(--gray-2);
border: 1px solid var(--border);
margin-bottom: 1.5rem;
}
#qrImg {
width: 200px;
height: 200px;
display: block;
}
.info {
text-align: left;
padding: 16px;
background: #f9f9f9;
border-radius: 6px;
font-family: 'Courier New', monospace;
font-size: 12px;
color: #666;
padding: 1rem;
background: var(--gray-2);
border: 1px solid var(--border);
font-family: var(--font-mono);
font-size: 0.75rem;
color: var(--muted-foreground);
white-space: pre-wrap;
word-break: break-all;
max-height: 200px;
max-height: 12.5rem;
overflow-y: auto;
}
.status {
margin-top: 16px;
padding: 12px;
border-radius: 6px;
font-size: 14px;
margin-top: 1rem;
padding: 0.75rem;
border: 1px solid var(--border);
font-family: var(--font-mono);
font-size: 0.875rem;
font-weight: 500;
}
.status.waiting {
background: #fef3c7;
color: #92400e;
}
.status.success {
background: #d1fae5;
color: #065f46;
}
.status.error {
background: #fee2e2;
color: #991b1b;
}
.status.waiting { border-color: var(--warning); color: var(--warning); background: color-mix(in srgb, var(--warning) 8%, transparent); }
.status.success { border-color: var(--success); color: var(--success); background: color-mix(in srgb, var(--success) 8%, transparent); }
.status.error { border-color: var(--destructive); color: var(--destructive); background: color-mix(in srgb, var(--destructive) 8%, transparent); }
.hint {
margin-top: 16px;
font-size: 13px;
color: #999;
margin-top: 1rem;
font-size: 0.8125rem;
color: var(--muted-foreground);
}
.shell-header { text-align: center; }
</style>
</head>
<body>
<div class="container">
<h1>二维码登录</h1>
<p class="subtitle">使用网易云音乐App扫描二维码登录</p>
<div class="shell-card shell-container shell-narrow" style="text-align:center;padding:3rem">
<header class="shell-header">
<h1>二维码登录</h1>
<p class="sub">使用网易云音乐App扫描二维码登录</p>
</header>
<div class="qr-wrapper">
<img id="qrImg" src="" alt="二维码加载中..." />
@ -115,9 +72,13 @@
<div id="status" class="status waiting">等待扫描...</div>
<div id="info" class="info"></div>
<div id="info" class="info shell-mt-3"></div>
<p class="hint">请打开网易云音乐App扫描上方二维码完成登录</p>
<div class="shell-flex shell-flex--center shell-mt-4">
<a class="shell-btn shell-btn--outline shell-btn--sm" href="/">返回首页</a>
</div>
</div>
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>

View File

@ -4,110 +4,67 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="docs/netease.png" />
<link rel="stylesheet" href="/static/shell.css" />
<title>二维码登录</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-height: 100vh;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
padding: 1.25rem;
}
.container {
background: white;
border-radius: 12px;
padding: 48px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
max-width: 450px;
width: 100%;
text-align: center;
}
h1 {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.subtitle {
font-size: 14px;
color: #666;
margin-bottom: 32px;
}
.qr-wrapper {
display: inline-block;
padding: 16px;
background: #f9f9f9;
border-radius: 8px;
margin-bottom: 24px;
padding: 1rem;
background: var(--gray-2);
border: 1px solid var(--border);
margin-bottom: 1.5rem;
}
#qrImg {
width: 200px;
height: 200px;
display: block;
}
.info {
text-align: left;
padding: 16px;
background: #f9f9f9;
border-radius: 6px;
font-family: 'Courier New', monospace;
font-size: 12px;
color: #666;
padding: 1rem;
background: var(--gray-2);
border: 1px solid var(--border);
font-family: var(--font-mono);
font-size: 0.75rem;
color: var(--muted-foreground);
white-space: pre-wrap;
word-break: break-all;
max-height: 200px;
max-height: 12.5rem;
overflow-y: auto;
}
.status {
margin-top: 16px;
padding: 12px;
border-radius: 6px;
font-size: 14px;
margin-top: 1rem;
padding: 0.75rem;
border: 1px solid var(--border);
font-family: var(--font-mono);
font-size: 0.875rem;
font-weight: 500;
}
.status.waiting {
background: #fef3c7;
color: #92400e;
}
.status.success {
background: #d1fae5;
color: #065f46;
}
.status.error {
background: #fee2e2;
color: #991b1b;
}
.status.waiting { border-color: var(--warning); color: var(--warning); background: color-mix(in srgb, var(--warning) 8%, transparent); }
.status.success { border-color: var(--success); color: var(--success); background: color-mix(in srgb, var(--success) 8%, transparent); }
.status.error { border-color: var(--destructive); color: var(--destructive); background: color-mix(in srgb, var(--destructive) 8%, transparent); }
.hint {
margin-top: 16px;
font-size: 13px;
color: #999;
margin-top: 1rem;
font-size: 0.8125rem;
color: var(--muted-foreground);
}
.shell-header { text-align: center; }
</style>
</head>
<body>
<div class="container">
<h1>二维码登录</h1>
<p class="subtitle">使用网易云音乐App扫描二维码登录</p>
<div class="shell-card shell-container shell-narrow" style="text-align:center;padding:3rem">
<header class="shell-header">
<h1>二维码登录</h1>
<p class="sub">使用网易云音乐App扫描二维码登录</p>
</header>
<div class="qr-wrapper">
<img id="qrImg" src="" alt="二维码加载中..." />
@ -115,9 +72,13 @@
<div id="status" class="status waiting">等待扫描...</div>
<div id="info" class="info"></div>
<div id="info" class="info shell-mt-3"></div>
<p class="hint">请打开网易云音乐App扫描上方二维码完成登录</p>
<div class="shell-flex shell-flex--center shell-mt-4">
<a class="shell-btn shell-btn--outline shell-btn--sm" href="/">返回首页</a>
</div>
</div>
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>

View File

@ -4,346 +4,127 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="docs/netease.png" />
<link rel="stylesheet" href="/static/shell.css" />
<title>听歌打卡 - 网易云音乐 API Enhanced</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-height: 100vh;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 520px;
margin: 40px auto;
background: white;
border-radius: 12px;
padding: 32px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
text-align: center;
}
.subtitle {
font-size: 14px;
color: #666;
margin-bottom: 24px;
text-align: center;
}
.login-link {
display: block;
margin-bottom: 20px;
color: #666;
font-size: 14px;
text-decoration: none;
text-align: center;
}
.login-link:hover {
color: #333;
text-decoration: underline;
}
/* ---- Tabs ---- */
.tabs {
display: flex;
gap: 0;
margin-bottom: 24px;
border-bottom: 2px solid #eee;
}
.tab-btn {
flex: 1;
padding: 10px 0;
background: none;
border: none;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
font-size: 14px;
font-weight: 500;
color: #999;
cursor: pointer;
transition: color 0.2s, border-color 0.2s;
}
.tab-btn:hover {
color: #555;
}
.tab-btn.active {
color: #333;
border-bottom-color: #333;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
/* ---- Form ---- */
.form-group {
margin-bottom: 16px;
text-align: left;
}
label {
display: block;
font-size: 13px;
font-weight: 500;
color: #555;
margin-bottom: 6px;
}
label .tag {
display: inline-block;
font-size: 11px;
font-weight: 400;
padding: 1px 6px;
border-radius: 3px;
margin-left: 6px;
}
label .tag.r {
background: #fee2e2;
color: #991b1b;
}
label .tag.o {
background: #fef3c7;
color: #92400e;
}
input[type="text"],
input[type="number"],
textarea {
width: 100%;
padding: 10px 14px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
outline: none;
font-family: inherit;
transition: border-color 0.2s;
}
input[type="text"]:focus,
input[type="number"]:focus,
textarea:focus {
border-color: #333;
}
textarea {
resize: vertical;
}
.row {
display: flex;
gap: 12px;
}
.row .form-group {
flex: 1;
}
.quick-fill {
margin-top: 5px;
font-size: 12px;
color: #666;
}
.quick-fill span {
color: #0066cc;
cursor: pointer;
margin-right: 8px;
}
.quick-fill span:hover {
text-decoration: underline;
}
.btn {
display: block;
width: 100%;
padding: 12px;
background: #333;
color: white;
font-size: 15px;
font-weight: 500;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s ease;
border: none;
text-align: center;
margin-top: 20px;
}
.btn:hover {
background: #555;
}
.btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.result {
margin-top: 16px;
padding: 12px 16px;
border-radius: 6px;
font-size: 14px;
text-align: left;
white-space: pre-wrap;
word-break: break-all;
font-family: monospace;
}
.result.success {
background: #d1fae5;
color: #065f46;
}
.result.error {
background: #fee2e2;
color: #991b1b;
}
.result.info {
background: #e0f2fe;
color: #0369a1;
}
.footer-link {
text-align: center;
margin-top: 24px;
font-size: 13px;
}
.footer-link a {
color: #666;
text-decoration: none;
}
.footer-link a:hover {
text-decoration: underline;
}
body { padding: 1.25rem; }
.shell-header { text-align: center; }
.btn-shell { display: block; width: 100%; }
</style>
</head>
<body>
<div class="container">
<h1>听歌打卡</h1>
<p class="subtitle">同步听歌记录至网易云音乐,增加听歌排行计数</p>
<header class="shell-topbar">
<a class="brand" href="/"><img src="docs/netease.png" alt="logo">听歌打卡</a>
<nav>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/qrlogin-nocookie.html">登录</a>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/">返回首页</a>
</nav>
</header>
<a href="/qrlogin-nocookie.html" class="login-link">还没登录?点击登录</a>
<main class="shell-main">
<div class="shell-container shell-narrow">
<header class="shell-header">
<h1>听歌打卡</h1>
<p class="sub">同步听歌记录至网易云音乐,增加听歌排行计数</p>
</header>
<!-- Cookie (共享) -->
<div class="form-group">
<label for="cookie">Cookie <span class="tag o">可选</span></label>
<textarea id="cookie" placeholder="留空则默认读取本地存储的登录态" rows="2"></textarea>
<div class="quick-fill">
<span onclick="loadLocalCookie()">读取本地 Cookie</span>
</div>
</div>
<!-- Tabs -->
<div class="tabs">
<button class="tab-btn active" data-tab="v0" onclick="switchTab('v0')">原版</button>
<button class="tab-btn" data-tab="v1" onclick="switchTab('v1')">V2 (NCBL 加密版)</button>
</div>
<!-- ============ Tab: v0 (原版 /scrobble) ============ -->
<div class="tab-content active" id="tab-v0">
<div class="form-group">
<label>歌曲 ID <span class="tag r">必填</span></label>
<input type="text" id="v0-songId" placeholder="例如: 2756058128" value="2756058128" />
<div class="quick-fill">
示例:
<span onclick="fillV0('2756058128', '288651229')">妖精小姐的魔法邀约</span>
<span onclick="fillV0('2637402867', '251025018')">Echoes of Memoria</span>
<span onclick="fillV0('36307815', '3394198')">Lose Control</span>
</div>
</div>
<div class="form-group">
<label>来源 ID (歌单或专辑 ID) <span class="tag r">必填</span></label>
<input type="text" id="v0-sourceId" placeholder="例如: 2756058128" value="2756058128" />
</div>
<div class="form-group">
<label>播放时间 (秒) <span class="tag o">可选</span></label>
<input type="number" id="v0-time" placeholder="300" value="300" />
</div>
<button class="btn" onclick="submitV0()">立即打卡 (原版)</button>
</div>
<!-- ============ Tab: v1 (NCBL 加密版 /scrobble/v1) ============ -->
<div class="tab-content" id="tab-v1">
<div class="form-group">
<label>歌曲 ID <span class="tag r">必填</span></label>
<input type="text" id="v1-songId" placeholder="例如: 518066366" value="518066366" />
<div class="quick-fill">
示例:
<span onclick="fillV1('518066366', '36780169', '291')">默认示例</span>
<span onclick="fillV1('2756058128', '288651229', '300')">妖精小姐的魔法邀约</span>
<span onclick="fillV1('2637402867', '251025018', '300')">Echoes of Memoria</span>
</div>
</div>
<div class="form-group">
<label>播放时间 (秒) <span class="tag r">必填</span></label>
<input type="number" id="v1-time" placeholder="例如: 291" value="291" />
</div>
<div class="form-group">
<label>来源 ID <span class="tag o">可选</span></label>
<input type="text" id="v1-sourceId" placeholder="歌单或专辑 ID" value="36780169" />
</div>
<div class="row">
<section class="shell-card">
<!-- Cookie (共享) -->
<div class="form-group">
<label>歌曲名 <span class="tag o">可选</span></label>
<input type="text" id="v1-name" placeholder="歌曲名称" />
<label for="cookie">Cookie <span class="tag o">可选</span></label>
<textarea id="cookie" placeholder="留空则默认读取本地存储的登录态" rows="2"></textarea>
<div class="quick-fill">
<span onclick="loadLocalCookie()">读取本地 Cookie</span>
</div>
</div>
<div class="form-group">
<label>艺术家 <span class="tag o">可选</span></label>
<input type="text" id="v1-artist" placeholder="歌手名" />
</div>
</div>
<div class="row">
<div class="form-group">
<label>码率 <span class="tag o">可选</span></label>
<input type="number" id="v1-bitrate" placeholder="默认 320" value="320" />
</div>
<div class="form-group">
<label>音质等级 <span class="tag o">可选</span></label>
<input type="text" id="v1-level" placeholder="默认 exhigh" value="exhigh" />
</div>
</div>
<div class="form-group">
<label>歌曲总时长 (秒) <span class="tag o">可选</span></label>
<input type="number" id="v1-total" placeholder="留空则与播放时间相同" />
</div>
<button class="btn" onclick="submitV1()">立即打卡 (NCBL 加密版)</button>
</div>
<!-- Result -->
<div id="result" class="result" style="display: none;"></div>
<!-- Tabs -->
<div class="tabs">
<button class="tab-btn active" data-tab="v0" onclick="switchTab('v0')">原版</button>
<button class="tab-btn" data-tab="v1" onclick="switchTab('v1')">V2 (NCBL 加密版)</button>
</div>
<div class="footer-link">
<a href="/">返回首页</a>
<!-- ============ Tab: v0 (原版 /scrobble) ============ -->
<div class="tab-content active" id="tab-v0">
<div class="form-group">
<label>歌曲 ID <span class="tag r">必填</span></label>
<input type="text" id="v0-songId" placeholder="例如: 2756058128" value="2756058128" />
<div class="quick-fill">
示例:
<span onclick="fillV0('2756058128', '288651229')">妖精小姐的魔法邀约</span>
<span onclick="fillV0('2637402867', '251025018')">Echoes of Memoria</span>
<span onclick="fillV0('36307815', '3394198')">Lose Control</span>
</div>
</div>
<div class="form-group">
<label>来源 ID (歌单或专辑 ID) <span class="tag r">必填</span></label>
<input type="text" id="v0-sourceId" placeholder="例如: 2756058128" value="2756058128" />
</div>
<div class="form-group">
<label>播放时间 (秒) <span class="tag o">可选</span></label>
<input type="number" id="v0-time" placeholder="300" value="300" />
</div>
<button class="btn shell-btn shell-btn--default shell-btn--block" onclick="submitV0()">立即打卡 (原版)</button>
</div>
<!-- ============ Tab: v1 (NCBL 加密版 /scrobble/v1) ============ -->
<div class="tab-content" id="tab-v1">
<div class="form-group">
<label>歌曲 ID <span class="tag r">必填</span></label>
<input type="text" id="v1-songId" placeholder="例如: 518066366" value="518066366" />
<div class="quick-fill">
示例:
<span onclick="fillV1('518066366', '36780169', '291')">默认示例</span>
<span onclick="fillV1('2756058128', '288651229', '300')">妖精小姐的魔法邀约</span>
<span onclick="fillV1('2637402867', '251025018', '300')">Echoes of Memoria</span>
</div>
</div>
<div class="form-group">
<label>播放时间 (秒) <span class="tag r">必填</span></label>
<input type="number" id="v1-time" placeholder="例如: 291" value="291" />
</div>
<div class="form-group">
<label>来源 ID <span class="tag o">可选</span></label>
<input type="text" id="v1-sourceId" placeholder="歌单或专辑 ID" value="36780169" />
</div>
<div class="row">
<div class="form-group">
<label>歌曲名 <span class="tag o">可选</span></label>
<input type="text" id="v1-name" placeholder="歌曲名称" />
</div>
<div class="form-group">
<label>艺术家 <span class="tag o">可选</span></label>
<input type="text" id="v1-artist" placeholder="歌手名" />
</div>
</div>
<div class="row">
<div class="form-group">
<label>码率 <span class="tag o">可选</span></label>
<input type="number" id="v1-bitrate" placeholder="默认 320" value="320" />
</div>
<div class="form-group">
<label>音质等级 <span class="tag o">可选</span></label>
<input type="text" id="v1-level" placeholder="默认 exhigh" value="exhigh" />
</div>
</div>
<div class="form-group">
<label>歌曲总时长 (秒) <span class="tag o">可选</span></label>
<input type="number" id="v1-total" placeholder="留空则与播放时间相同" />
</div>
<button class="btn shell-btn shell-btn--default shell-btn--block" onclick="submitV1()">立即打卡 (NCBL 加密版)</button>
</div>
<!-- Result -->
<div id="result" class="result" style="display: none;"></div>
<div class="shell-flex shell-flex--center shell-mt-4">
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/">返回首页</a>
</div>
</section>
</div>
</div>
</main>
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
<script>

901
public/static/shell.css Normal file
View File

@ -0,0 +1,901 @@
/* ==========================================================================
shell.css 网易云音乐 API Enhanced 统一设计系统深色 · 直角
技术栈原生 HTML + CSS 变量设计令牌 / shadcn 风格组件 / 无构建静态托管
字体font-mono 等宽优先按钮/徽章/代码正文系统无衬线
========================================================================== */
/* ============================== 设计令牌 ============================== */
:root {
--background: #171717; /* 页底 */
--foreground: #e4e4e4; /* 正文 */
--card: #1f1f1f; /* 卡片 */
--card-foreground: #e4e4e4;
--popover: #242424; /* 浮层 */
--popover-foreground: #e4e4e4;
--primary: #e4e4e4; /* 主按钮 = 白底黑字 */
--primary-foreground: #171717;
--secondary: #a4a4a4;
--secondary-foreground: #171717;
--muted: #2e2e2e; /* 弱化块 */
--muted-foreground: #a1a1a1;
--accent: #383838; /* 悬停/高亮块 */
--accent-foreground: #e4e4e4;
--destructive: #ff544c; /* 危险 */
--border: #404040; /* 边框(灰阶递进最高一级) */
--input: #4d4d4d;
--ring: #e4e4e4;
--radius: 0px; /* ← 全站直角的核心 */
/* 灰阶递进:#171717 → #1f1f1f → #242424 → #2e2e2e → #383838 */
--gray-1: #171717;
--gray-2: #1f1f1f;
--gray-3: #242424;
--gray-4: #2e2e2e;
--gray-5: #383838;
/* 点缀色 */
--live: #ff2d55; /* 直播在线(三层扩散光环) */
--bili: #fb7299; /* B站闪烁光标 */
--success: #46c881;
--warning: #e8a33d;
--info: #5aa7f0;
/* 等宽字体栈font-mono */
--font-mono: ui-monospace, 'SF Mono', 'Cascadia Code', 'JetBrains Mono',
Menlo, Consolas, 'Courier New', monospace;
/* 系统无衬线 */
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC',
'Hiragino Sans GB', 'Microsoft YaHei', Roboto, sans-serif;
--transition: 75ms ease; /* 全站微交互统一 75ms */
--topbar-h: 3.5rem;
}
/* ============================== 基础 Reset ============================== */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
color-scheme: dark;
}
body {
font-family: var(--font-sans);
min-height: 100vh;
background: var(--background);
color: var(--foreground);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
::selection {
background: var(--foreground);
color: var(--background);
}
/* 滚动条(深色适配) */
*::-webkit-scrollbar {
width: 10px;
height: 10px;
}
*::-webkit-scrollbar-track {
background: var(--gray-1);
}
*::-webkit-scrollbar-thumb {
background: var(--gray-5);
border-radius: 0;
}
*::-webkit-scrollbar-thumb:hover {
background: var(--border);
}
/* ============================== 布局模式 ============================== */
/* 顶栏:毛玻璃,固定 */
.shell-topbar {
position: fixed;
inset-inline: 0;
top: 0;
z-index: 40;
height: var(--topbar-h);
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding-inline: 1.25rem;
border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent);
background: color-mix(in srgb, var(--background) 80%, transparent);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
}
.shell-topbar .brand {
display: flex;
align-items: center;
gap: 0.5rem;
font-family: var(--font-mono);
font-size: 0.9rem;
font-weight: 600;
letter-spacing: 0.02em;
color: var(--foreground);
text-decoration: none;
}
.shell-topbar .brand img {
width: 1.5rem;
height: 1.5rem;
object-fit: contain;
}
.shell-topbar nav {
display: flex;
align-items: center;
gap: 0.25rem;
}
/* 主体:居中大留白 */
.shell-main {
display: flex;
min-height: 100vh;
flex-direction: column;
padding: calc(var(--topbar-h) + 3rem) 1.25rem 4rem;
}
.shell-container {
width: 100%;
max-width: 72rem; /* max-w-6xl */
margin-inline: auto;
}
.shell-narrow {
max-width: 36rem;
}
/* 页面标题 */
.shell-header {
margin-bottom: 2rem;
}
.shell-header h1 {
font-family: var(--font-mono);
font-size: 1.75rem;
font-weight: 700;
letter-spacing: 0.01em;
margin-bottom: 0.375rem;
}
.shell-header .sub {
color: var(--muted-foreground);
font-size: 0.875rem;
}
/* 卡片网格用背景色当分隔线卡片零间距hover 反色区分 */
.shell-grid {
display: grid;
gap: 1px;
border-block: 1px solid var(--border);
background: var(--border);
}
@media (min-width: 640px) {
.shell-grid {
border: 1px solid var(--border);
}
}
.shell-grid > * {
background: var(--background);
transition: background var(--transition);
}
.shell-grid > *:hover {
background: var(--accent);
}
/* ============================== 组件Button ============================== */
/* 直角font-monoh-9 px-475mshover 前景/背景反转 */
.shell-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
height: 2.25rem; /* h-9 */
padding-inline: 1rem; /* px-4 */
font-family: var(--font-mono);
font-size: 0.875rem; /* text-sm */
font-weight: 500;
line-height: 1;
white-space: nowrap;
border: 1px solid transparent;
border-radius: var(--radius);
cursor: pointer;
user-select: none;
background: transparent;
color: var(--foreground);
transition: background-color var(--transition), color var(--transition),
border-color var(--transition), opacity var(--transition);
}
.shell-btn:hover {
background: var(--foreground);
color: var(--background);
}
.shell-btn:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 2px;
}
.shell-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
pointer-events: none;
}
/* default实心白 */
.shell-btn--default {
background: var(--primary);
color: var(--primary-foreground);
}
.shell-btn--default:hover {
background: var(--primary);
color: var(--primary-foreground);
opacity: 0.85;
}
/* outline透明 + 边框 */
.shell-btn--outline {
border-color: var(--border);
}
.shell-btn--outline:hover {
border-color: var(--foreground);
background: var(--foreground);
color: var(--background);
}
/* ghost无边框 */
.shell-btn--ghost:hover {
background: var(--accent);
color: var(--foreground);
}
/* danger */
.shell-btn--danger {
border-color: var(--destructive);
color: var(--destructive);
}
.shell-btn--danger:hover {
background: var(--destructive);
color: var(--background);
border-color: var(--destructive);
}
/* success */
.shell-btn--success {
border-color: var(--success);
color: var(--success);
}
.shell-btn--success:hover {
background: var(--success);
color: var(--background);
border-color: var(--success);
}
/* icon 尺寸变体(宽高互换) */
.shell-btn--icon {
width: 2.25rem;
padding: 0;
}
.shell-btn--icon-xs {
width: 1.5rem;
height: 1.5rem;
padding: 0;
font-size: 0.75rem;
}
.shell-btn--icon-sm {
width: 2rem;
height: 2rem;
padding: 0;
font-size: 0.8125rem;
}
.shell-btn--icon-lg {
width: 3rem;
height: 3rem;
padding: 0;
font-size: 1rem;
}
/* sm */
.shell-btn--sm {
height: 1.875rem;
padding-inline: 0.75rem;
font-size: 0.8125rem;
}
/* block */
.shell-btn--block {
width: 100%;
}
/* 旧式 .btn / .btn-sm 兼容(保留颜色语义,直角化) */
.btn,
.btn-sm {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
height: 2.25rem;
padding-inline: 1rem;
background: var(--primary);
color: var(--primary-foreground);
font-family: var(--font-mono);
font-size: 0.875rem;
font-weight: 500;
border: 1px solid transparent;
border-radius: var(--radius);
cursor: pointer;
transition: opacity var(--transition), background-color var(--transition),
color var(--transition);
}
.btn:hover,
.btn-sm:hover {
opacity: 0.85;
}
.btn:disabled,
.btn-sm:disabled {
opacity: 0.4;
cursor: not-allowed;
pointer-events: none;
}
.btn-sm {
height: 1.875rem;
padding-inline: 0.75rem;
font-size: 0.8125rem;
}
.btn-sm.green { background: var(--success); color: var(--gray-1); }
.btn-sm.red { background: var(--destructive); color: var(--gray-1); }
.btn-sm.gray { background: var(--secondary); color: var(--secondary-foreground); }
.btn-secondary { background: var(--gray-4); color: var(--foreground); }
.btn-success { background: var(--success); color: var(--gray-1); }
/* ============================== 组件Badge ============================== */
.shell-badge {
display: inline-flex;
align-items: center;
gap: 0.25rem;
font-family: var(--font-mono);
font-size: 0.625rem; /* text-[10px] */
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 1px 0.375rem; /* px-1.5 py-px */
line-height: 1.4;
border: 1px solid var(--border);
border-radius: 9999px; /* rounded-full唯一全圆保留 */
background: transparent;
color: var(--muted-foreground);
}
.shell-badge--primary {
background: var(--primary);
color: var(--primary-foreground);
border-color: var(--primary);
}
.shell-badge--success {
background: var(--success);
color: var(--gray-1);
border-color: var(--success);
}
.shell-badge--danger {
background: var(--destructive);
color: var(--gray-1);
border-color: var(--destructive);
}
.shell-badge--warn {
background: var(--warning);
color: var(--gray-1);
border-color: var(--warning);
}
/* ============================== 组件Card ============================== */
/* 直角细边框,[--card-spacing] 控制间距 */
.shell-card {
--card-spacing: 1.25rem;
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: var(--card-spacing);
}
.shell-card--popover {
background: var(--popover);
}
.shell-card--muted {
background: var(--muted);
}
.shell-card h3 {
font-size: 1rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
.shell-card .desc {
font-size: 0.8125rem;
color: var(--muted-foreground);
margin-bottom: 0.75rem;
}
/* ============================== 组件:输入控件 ============================== */
.shell-input,
.shell-textarea,
.shell-select,
input[type='text'],
input[type='number'],
input[type='tel'],
input[type='password'],
textarea,
select {
width: 100%;
padding: 0.5rem 0.75rem;
background: var(--gray-1);
color: var(--foreground);
border: 1px solid var(--input);
border-radius: var(--radius);
font-family: inherit;
font-size: 0.875rem;
outline: none;
transition: border-color var(--transition), background-color var(--transition);
}
input:focus,
textarea:focus,
select:focus {
border-color: var(--ring);
background: var(--gray-2);
}
input::placeholder,
textarea::placeholder {
color: var(--muted-foreground);
opacity: 0.7;
}
input:disabled,
textarea:disabled {
opacity: 0.5;
cursor: not-allowed;
}
textarea {
resize: vertical;
}
.shell-label,
label {
display: block;
font-size: 0.8125rem;
font-weight: 500;
color: var(--muted-foreground);
margin-bottom: 0.375rem;
}
/* 标签内的小 tag必填/可选) */
label .tag,
label .tag.r,
label .tag.o,
label .tag.g {
display: inline-block;
font-family: var(--font-mono);
font-size: 0.625rem;
text-transform: uppercase;
padding: 0 0.375rem;
margin-left: 0.375rem;
border: 1px solid var(--border);
border-radius: 9999px;
background: transparent;
color: var(--muted-foreground);
}
label .tag.r { border-color: var(--destructive); color: var(--destructive); }
label .tag.o { border-color: var(--warning); color: var(--warning); }
label .tag.g { border-color: var(--success); color: var(--success); }
/* Checkboxshadcn 风格,直角) */
input[type='checkbox'],
input[type='radio'] {
accent-color: var(--primary);
width: 1rem;
height: 1rem;
cursor: pointer;
}
.checkbox-group {
display: flex;
align-items: center;
gap: 0.5rem;
}
.checkbox-group label {
margin: 0;
cursor: pointer;
}
/* ============================== 组件Skeleton / Spinner ============================== */
.shell-skeleton {
position: relative;
overflow: hidden;
border-radius: var(--radius);
background: var(--gray-4);
}
.shell-skeleton::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(
90deg,
transparent,
color-mix(in srgb, var(--gray-5) 70%, transparent),
transparent
);
animation: shell-scan 1.5s ease-in-out infinite;
transform: translateX(-100%);
}
/* 三方块依次呼吸加载 */
.shell-spinner {
display: inline-flex;
gap: 0.375rem;
align-items: center;
justify-content: center;
}
.shell-spinner-dot {
width: 0.5rem;
height: 0.5rem;
background: var(--foreground);
animation: shell-spinner-dot 0.9s ease-in-out infinite;
}
.shell-spinner-dot:nth-child(1) { animation-delay: 0s; }
.shell-spinner-dot:nth-child(2) { animation-delay: 0.3s; }
.shell-spinner-dot:nth-child(3) { animation-delay: 0.6s; }
/* ============================== 动画系统shell- 前缀) ============================== */
@keyframes shell-blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
@keyframes shell-fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes shell-fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes shell-slide-in-left {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
@keyframes shell-slide-out-left {
from { transform: translateX(0); }
to { transform: translateX(-100%); }
}
@keyframes shell-slide-in-right {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
@keyframes shell-slide-out-right {
from { transform: translateX(0); }
to { transform: translateX(100%); }
}
@keyframes shell-slide-in-up {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
@keyframes shell-backdrop-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes shell-backdrop-out {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes shell-scan {
to { transform: translateX(100%); }
}
@keyframes shell-spinner-dot {
0%, 100% { opacity: 0.25; transform: scale(0.8); }
50% { opacity: 1; transform: scale(1); }
}
/* 直播在线:三层扩散光环 */
@keyframes live-pulse {
0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--live) 60%, transparent); }
100% { box-shadow: 0 0 0 0.75rem transparent; }
}
@keyframes live-ring {
0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--live) 55%, transparent),
0 0 0 0.5rem color-mix(in srgb, var(--live) 25%, transparent),
0 0 0 1rem color-mix(in srgb, var(--live) 10%, transparent); }
70% { box-shadow: 0 0 0 0.5rem color-mix(in srgb, var(--live) 25%, transparent),
0 0 0 1rem color-mix(in srgb, var(--live) 10%, transparent),
0 0 0 1.5rem transparent; }
}
/* B站闪烁光标 */
@keyframes bili-blink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: 0; }
}
.shell-anim-blink { animation: shell-blink 1.2s steps(2, start) infinite; }
.shell-anim-fade-in { animation: shell-fade-in 0.2s ease both; }
.shell-live-dot {
width: 0.5rem;
height: 0.5rem;
border-radius: 9999px;
background: var(--live);
animation: live-ring 2s ease-out infinite;
}
.shell-bili-caret::after {
content: '▍';
color: var(--bili);
animation: bili-blink 1s steps(2, start) infinite;
}
/* 尊重系统减弱动效 */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* ============================== 状态结果块 ============================== */
.shell-result {
margin-top: 1rem;
padding: 0.75rem 1rem;
border: 1px solid var(--border);
border-radius: var(--radius);
font-family: var(--font-mono);
font-size: 0.8125rem;
white-space: pre-wrap;
word-break: break-all;
text-align: left;
}
.shell-result--success { border-color: var(--success); color: var(--success); background: color-mix(in srgb, var(--success) 8%, transparent); }
.shell-result--error { border-color: var(--destructive); color: var(--destructive); background: color-mix(in srgb, var(--destructive) 8%, transparent); }
.shell-result--info { border-color: var(--info); color: var(--info); background: color-mix(in srgb, var(--info) 8%, transparent); }
.shell-result--warning { border-color: var(--warning); color: var(--warning); background: color-mix(in srgb, var(--warning) 8%, transparent); }
/* 旧 .result 兼容 */
.result {
margin-top: 1rem;
padding: 0.75rem 1rem;
border: 1px solid var(--border);
border-radius: var(--radius);
font-family: var(--font-mono);
font-size: 0.8125rem;
white-space: pre-wrap;
word-break: break-all;
}
.result.success { border-color: var(--success); color: var(--success); background: color-mix(in srgb, var(--success) 8%, transparent); }
.result.error { border-color: var(--destructive); color: var(--destructive); background: color-mix(in srgb, var(--destructive) 8%, transparent); }
.result.info { border-color: var(--info); color: var(--info); background: color-mix(in srgb, var(--info) 8%, transparent); }
.result.warning { border-color: var(--warning); color: var(--warning); background: color-mix(in srgb, var(--warning) 8%, transparent); }
/* 步骤 tag旧式兼容 */
.tag {
display: inline-flex;
align-items: center;
padding: 0.125rem 0.625rem;
border: 1px solid var(--border);
border-radius: 9999px;
font-family: var(--font-mono);
font-size: 0.75rem;
font-weight: 500;
color: var(--muted-foreground);
}
.tag.ok { border-color: var(--success); color: var(--success); background: color-mix(in srgb, var(--success) 8%, transparent); }
.tag.no { border-color: var(--destructive); color: var(--destructive); background: color-mix(in srgb, var(--destructive) 8%, transparent); }
.tag.busy { border-color: var(--warning); color: var(--warning); background: color-mix(in srgb, var(--warning) 8%, transparent); }
/* ============================== 代码块 ============================== */
pre {
margin: 0;
padding: 0.75rem 1rem;
background: var(--gray-2);
border: 1px solid var(--border);
border-radius: var(--radius);
font-family: var(--font-mono);
font-size: 0.8125rem;
line-height: 1.6;
overflow: auto;
white-space: pre-wrap;
word-break: break-all;
color: var(--foreground);
}
code {
font-family: var(--font-mono);
font-size: 0.875em;
}
/* ============================== 链接 ============================== */
a {
color: var(--foreground);
text-decoration: none;
transition: color var(--transition), border-color var(--transition);
}
a:hover {
color: var(--foreground);
text-decoration: underline;
}
a.shell-link-muted {
color: var(--muted-foreground);
}
a.shell-link-muted:hover {
color: var(--foreground);
text-decoration: underline;
}
/* ============================== 页脚 ============================== */
.shell-footer {
margin-top: 2.5rem;
padding-top: 0.75rem;
border-top: 1px solid var(--border);
color: var(--muted-foreground);
text-align: center;
font-size: 0.8125rem;
}
/* ============================== 通用工具 ============================== */
.shell-flex { display: flex; align-items: center; gap: 0.625rem; }
.shell-flex--sb { justify-content: space-between; }
.shell-flex--wrap { flex-wrap: wrap; }
.shell-flex--center { justify-content: center; }
.shell-mt-1 { margin-top: 0.25rem; }
.shell-mt-2 { margin-top: 0.5rem; }
.shell-mt-3 { margin-top: 0.75rem; }
.shell-mt-4 { margin-top: 1rem; }
.shell-mt-6 { margin-top: 1.5rem; }
.shell-mb-2 { margin-bottom: 0.5rem; }
.shell-mb-4 { margin-bottom: 1rem; }
.shell-gap-2 { gap: 0.5rem; }
.shell-gap-3 { gap: 0.75rem; }
.shell-hidden { display: none; }
.shell-mono { font-family: var(--font-mono); }
.shell-text-sm { font-size: 0.8125rem; }
.shell-text-xs { font-size: 0.75rem; }
.shell-muted { color: var(--muted-foreground); }
.shell-fade { transition: opacity var(--transition); }
/* 表单行 */
.form-group {
margin-bottom: 1rem;
text-align: left;
}
.row {
display: flex;
gap: 0.75rem;
flex-wrap: wrap;
}
.row .form-group {
flex: 1;
}
.quick-fill {
margin-top: 0.375rem;
font-size: 0.75rem;
color: var(--muted-foreground);
}
.quick-fill span {
color: var(--info);
cursor: pointer;
margin-right: 0.5rem;
}
.quick-fill span:hover {
text-decoration: underline;
}
/* ============================== 表格 ============================== */
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 1rem;
}
table th,
table td {
padding: 0.625rem 0.75rem;
text-align: left;
border-bottom: 1px solid var(--border);
}
table th {
font-family: var(--font-mono);
font-size: 0.8125rem;
font-weight: 600;
color: var(--muted-foreground);
background: var(--gray-2);
}
table td input {
width: 100%;
}
/* ============================== Tabs直角 ============================== */
.tabs {
display: flex;
gap: 0.25rem;
margin-bottom: 1.5rem;
border-bottom: 1px solid var(--border);
}
.tab-btn {
padding: 0.625rem 1rem;
background: transparent;
border: none;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
font-family: var(--font-mono);
font-size: 0.8125rem;
font-weight: 500;
color: var(--muted-foreground);
cursor: pointer;
transition: color var(--transition), border-color var(--transition);
}
.tab-btn:hover {
color: var(--foreground);
}
.tab-btn.active {
color: var(--foreground);
border-bottom-color: var(--foreground);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
animation: shell-fade-in 0.15s ease;
}
/* ============================== 音频 / 进度条 ============================== */
audio {
width: 100%;
}
audio::-webkit-media-controls-panel {
background-color: var(--gray-2);
}
.progress-bar {
height: 0.375rem;
background: var(--gray-4);
border-radius: 2px; /* rounded-[2px]:唯一 2px 微圆角 */
overflow: hidden;
}
.progress-bar .fill {
height: 100%;
background: var(--foreground);
border-radius: 2px;
transition: width 0.3s ease;
width: 0%;
}
.progress-item.success .fill { background: var(--success); }
.progress-item.error .fill { background: var(--destructive); }
.progress-item.error .status { color: var(--destructive); }
/* 头像rounded-full 保留) */
.avatar,
.shell-avatar {
border-radius: 9999px;
object-fit: cover;
}
/* Toast深色 */
.toast {
position: fixed;
bottom: 1.5rem;
left: 50%;
transform: translateX(-50%) translateY(6.25rem);
padding: 0.75rem 1.25rem;
border-radius: var(--radius);
font-size: 0.875rem;
font-weight: 500;
color: var(--gray-1);
background: var(--foreground);
box-shadow: 0 0.25rem 1.5rem rgba(0, 0, 0, 0.5);
opacity: 0;
transition: all 0.3s ease;
z-index: 1000;
max-width: 90%;
text-align: center;
}
.toast.show {
transform: translateX(-50%) translateY(0);
opacity: 1;
}
.toast.success { background: var(--success); }
.toast.error { background: var(--destructive); }
.toast.info { background: var(--info); color: var(--gray-1); }
/* ============================== 响应式 ============================== */
@media (max-width: 640px) {
.shell-main {
padding-top: calc(var(--topbar-h) + 1.5rem);
}
.shell-header h1 {
font-size: 1.375rem;
}
.shell-topbar {
padding-inline: 0.875rem;
}
}

View File

@ -6,472 +6,204 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="referrer" content="no-referrer" />
<link rel="icon" href="docs/netease.png" />
<meta name="referrer" content="no-referrer" />
<link rel="icon" href="docs/netease.png" />
<link rel="stylesheet" href="/static/shell.css" />
<title>云小编任务中心 - 网易云音乐 API Enhanced</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body { padding: 1.25rem; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-height: 100vh;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 600px;
margin: 40px auto;
background: white;
border-radius: 12px;
padding: 32px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
text-align: center;
}
.subtitle {
font-size: 14px;
color: #666;
margin-bottom: 24px;
text-align: center;
}
.login-link {
display: block;
margin-bottom: 20px;
color: #666;
font-size: 14px;
text-decoration: none;
text-align: center;
}
.login-link:hover {
color: #333;
text-decoration: underline;
}
/* ---- Section ---- */
.section {
margin-bottom: 24px;
padding-bottom: 20px;
border-bottom: 1px solid #eee;
margin-bottom: 1.5rem;
padding-bottom: 1.25rem;
border-bottom: 1px solid var(--border);
}
.section:last-of-type {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
margin-bottom: 0.75rem;
}
.section-title {
font-size: 16px;
font-family: var(--font-mono);
font-size: 1rem;
font-weight: 600;
color: #333;
}
/* ---- Form ---- */
.form-group {
margin-bottom: 16px;
text-align: left;
}
label {
display: block;
font-size: 13px;
font-weight: 500;
color: #555;
margin-bottom: 6px;
}
label .tag {
display: inline-block;
font-size: 11px;
font-weight: 400;
padding: 1px 6px;
border-radius: 3px;
margin-left: 6px;
}
label .tag.r {
background: #fee2e2;
color: #991b1b;
}
label .tag.o {
background: #fef3c7;
color: #92400e;
}
label .tag.g {
background: #d1fae5;
color: #065f46;
}
input[type="text"],
input[type="number"],
textarea {
width: 100%;
padding: 10px 14px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
outline: none;
font-family: inherit;
transition: border-color 0.2s;
}
input[type="text"]:focus,
input[type="number"]:focus,
textarea:focus {
border-color: #333;
}
textarea {
resize: vertical;
}
.row {
display: flex;
gap: 12px;
}
.row .form-group {
flex: 1;
}
.quick-fill {
margin-top: 5px;
font-size: 12px;
color: #666;
}
.quick-fill span {
color: #0066cc;
cursor: pointer;
margin-right: 8px;
}
.quick-fill span:hover {
text-decoration: underline;
}
.btn {
display: block;
width: 100%;
padding: 12px;
background: #333;
color: white;
font-size: 15px;
font-weight: 500;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s ease;
border: none;
text-align: center;
margin-top: 20px;
}
.btn:hover {
background: #555;
}
.btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.btn-sm {
display: inline-block;
width: auto;
padding: 6px 16px;
background: #333;
color: white;
font-size: 13px;
font-weight: 500;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s ease;
border: none;
text-align: center;
margin-top: 0;
}
.btn-sm:hover {
background: #555;
}
.btn-sm:disabled {
background: #ccc;
cursor: not-allowed;
}
.btn-sm.green {
background: #059669;
}
.btn-sm.green:hover {
background: #047857;
}
.btn-sm.green:disabled {
background: #a7f3d0;
}
.btn-sm.red {
background: #dc2626;
}
.btn-sm.red:hover {
background: #b91c1c;
}
.btn-sm.red:disabled {
background: #fca5a5;
}
.btn-sm.gray {
background: #6b7280;
}
.btn-sm.gray:hover {
background: #4b5563;
}
.btn-sm.gray:disabled {
background: #d1d5db;
}
.result {
margin-top: 16px;
padding: 12px 16px;
border-radius: 6px;
font-size: 14px;
text-align: left;
white-space: pre-wrap;
word-break: break-all;
font-family: monospace;
}
.result.success {
background: #d1fae5;
color: #065f46;
}
.result.error {
background: #fee2e2;
color: #991b1b;
}
.result.info {
background: #e0f2fe;
color: #0369a1;
}
.footer-link {
text-align: center;
margin-top: 24px;
font-size: 13px;
}
.footer-link a {
color: #666;
text-decoration: none;
}
.footer-link a:hover {
text-decoration: underline;
}
/* ---- Info Card ---- */
.info-card {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
background: #f9fafb;
border-radius: 8px;
border: 1px solid #eee;
gap: 0.75rem;
padding: 1rem;
background: var(--gray-2);
border: 1px solid var(--border);
}
.info-card .avatar {
width: 50px;
height: 50px;
border-radius: 50%;
width: 3.125rem;
height: 3.125rem;
border-radius: 9999px;
object-fit: cover;
background: #e5e7eb;
background: var(--gray-4);
}
.info-card .info-text {
flex: 1;
font-size: 14px;
color: #333;
font-size: 0.875rem;
color: var(--foreground);
}
.info-card .btn-sm {
flex-shrink: 0;
}
.info-card .btn-sm { flex-shrink: 0; }
#userPoint {
font-size: 14px;
font-size: 0.875rem;
font-family: var(--font-mono);
color: var(--success);
}
/* ---- Song Review ---- */
.song-review {
margin-top: 16px;
padding: 16px;
background: #f9fafb;
border-radius: 8px;
border: 1px solid #eee;
margin-top: 1rem;
padding: 1rem;
background: var(--gray-2);
border: 1px solid var(--border);
}
.song-review .cover {
width: 100px;
height: 100px;
border-radius: 6px;
width: 6.25rem;
height: 6.25rem;
border-radius: 2px;
object-fit: cover;
background: #e5e7eb;
background: var(--gray-4);
display: block;
margin-bottom: 12px;
margin-bottom: 0.75rem;
}
.song-review .song-title {
font-size: 14px;
font-size: 0.875rem;
font-weight: 600;
color: #333;
margin-bottom: 8px;
color: var(--foreground);
margin-bottom: 0.5rem;
}
.song-review audio {
width: 100%;
margin-bottom: 8px;
}
.song-review audio { width: 100%; margin-bottom: 0.5rem; }
.song-review .song-tag {
font-size: 13px;
color: #666;
margin-bottom: 10px;
font-size: 0.8125rem;
color: var(--muted-foreground);
margin-bottom: 0.625rem;
}
.song-review .btn-group {
display: flex;
gap: 8px;
margin-bottom: 10px;
gap: 0.5rem;
margin-bottom: 0.625rem;
flex-wrap: wrap;
}
.song-review .lyric {
font-size: 13px;
color: #555;
background: #fff;
padding: 10px 12px;
border-radius: 6px;
border: 1px solid #e5e7eb;
font-size: 0.8125rem;
color: var(--muted-foreground);
background: var(--gray-1);
padding: 0.625rem 0.75rem;
border: 1px solid var(--border);
white-space: pre-wrap;
max-height: 160px;
max-height: 10rem;
overflow-y: auto;
font-family: monospace;
font-family: var(--font-mono);
line-height: 1.6;
}
.status-text {
font-size: 14px;
color: #888;
.status-text { font-size: 0.875rem; color: var(--muted-foreground); }
.footer-link {
text-align: center;
margin-top: 1.5rem;
font-size: 0.8125rem;
}
.footer-link a { color: var(--muted-foreground); }
.footer-link a:hover { color: var(--foreground); text-decoration: underline; }
</style>
</head>
<body>
<div class="container">
<h1>云小编任务中心</h1>
<p class="subtitle">管理云音乐审核任务、签到与会员权益领取</p>
<header class="shell-topbar">
<a class="brand" href="/"><img src="docs/netease.png" alt="logo">云小编任务中心</a>
<nav>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/qrlogin-nocookie.html">登录</a>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/">返回首页</a>
</nav>
</header>
<a href="/qrlogin-nocookie.html" class="login-link">还没登录?点击登录</a>
<main class="shell-main">
<div class="shell-container shell-narrow">
<header class="shell-header">
<h1>云小编任务中心</h1>
<p class="sub">管理云音乐审核任务、签到与会员权益领取</p>
</header>
<!-- Cookie -->
<div class="form-group">
<label for="cookie">Cookie <span class="tag o">可选</span></label>
<textarea id="cookie" placeholder="留空则默认读取本地存储的登录态" rows="2"></textarea>
<div class="quick-fill">
<span onclick="loadLocalCookie()">读取本地 Cookie</span>
<span onclick="clearCookie()">清除 Cookie</span>
<span onclick="saveCookie()">保存 Cookie</span>
<span onclick="loadAll()">一键载入</span>
</div>
</div>
<div class="section">
<div class="section-header">
<span class="section-title">用户信息</span>
<button class="btn-sm" id="userGet">获取用户详情</button>
</div>
<div class="info-card" id="user">
<img src="docs/netease.png" alt="头像" class="avatar" id="userAvatar" />
<span class="info-text" id="userName">未登录</span>
<span id="userPoint">0</span>
<button class="btn-sm green" id="userSign" disabled>签到</button>
</div>
</div>
<div class="section">
<div class="section-header">
<span class="section-title">会员领取</span>
<button class="btn-sm" id="vipGet">获取会员详情</button>
</div>
<div class="info-card" id="vip">
<span class="info-text" id="vipStatus">未载入</span>
<button class="btn-sm" id="vipCollect" disabled>领取</button>
</div>
</div>
<div class="section">
<div class="section-header">
<span class="section-title">审核任务</span>
<button class="btn-sm" id="taskGet">获取任务详情</button>
</div>
<div class="info-card" id="task">
<span class="info-text" id="taskInfo">[<span id="taskCount">0</span>/<span id="taskGoal">0</span>] 审核歌曲赚贡献分 (+<span id="taskPoint">0</span>)</span>
<button class="btn-sm" id="taskStart" disabled>去完成</button>
</div>
<!-- Song Review -->
<div class="song-review" id="song" style="display: none;">
<img alt="封面" class="cover" id="songCover" />
<div class="song-title" id="songTitle">未载入</div>
<audio controls id="songAudio"></audio>
<div class="song-tag" id="songTag"></div>
<div class="btn-group">
<button class="btn-sm green" id="songApprove" disabled>同意</button>
<button class="btn-sm red" id="songReject" disabled>否决</button>
<button class="btn-sm gray" id="songSkip" disabled>跳过 (不计次数)</button>
<section class="shell-card">
<!-- Cookie -->
<div class="form-group">
<label for="cookie">Cookie <span class="tag o">可选</span></label>
<textarea id="cookie" placeholder="留空则默认读取本地存储的登录态" rows="2"></textarea>
<div class="quick-fill">
<span onclick="loadLocalCookie()">读取本地 Cookie</span>
<span onclick="clearCookie()">清除 Cookie</span>
<span onclick="saveCookie()">保存 Cookie</span>
<span onclick="loadAll()">一键载入</span>
</div>
</div>
<div class="lyric" id="songLyric">[00:00.00] 暂无歌词</div>
</div>
</div>
<div id="result" class="result" style="display: none;"></div>
<div class="section">
<div class="section-header">
<span class="section-title">用户信息</span>
<button class="btn-sm" id="userGet">获取用户详情</button>
</div>
<div class="info-card" id="user">
<img src="docs/netease.png" alt="头像" class="avatar" id="userAvatar" />
<span class="info-text" id="userName">未登录</span>
<span id="userPoint">0</span>
<button class="btn-sm green" id="userSign" disabled>签到</button>
</div>
</div>
<div class="footer-link">
<a href="/">返回首页</a>
<div class="section">
<div class="section-header">
<span class="section-title">会员领取</span>
<button class="btn-sm" id="vipGet">获取会员详情</button>
</div>
<div class="info-card" id="vip">
<span class="info-text" id="vipStatus">未载入</span>
<button class="btn-sm" id="vipCollect" disabled>领取</button>
</div>
</div>
<div class="section">
<div class="section-header">
<span class="section-title">审核任务</span>
<button class="btn-sm" id="taskGet">获取任务详情</button>
</div>
<div class="info-card" id="task">
<span class="info-text" id="taskInfo">[<span id="taskCount">0</span>/<span id="taskGoal">0</span>] 审核歌曲赚贡献分 (+<span id="taskPoint">0</span>)</span>
<button class="btn-sm" id="taskStart" disabled>去完成</button>
</div>
<!-- Song Review -->
<div class="song-review" id="song" style="display: none;">
<img alt="封面" class="cover" id="songCover" />
<div class="song-title" id="songTitle">未载入</div>
<audio controls id="songAudio"></audio>
<div class="song-tag" id="songTag"></div>
<div class="btn-group">
<button class="btn-sm green" id="songApprove" disabled>同意</button>
<button class="btn-sm red" id="songReject" disabled>否决</button>
<button class="btn-sm gray" id="songSkip" disabled>跳过 (不计次数)</button>
</div>
<div class="lyric" id="songLyric">[00:00.00] 暂无歌词</div>
</div>
</div>
<div id="result" class="result" style="display: none;"></div>
<div class="footer-link">
<a href="/">返回首页</a>
</div>
</section>
</div>
</div>
</main>
<script>
(function () {

View File

@ -6,230 +6,26 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="referrer" content="no-referrer" />
<link rel="icon" href="docs/netease.png" />
<link rel="stylesheet" href="/static/shell.css" />
<title>云小编每日抽奖 - 网易云音乐 API Enhanced</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-height: 100vh;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 520px;
margin: 40px auto;
background: white;
border-radius: 12px;
padding: 32px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
text-align: center;
}
.subtitle {
font-size: 14px;
color: #666;
margin-bottom: 24px;
text-align: center;
}
.subtitle b {
color: #d63031;
}
/* ---- Section ---- */
.section {
margin-bottom: 24px;
padding-bottom: 20px;
border-bottom: 1px solid #eee;
}
.section:last-of-type {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #333;
}
/* ---- Form Elements ---- */
.form-group {
margin-bottom: 16px;
text-align: left;
}
label {
display: block;
font-size: 13px;
font-weight: 500;
color: #555;
margin-bottom: 6px;
}
label .tag {
display: inline-block;
font-size: 11px;
font-weight: 400;
padding: 1px 6px;
border-radius: 3px;
margin-left: 6px;
}
label .tag.o {
background: #fef3c7;
color: #92400e;
}
input[type="text"],
input[type="number"],
textarea {
width: 100%;
padding: 10px 14px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
outline: none;
font-family: inherit;
transition: border-color 0.2s;
}
input[type="text"]:focus,
input[type="number"]:focus,
textarea:focus {
border-color: #333;
}
textarea {
resize: vertical;
}
.btn {
display: block;
width: 100%;
padding: 12px;
background: #333;
color: white;
font-size: 15px;
font-weight: 500;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s ease;
border: none;
text-align: center;
margin-top: 16px;
}
.btn:hover {
background: #555;
}
.btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.btn-sm {
display: inline-block;
width: auto;
padding: 6px 16px;
background: #333;
color: white;
font-size: 13px;
font-weight: 500;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s ease;
border: none;
text-align: center;
margin-top: 0;
}
.btn-sm:hover {
background: #555;
}
.btn-sm:disabled {
background: #ccc;
cursor: not-allowed;
}
.btn-sm.gray {
background: #6b7280;
}
.btn-sm.gray:hover {
background: #4b5563;
}
.btn-sm.gray:disabled {
background: #d1d5db;
}
.btn-sm.green {
background: #059669;
}
.btn-sm.green:hover {
background: #047857;
}
.btn-sm.green:disabled {
background: #a7f3d0;
}
.quick-fill {
margin-top: 5px;
font-size: 12px;
color: #666;
}
.quick-fill span {
color: #0066cc;
cursor: pointer;
margin-right: 8px;
}
.quick-fill span:hover {
text-decoration: underline;
}
body { padding: 1.25rem; }
.shell-header { text-align: center; }
.subtitle { text-align: center; }
.subtitle b { color: var(--destructive); }
/* ---- Lottery Display ---- */
.lottery-display {
text-align: center;
padding: 24px 16px;
background: #f9fafb;
border-radius: 8px;
border: 1px solid #eee;
margin-bottom: 16px;
padding: 1.5rem 1rem;
background: var(--gray-2);
border: 1px solid var(--border);
margin-bottom: 1rem;
}
.rolling-display {
font-size: 2.4rem;
font-weight: 800;
color: #333;
color: var(--foreground);
min-height: 4rem;
display: flex;
align-items: center;
@ -237,200 +33,155 @@
letter-spacing: 1px;
word-break: break-all;
}
.rolling-display.idle {
color: #999;
color: var(--muted-foreground);
font-size: 1.2rem;
font-weight: 500;
}
/* ---- Winner Result ---- */
.winner-display {
background: #f0fdf4;
border: 1px solid #86efac;
border-radius: 8px;
padding: 20px 16px;
margin-top: 16px;
background: color-mix(in srgb, var(--success) 8%, var(--gray-2));
border: 1px solid var(--success);
padding: 1.25rem 1rem;
margin-top: 1rem;
text-align: center;
}
.winner-label {
font-size: 14px;
font-family: var(--font-mono);
font-size: 0.875rem;
font-weight: 600;
color: #065f46;
margin-bottom: 8px;
color: var(--success);
margin-bottom: 0.5rem;
}
.winner-name {
font-size: 2rem;
font-weight: 800;
color: #dc2626;
color: var(--foreground);
letter-spacing: 1px;
word-break: break-all;
}
.confetti-row {
margin-top: 8px;
margin-top: 0.5rem;
display: flex;
justify-content: center;
gap: 0.5rem;
}
.confetti-emoji {
font-size: 1.6rem;
animation: bounce 0.6s ease infinite alternate;
}
.confetti-emoji:nth-child(2) {
animation-delay: 0.15s;
}
.confetti-emoji:nth-child(3) {
animation-delay: 0.3s;
}
.confetti-emoji:nth-child(2) { animation-delay: 0.15s; }
.confetti-emoji:nth-child(3) { animation-delay: 0.3s; }
@keyframes bounce {
from {
transform: translateY(0);
}
to {
transform: translateY(-8px);
}
from { transform: translateY(0); }
to { transform: translateY(-8px); }
}
/* ---- Status ---- */
.status-bar {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 14px;
background: #f9fafb;
border-radius: 6px;
margin-top: 16px;
font-size: 13px;
color: #666;
gap: 0.5rem;
padding: 0.625rem 0.875rem;
background: var(--gray-2);
border: 1px solid var(--border);
margin-top: 1rem;
font-size: 0.8125rem;
color: var(--muted-foreground);
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #059669;
width: 0.5rem;
height: 0.5rem;
border-radius: 9999px;
background: var(--success);
flex-shrink: 0;
animation: pulse 1.5s ease infinite;
}
.status-dot.error {
background: #dc2626;
}
.status-dot.idle {
background: #9ca3af;
animation: none;
}
.status-dot.error { background: var(--destructive); }
.status-dot.idle { background: var(--gray-5); animation: none; }
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.4;
}
}
.status-text {
flex: 1;
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.status-text { flex: 1; }
/* ---- Footer ---- */
.footer-link {
text-align: center;
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid #eee;
font-size: 13px;
margin-top: 1.5rem;
padding-top: 1rem;
border-top: 1px solid var(--border);
font-size: 0.8125rem;
}
.footer-link a { color: var(--muted-foreground); }
.footer-link a:hover { color: var(--foreground); text-decoration: underline; }
.footer-link a {
color: #666;
text-decoration: none;
}
.footer-link a:hover {
text-decoration: underline;
}
/* ---- Responsive ---- */
@media (max-width: 500px) {
.container {
padding: 20px 16px;
margin: 20px auto;
}
h1 {
font-size: 20px;
}
.rolling-display {
font-size: 1.8rem;
min-height: 3rem;
}
.winner-name {
font-size: 1.5rem;
}
.rolling-display { font-size: 1.8rem; min-height: 3rem; }
.winner-name { font-size: 1.5rem; }
}
</style>
</head>
<body>
<div class="container">
<h1>云小编每日抽奖</h1>
<p class="subtitle">每次抽奖消耗 <b>200 个曲库编辑部积分</b>,每日最多可抽奖 3 次</p>
<header class="shell-topbar">
<a class="brand" href="/"><img src="docs/netease.png" alt="logo">云小编抽奖</a>
<nav>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/ugc.html">任务中心</a>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/">返回首页</a>
</nav>
</header>
<!-- Cookie 管理 -->
<div class="form-group">
<label for="cookieInput">令牌 <span class="tag o">可选</span></label>
<input type="text" id="cookieInput" placeholder="留空则默认读取本地存储的令牌" autocomplete="off" />
<div class="quick-fill">
<span onclick="readLocalCookie()">读取本地令牌</span>
<span onclick="clearCookie()">清除令牌</span>
<span onclick="saveCookie()">保存令牌</span>
</div>
<main class="shell-main">
<div class="shell-container shell-narrow" style="max-width:32rem">
<header class="shell-header">
<h1>云小编每日抽奖</h1>
<p class="subtitle">每次抽奖消耗 <b>200 个曲库编辑部积分</b>,每日最多可抽奖 3 次</p>
</header>
<section class="shell-card">
<!-- Cookie 管理 -->
<div class="form-group">
<label for="cookieInput">令牌 <span class="tag o">可选</span></label>
<input type="text" id="cookieInput" placeholder="留空则默认读取本地存储的令牌" autocomplete="off" />
<div class="quick-fill">
<span onclick="readLocalCookie()">读取本地令牌</span>
<span onclick="clearCookie()">清除令牌</span>
<span onclick="saveCookie()">保存令牌</span>
</div>
</div>
<!-- 抽奖区域 -->
<div class="section" style="border-bottom:none;padding-bottom:0;margin-bottom:0">
<div class="section-header">
<span class="section-title" style="font-family:var(--font-mono);font-size:1rem;font-weight:600">每日抽奖</span>
</div>
<div class="lottery-display">
<div class="rolling-display idle" id="rollingDisplay">等待抽奖</div>
<div id="resultContainer"></div>
</div>
<button class="shell-btn shell-btn--default shell-btn--block" id="lotteryBtn">
🎲 开始抽奖
</button>
<div class="status-bar" id="statusBar">
<span class="status-dot idle" id="statusDot"></span>
<span class="status-text" id="statusText">就绪,点击按钮开始抽奖</span>
</div>
</div>
<!-- 页脚 -->
<div class="footer-link">
<a href="/">← 返回首页</a>
<span style="margin: 0 0.5rem; color: var(--border);">·</span>
<a href="/ugc.html">云小编任务中心</a>
</div>
</section>
</div>
<!-- 抽奖区域 -->
<div class="section">
<div class="section-header">
<span class="section-title">每日抽奖</span>
</div>
<div class="lottery-display">
<div class="rolling-display idle" id="rollingDisplay">等待抽奖</div>
<div id="resultContainer"></div>
</div>
<button class="btn" id="lotteryBtn">
🎲 开始抽奖
</button>
<div class="status-bar" id="statusBar">
<span class="status-dot idle" id="statusDot"></span>
<span class="status-text" id="statusText">就绪,点击按钮开始抽奖</span>
</div>
</div>
<!-- 页脚 -->
<div class="footer-link">
<a href="/">← 返回首页</a>
<span style="margin: 0 8px; color: #ddd;">·</span>
<a href="/ugc.html">云小编任务中心</a>
</div>
</div>
</main>
<script>
(function () {

View File

@ -3,123 +3,52 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="docs/netease.png">
<link rel="stylesheet" href="/static/shell.css">
<title>音乐解灰测试</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-height: 100vh;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
border-radius: 12px;
padding: 32px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 24px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
font-size: 14px;
font-weight: 500;
color: #555;
margin-bottom: 8px;
}
input {
width: 100%;
padding: 12px 14px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 15px;
outline: none;
}
input:focus {
border-color: #333;
}
button {
background: #333;
color: white;
padding: 14px 28px;
border: none;
border-radius: 6px;
font-size: 15px;
font-weight: 500;
cursor: pointer;
transition: background 0.2s ease;
}
button:hover {
background: #555;
}
button:disabled {
background: #999;
cursor: not-allowed;
}
#result {
margin-top: 24px;
padding: 16px;
background: #f9f9f9;
border-radius: 6px;
border: 1px solid #eee;
font-family: 'Courier New', monospace;
font-size: 13px;
white-space: pre-wrap;
word-break: break-all;
min-height: 100px;
}
body { padding: 1.25rem; }
.hint {
font-size: 12px;
color: #999;
margin-top: 4px;
font-size: 0.75rem;
color: var(--muted-foreground);
margin-top: 0.25rem;
}
</style>
</head>
<body>
<div class="container">
<h1>音乐解灰测试</h1>
<header class="shell-topbar">
<a class="brand" href="/"><img src="docs/netease.png" alt="logo">解灰测试</a>
<nav>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/">返回首页</a>
</nav>
</header>
<div class="form-group">
<label for="songId">音乐 ID</label>
<input type="number" id="songId" placeholder="请输入音乐ID" />
<div class="hint">例如: 1372188635</div>
</div>
<main class="shell-main">
<div class="shell-container shell-narrow" style="max-width:50rem">
<header class="shell-header">
<h1>音乐解灰测试</h1>
<p class="sub">测试多音源替换匹配,解除灰色歌曲限制</p>
</header>
<div class="form-group">
<label for="sources">音源列表(可选)</label>
<input type="text" id="sources" placeholder="请输入音源" />
<div class="hint">例如: kuwo, kugou, migu</div>
</div>
<section class="shell-card">
<div class="form-group">
<label for="songId">音乐 ID</label>
<input type="number" id="songId" placeholder="请输入音乐ID" />
<div class="hint">例如: 1372188635</div>
</div>
<button id="testBtn" onclick="testSong()">开始测试</button>
<div class="form-group">
<label for="sources">音源列表(可选)</label>
<input type="text" id="sources" placeholder="请输入音源" />
<div class="hint">例如: kuwo, kugou, migu</div>
</div>
<div id="result"></div>
</div>
<button id="testBtn" class="shell-btn shell-btn--default" onclick="testSong()">开始测试</button>
<pre id="result" style="margin-top:1.5rem;min-height:6.25rem"></pre>
</section>
</div>
</main>
<script>
async function testSong() {

View File

@ -3,233 +3,154 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="docs/netease.png" />
<link rel="stylesheet" href="/static/shell.css" />
<title>播客上传声音</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-height: 100vh;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
background: white;
border-radius: 12px;
padding: 32px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 24px;
}
.login-link {
display: block;
margin-bottom: 24px;
color: #666;
font-size: 14px;
text-decoration: none;
}
.login-link:hover {
color: #333;
text-decoration: underline;
}
body { padding: 1.25rem; }
.content {
display: flex;
gap: 24px;
gap: 1.5rem;
flex-wrap: wrap;
}
.voice-list {
flex: 1;
min-width: 300px;
}
.voice-list { flex: 1; min-width: 300px; }
.voice-item {
padding: 12px 16px;
border: 1px solid #eee;
border-radius: 6px;
margin-bottom: 8px;
padding: 0.75rem 1rem;
border: 1px solid var(--border);
margin-bottom: 0.5rem;
cursor: pointer;
transition: all 0.2s ease;
transition: all var(--transition);
}
.voice-item:hover {
border-color: #333;
background: #f9f9f9;
border-color: var(--foreground);
background: var(--gray-2);
}
.voice-item.active {
border-color: #333;
background: #f0f0f0;
border-color: var(--foreground);
background: var(--gray-4);
}
.voice-header {
display: flex;
align-items: center;
gap: 12px;
gap: 0.75rem;
}
.voice-cover {
width: 50px;
height: 50px;
border-radius: 4px;
width: 3.125rem;
height: 3.125rem;
border-radius: 2px;
object-fit: cover;
background: var(--gray-4);
}
.voice-name {
font-size: 14px;
font-size: 0.875rem;
font-weight: 500;
color: #333;
color: var(--foreground);
}
.voice-tracks {
margin-top: 8px;
padding-left: 62px;
margin-top: 0.5rem;
padding-left: 3.875rem;
}
.voice-track {
font-size: 13px;
color: #666;
padding: 4px 0;
font-size: 0.8125rem;
color: var(--muted-foreground);
padding: 0.25rem 0;
}
.upload-section {
flex: 1;
min-width: 300px;
}
.form-group {
margin-bottom: 16px;
}
label {
display: block;
font-size: 14px;
font-weight: 500;
color: #555;
margin-bottom: 8px;
}
input[type="text"], input[type="file"] {
width: 100%;
padding: 10px 14px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
outline: none;
}
input[type="text"]:focus {
border-color: #333;
}
.btn {
width: 100%;
padding: 12px;
background: #333;
color: white;
font-size: 15px;
font-weight: 500;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s ease;
}
.btn:hover {
background: #555;
}
.upload-section { flex: 1; min-width: 300px; }
.empty-state {
text-align: center;
padding: 40px 20px;
color: #999;
font-size: 14px;
padding: 2.5rem 1.25rem;
color: var(--muted-foreground);
font-size: 0.875rem;
}
.loading {
text-align: center;
padding: 20px;
color: #666;
padding: 1.25rem;
color: var(--muted-foreground);
}
.section-h3 {
font-family: var(--font-mono);
font-size: 1rem;
font-weight: 600;
margin-bottom: 1rem;
}
</style>
</head>
<body>
<div class="container">
<h1>播客上传声音</h1>
<a href="/qrlogin-nocookie.html" class="login-link">还没登录?点击登录</a>
<header class="shell-topbar">
<a class="brand" href="/"><img src="docs/netease.png" alt="logo">播客上传声音</a>
<nav>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/qrlogin-nocookie.html">登录</a>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/">返回首页</a>
</nav>
</header>
<div class="content">
<div class="voice-list">
<h3 style="font-size: 16px; font-weight: 600; color: #333; margin-bottom: 16px;">选择播客列表</h3>
<div v-if="loading" class="loading">加载中...</div>
<div v-else-if="voicelist.length > 0">
<div
v-for="(item, index) in voicelist"
:key="index"
@click="currentVoiceIndex = index"
:class="{ active: currentVoiceIndex === index }"
class="voice-item"
>
<div class="voice-header">
<img :src="item.coverUrl" class="voice-cover" alt="cover" />
<span class="voice-name">{{ item.voiceListName }}</span>
</div>
<div class="voice-tracks" v-if="item.voiceListData">
<main class="shell-main">
<div class="shell-container shell-narrow" style="max-width:56rem">
<header class="shell-header">
<h1>播客上传声音</h1>
<p class="sub">上传音频到你的播客列表</p>
</header>
<section class="shell-card">
<div class="content">
<div class="voice-list">
<h3 class="section-h3">选择播客列表</h3>
<div v-if="loading" class="loading">加载中...</div>
<div v-else-if="voicelist.length > 0">
<div
v-for="(item2, index2) in item.voiceListData"
:key="index2"
class="voice-track"
v-for="(item, index) in voicelist"
:key="index"
@click="currentVoiceIndex = index"
:class="{ active: currentVoiceIndex === index }"
class="voice-item"
>
{{ item2.voiceName }}
<div class="voice-header">
<img :src="item.coverUrl" class="voice-cover" alt="cover" />
<span class="voice-name">{{ item.voiceListName }}</span>
</div>
<div class="voice-tracks" v-if="item.voiceListData">
<div
v-for="(item2, index2) in item.voiceListData"
:key="index2"
class="voice-track"
>
{{ item2.voiceName }}
</div>
</div>
</div>
</div>
<div v-else class="empty-state">暂无播客列表</div>
</div>
<div class="upload-section">
<h3 class="section-h3">上传声音</h3>
<div class="form-group">
<label for="songName">声音名称</label>
<input id="songName" v-model="songName" placeholder="请输入声音名称" />
</div>
<div class="form-group">
<label for="description">介绍</label>
<input id="description" v-model="description" placeholder="请输入介绍" />
</div>
<div class="form-group">
<label for="songFile">选择声音文件</label>
<input id="songFile" type="file" name="songFile" accept="audio/*" />
</div>
<div class="form-group">
<label for="imgFile">选择声音封面(可选)</label>
<input id="imgFile" type="file" name="imgFile" accept="image/*" />
</div>
<button class="shell-btn shell-btn--default shell-btn--block" @click="submit">上传</button>
</div>
</div>
<div v-else class="empty-state">暂无播客列表</div>
</div>
<div class="upload-section">
<h3 style="font-size: 16px; font-weight: 600; color: #333; margin-bottom: 16px;">上传声音</h3>
<div class="form-group">
<label for="songName">声音名称</label>
<input id="songName" v-model="songName" placeholder="请输入声音名称" />
</div>
<div class="form-group">
<label for="description">介绍</label>
<input id="description" v-model="description" placeholder="请输入介绍" />
</div>
<div class="form-group">
<label for="songFile">选择声音文件</label>
<input id="songFile" type="file" name="songFile" accept="audio/*" />
</div>
<div class="form-group">
<label for="imgFile">选择声音封面(可选)</label>
<input id="imgFile" type="file" name="imgFile" accept="image/*" />
</div>
<button class="btn" @click="submit">上传</button>
</div>
</section>
</div>
</div>
</main>
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
<script src="https://fastly.jsdelivr.net/npm/vue@3"></script>

View File

@ -4,74 +4,57 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="icon" href="docs/netease.png">
<link rel="stylesheet" href="/static/shell.css">
<title>Token 注册器 — 网易云音乐 API Enhanced</title>
<style>
:root {
--fg: #333;
--muted: #666;
--border: #ddd;
--bg: #f5f5f5;
--panel: #ffffff;
--accent: #333;
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: var(--fg); background: var(--bg); line-height: 1.6; }
.container { max-width: 720px; margin: 40px auto; padding: 0 20px; }
@media (max-width: 480px) {
.container { margin: 20px auto; padding: 0 16px; }
header.site-header h1 { font-size: 22px; }
.block { padding: 16px; }
}
header.site-header { margin-bottom: 24px; }
header.site-header h1 { font-size: 24px; font-weight: 600; margin: 0; }
.sub { margin-top: 4px; color: var(--muted); font-size: 14px; }
.block { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 20px; margin-bottom: 16px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); }
.block h3 { margin: 0 0 12px; font-size: 15px; font-weight: 600; }
pre { margin: 0; background: #f9f9f9; border: 1px solid var(--border); border-radius: 6px; padding: 12px; overflow-x: auto; white-space: pre-wrap; word-break: break-all; font-family: 'Courier New', monospace; font-size: 13px; max-height: 160px; overflow: auto; }
.row { display: flex; gap: 8px; margin-top: 14px; flex-wrap: wrap; }
button { padding: 8px 18px; border-radius: 8px; border: 1px solid var(--border); font-size: 14px; font-weight: 500; cursor: pointer; background: var(--panel); color: var(--fg); transition: all .15s; }
button:hover { background: #eee; }
button.primary { background: var(--fg); color: var(--panel); border-color: var(--fg); }
button.primary:hover { opacity: .85; }
button:disabled { opacity: .4; cursor: not-allowed; }
.tag { display: inline-block; padding: 2px 10px; border-radius: 20px; font-size: 12px; font-weight: 500; border: 1px solid var(--border); }
.tag.ok { background: #e6f7e6; color: #1a7a1a; border-color: #b7e6b7; }
.tag.no { background: #ffe6e6; color: #a11; border-color: #f5c6c6; }
.tag.busy { background: #fff3cd; color: #856404; border-color: #ffeeba; }
.flex { display: flex; align-items: center; gap: 10px; }
body { padding: 1.25rem; }
.flex { display: flex; align-items: center; gap: 0.625rem; }
.flex.sb { justify-content: space-between; }
.mt-2 { margin-top: 14px; }
footer.site-footer { margin-top: 24px; padding-top: 12px; border-top: 1px solid var(--border); color: var(--muted); text-align: center; font-size: 13px; }
.token-display {
font-family: var(--font-mono);
font-size: 0.8125rem;
color: var(--success);
word-break: break-all;
}
</style>
</head>
<body>
<main class="container">
<header class="site-header">
<h1>🔑 Token 注册器</h1>
<p class="sub">从易盾获取反作弊 token 并注册到服务器,供广告等需要 checkToken 的接口使用</p>
</header>
<header class="shell-topbar">
<a class="brand" href="/"><img src="docs/netease.png" alt="logo">Token 注册器</a>
<nav>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/">返回首页</a>
</nav>
</header>
<div class="block">
<div class="flex sb">
<h3>当前 Token</h3>
<span id="statusTag" class="tag no">未注册</span>
</div>
<pre id="tokenDisplay">(空)</pre>
<div class="row">
<button id="btnGet" class="primary">获取 Token</button>
<button id="btnRefresh">强制刷新</button>
</div>
<main class="shell-main">
<div class="shell-container shell-narrow" style="max-width:45rem">
<header class="shell-header">
<h1>🔑 Token 注册器</h1>
<p class="sub">从易盾获取反作弊 token 并注册到服务器,供广告等需要 checkToken 的接口使用</p>
</header>
<section class="shell-card">
<div class="flex sb" style="margin-bottom:0.625rem">
<h3>当前 Token</h3>
<span id="statusTag" class="tag no">未注册</span>
</div>
<pre id="tokenDisplay">(空)</pre>
<div class="shell-flex shell-mt-3">
<button id="btnGet" class="shell-btn shell-btn--default">获取 Token</button>
<button id="btnRefresh" class="shell-btn shell-btn--outline">强制刷新</button>
</div>
</section>
<section class="shell-card shell-mt-6">
<h3>服务器响应</h3>
<pre id="respDisplay">{}</pre>
</section>
<footer class="shell-footer">
<a href="/">← 返回首页</a>
</footer>
</div>
<div class="block">
<h3>服务器响应</h3>
<pre id="respDisplay">{}</pre>
</div>
<footer class="site-footer">
<a href="/">← 返回首页</a>
</footer>
</main>
<script>