mirror of
https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced.git
synced 2026-03-21 19:13:10 +00:00
Compare commits
No commits in common. "51a958936c3636993df76e1c0e0dd38a4c1ac6d0" and "d760b2960ab4d98fc949c379df2639d9a1d6789e" have entirely different histories.
51a958936c
...
d760b2960a
10
CHANGELOG.MD
10
CHANGELOG.MD
@ -7,16 +7,6 @@
|
|||||||
- chore: 更新依赖项 (music-metadata: ^11.11.1 -> ^11.11.2, ansi-escapes: ^7.2.0 -> ^7.3.0, commander: ^14.0.2 -> ^14.0.3)
|
- chore: 更新依赖项 (music-metadata: ^11.11.1 -> ^11.11.2, ansi-escapes: ^7.2.0 -> ^7.3.0, commander: ^14.0.2 -> ^14.0.3)
|
||||||
- chore: 更新GitHub Actions (checkout: v4 -> v6, setup-node: v4 -> v6, upload-artifact: v4 -> v6, download-artifact: v4 -> v7, github-script: v7 -> v8)
|
- chore: 更新GitHub Actions (checkout: v4 -> v6, setup-node: v4 -> v6, upload-artifact: v4 -> v6, download-artifact: v4 -> v7, github-script: v7 -> v8)
|
||||||
- refactor: 注释掉IP地址日志输出以提升隐私保护
|
- refactor: 注释掉IP地址日志输出以提升隐私保护
|
||||||
- refactor: 重构前端测试页面, 主要改进:
|
|
||||||
- 统一使用简洁现代的设计风格
|
|
||||||
- 去除渐变背景和复杂动画
|
|
||||||
- 使用纯色背景(#f5f5f5)和白色卡片
|
|
||||||
- 优化表单布局和交互体验
|
|
||||||
- 增强错误处理和加载状态
|
|
||||||
- 移除第三方框架依赖(Tailwind、Bootstrap、MDUI)
|
|
||||||
- 升级Vue 2到Vue 3
|
|
||||||
- 将硬编码的配置项移至前端表单
|
|
||||||
- 所有页面现在都保持一致的设计语言,简洁清爽,功能完整。
|
|
||||||
|
|
||||||
### 4.25.0 | 2024.11.16
|
### 4.25.0 | 2024.11.16
|
||||||
- feat: 增加副歌时间、相关歌单推荐接口,原有相关歌单接口已废弃;fix: 将部分易盾白名单接口替换为eapi [#30](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/30)
|
- feat: 增加副歌时间、相关歌单推荐接口,原有相关歌单接口已废弃;fix: 将部分易盾白名单接口替换为eapi [#30](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/30)
|
||||||
|
|||||||
164
public/api.html
164
public/api.html
@ -5,148 +5,82 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>API 调试界面</title>
|
<title>API 调试界面</title>
|
||||||
<style>
|
<style>
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
min-height: 100vh;
|
margin: 20px;
|
||||||
background: #f5f5f5;
|
display: flex;
|
||||||
padding: 20px;
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: 1200px;
|
display: flex;
|
||||||
margin: 0 auto;
|
flex-direction: column;
|
||||||
background: white;
|
flex-grow: 1;
|
||||||
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 {
|
form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
gap: 16px;
|
align-items: center;
|
||||||
margin-bottom: 24px;
|
gap: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
input, button {
|
||||||
.form-row {
|
padding: 10px;
|
||||||
display: flex;
|
box-sizing: border-box;
|
||||||
gap: 12px;
|
flex: 1;
|
||||||
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 {
|
button {
|
||||||
background: #333;
|
background-color: #4CAF50;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 10px 24px;
|
border: none;
|
||||||
border: none;
|
cursor: pointer;
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.2s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-result {
|
.data-result {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
flex-direction: row;
|
||||||
min-height: 400px;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-result > div {
|
.data-result > div {
|
||||||
flex: 1;
|
display: flex;
|
||||||
display: flex;
|
flex-direction: column;
|
||||||
flex-direction: column;
|
flex-grow: 1;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-result label {
|
.data-result label {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 10px;
|
||||||
padding: 0;
|
|
||||||
}
|
}
|
||||||
|
#data, #result {
|
||||||
textarea {
|
height: 100%;
|
||||||
flex: 1;
|
box-sizing: border-box;
|
||||||
padding: 12px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
font-size: 13px;
|
|
||||||
resize: vertical;
|
|
||||||
min-height: 350px;
|
|
||||||
outline: none;
|
|
||||||
}
|
}
|
||||||
|
#data {
|
||||||
textarea:focus {
|
border-right: 1px solid #ccc;
|
||||||
border-color: #333;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>API 调试界面</h1>
|
|
||||||
<form onsubmit="event.preventDefault(); sendRequest();">
|
<form onsubmit="event.preventDefault(); sendRequest();">
|
||||||
<div class="form-row">
|
<label for="uri">uri</label>
|
||||||
<label for="uri">URI</label>
|
<input type="text" id="uri" name="uri" value="/api/song/lyric/v1">
|
||||||
<input type="text" id="uri" name="uri" value="/api/song/lyric/v1">
|
<label for="crypto">crypto</label>
|
||||||
</div>
|
<select id="crypto" name="crypto">
|
||||||
<div class="form-row">
|
<option value="weapi">weapi</option>
|
||||||
<label for="crypto">加密方式</label>
|
<option value="eapi">eapi</option>
|
||||||
<select id="crypto" name="crypto">
|
<option value="api">api</option>
|
||||||
<option value="weapi">weapi</option>
|
<option value="linuxapi">linuxapi</option>
|
||||||
<option value="eapi">eapi</option>
|
<option value="" selected>(默认)</option>
|
||||||
<option value="api">api</option>
|
</select>
|
||||||
<option value="linuxapi">linuxapi</option>
|
<button type="submit">发送</button>
|
||||||
<option value="" selected>(默认)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<label></label>
|
|
||||||
<button type="submit">发送请求</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
<div class="data-result">
|
<div class="data-result">
|
||||||
<div>
|
<div>
|
||||||
<label for="result">响应结果</label>
|
<label for="result">result</label>
|
||||||
<textarea id="result" name="result" readonly></textarea>
|
<textarea id="result" name="result"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="data">请求数据</label>
|
<label for="data">data</label>
|
||||||
<textarea id="data" name="data">
|
<textarea id="data" name="data">
|
||||||
{
|
{
|
||||||
"cp": false,
|
"cp": false,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
const WASM_BINARY_PLACEHOLDER = 'WASM_BINARY_PLACEHOLDER';
|
const WASM_BINARY_PLACEHOLDER = 'WASM_BINARY_PLACEHOLDER';
|
||||||
|
const logger = require('../../util/logger.js')
|
||||||
// See https://github.com/Distributive-Network/PythonMonkey/issues/266
|
// See https://github.com/Distributive-Network/PythonMonkey/issues/266
|
||||||
if (typeof globalThis.setInterval != 'function'){
|
if (typeof globalThis.setInterval != 'function'){
|
||||||
globalThis.setInterval = function pm$$setInterval(fn, timeout) {
|
globalThis.setInterval = function pm$$setInterval(fn, timeout) {
|
||||||
@ -1611,9 +1612,9 @@ function instantiateRuntime(){
|
|||||||
|
|
||||||
function GenerateFP(floatArray) {
|
function GenerateFP(floatArray) {
|
||||||
let PCMBuffer = Float32Array.from(floatArray)
|
let PCMBuffer = Float32Array.from(floatArray)
|
||||||
console.info('[afp] input samples n=', PCMBuffer.length)
|
logger.info('[afp] input samples n=', PCMBuffer.length)
|
||||||
return instantiateRuntime().then((fpRuntime) => {
|
return instantiateRuntime().then((fpRuntime) => {
|
||||||
console.info('[afp] begin fingerprinting')
|
logger.info('[afp] begin fingerprinting')
|
||||||
let fp_vector = fpRuntime.ExtractQueryFP(PCMBuffer.buffer)
|
let fp_vector = fpRuntime.ExtractQueryFP(PCMBuffer.buffer)
|
||||||
let result_buf = new Uint8Array(fp_vector.size());
|
let result_buf = new Uint8Array(fp_vector.size());
|
||||||
for (let t = 0; t < fp_vector.size(); t++)
|
for (let t = 0; t < fp_vector.size(); t++)
|
||||||
|
|||||||
@ -1,142 +1,27 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>听歌识曲 Demo</title>
|
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
font-family: sans-serif;
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
pre {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
font-family: monospace;
|
||||||
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 {
|
a {
|
||||||
color: #333;
|
font-family: sans-serif;
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section h3 {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-group {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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 {
|
audio {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 0;
|
height: 0;
|
||||||
transition: all linear 0.1s;
|
transition: all linear 0.1s;
|
||||||
background: #f9f9f9;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.canvas-active {
|
.canvas-active {
|
||||||
@ -144,80 +29,39 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
font-family: 'Courier New', monospace;
|
overflow: scroll;
|
||||||
font-size: 13px;
|
|
||||||
color: #666;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
max-height: 400px;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 16px;
|
|
||||||
background: #f9f9f9;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning {
|
|
||||||
padding: 12px 16px;
|
|
||||||
background: #fef3c7;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #92400e;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<h1>听歌识曲 Demo (Credit: <a href="https://github.com/mos9527/ncm-afp" target="_blank">https://github.com/mos9527/ncm-afp</a>)</h1>
|
||||||
<h1>听歌识曲 Demo</h1>
|
<hr>
|
||||||
<p class="subtitle">Credit: <a href="https://github.com/mos9527/ncm-afp" target="_blank">https://github.com/mos9527/ncm-afp</a></p>
|
<p><b>DISCLAIMER: </b></p>
|
||||||
|
<p>This site uses the offical NetEase audio matcher APIs (reverse engineered from <a
|
||||||
|
href="https://fn.music.163.com/g/chrome-extension-home-page-beta/">https://fn.music.163.com/g/chrome-extension-home-page-beta/</a>)
|
||||||
|
</p>
|
||||||
|
<p>And DOES NOT condone copyright infringment nor intellectual property theft.</p>
|
||||||
|
<hr>
|
||||||
|
<p><b>NOTE:</b></p>
|
||||||
|
<p>Before start using the site, you may want to access this link first:</p>
|
||||||
|
<a href="https://cors-anywhere.herokuapp.com/corsdemo">https://cors-anywhere.herokuapp.com/corsdemo</a>
|
||||||
|
<p>Since Netease APIs do not have CORS headers, this is required to alleviate this restriction.</p>
|
||||||
|
<hr>
|
||||||
|
<p>Usage:</p>
|
||||||
|
<li>Select your audio file through "Choose File" picker</li>
|
||||||
|
<li>Hit the "Clip" button and wait for the results!</li>
|
||||||
|
|
||||||
<hr>
|
<audio id="audio" controls autoplay></audio>
|
||||||
|
<canvas id="canvas"></canvas>
|
||||||
<div class="warning">
|
<button id="invoke">Clip</button>
|
||||||
<strong>免责声明:</strong>本站点使用网易云音乐官方音频识别API(逆向自 <a href="https://fn.music.163.com/g/chrome-extension-home-page-beta/" target="_blank">Chrome 扩展页面</a>),不鼓励版权侵犯或知识产权盗窃。
|
<input type="file" name="picker" accept="*" id="file">
|
||||||
</div>
|
<hr>
|
||||||
|
<label for="use-mic">Mix in Microphone input</label>
|
||||||
<div class="section">
|
<input type="checkbox" name="use-mic" id="usemic">
|
||||||
<h3>使用说明</h3>
|
<hr>
|
||||||
<p>在使用本站点之前,您可能需要先访问以下链接:</p>
|
<pre id="logs"></pre>
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<h3 style="font-size: 16px; font-weight: 600; color: #333; margin-bottom: 12px;">日志</h3>
|
|
||||||
<pre id="logs"></pre>
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
<script src="./afp.wasm.js"></script>
|
<script src="./afp.wasm.js"></script>
|
||||||
<script src="./afp.js"></script>
|
<script src="./afp.js"></script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
@ -232,17 +76,13 @@
|
|||||||
let canvas = document.getElementById('canvas')
|
let canvas = document.getElementById('canvas')
|
||||||
let canvasCtx = canvas.getContext('2d')
|
let canvasCtx = canvas.getContext('2d')
|
||||||
let logs = document.getElementById('logs')
|
let logs = document.getElementById('logs')
|
||||||
logs.write = line => {
|
logs.write = line => logs.innerHTML += line + '\n'
|
||||||
// Append log lines as text to avoid interpreting content as HTML
|
|
||||||
logs.appendChild(document.createTextNode(line));
|
|
||||||
logs.appendChild(document.createElement('br'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function RecorderCallback(channelL) {
|
function RecorderCallback(channelL) {
|
||||||
let sampleBuffer = new Float32Array(channelL.subarray(0, duration * 8000))
|
let sampleBuffer = new Float32Array(channelL.subarray(0, duration * 8000))
|
||||||
GenerateFP(sampleBuffer).then(FP => {
|
GenerateFP(sampleBuffer).then(FP => {
|
||||||
logs.write(`[index] 生成指纹 ${FP}`)
|
logs.write(`[index] Generated FP ${FP}`)
|
||||||
logs.write('[index] 正在查询,请稍候...')
|
logs.write('[index] Now querying, please wait...')
|
||||||
fetch(
|
fetch(
|
||||||
'/audio/match?' +
|
'/audio/match?' +
|
||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
@ -251,9 +91,9 @@
|
|||||||
method: 'POST'
|
method: 'POST'
|
||||||
}).then(resp => resp.json()).then(resp => {
|
}).then(resp => resp.json()).then(resp => {
|
||||||
if (!resp.data.result) {
|
if (!resp.data.result) {
|
||||||
return logs.write('[index] 查询失败,无结果')
|
return logs.write('[index] Query failed with no results.')
|
||||||
}
|
}
|
||||||
logs.write(`[index] 查询完成。结果数量=${resp.data.result.length}`)
|
logs.write(`[index] Query complete. Results=${resp.data.result.length}`)
|
||||||
for (var song of resp.data.result) {
|
for (var song of resp.data.result) {
|
||||||
logs.write(
|
logs.write(
|
||||||
`[result] <a target="_blank" href="https://music.163.com/song?id=${song.song.id}">${song.song.name} - ${song.song.album.name} (${song.startTime / 1000}s)</a>`
|
`[result] <a target="_blank" href="https://music.163.com/song?id=${song.song.id}">${song.song.name} - ${song.song.album.name} (${song.startTime / 1000}s)</a>`
|
||||||
@ -264,19 +104,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function InitAudioCtx() {
|
function InitAudioCtx() {
|
||||||
|
// AFP.wasm can't do it with anything other than 8KHz
|
||||||
audioCtx = new AudioContext({ 'sampleRate': 8000 })
|
audioCtx = new AudioContext({ 'sampleRate': 8000 })
|
||||||
if (audioCtx.state == 'suspended')
|
if (audioCtx.state == 'suspended')
|
||||||
return false
|
return false
|
||||||
let audioNode = audioCtx.createMediaElementSource(audio)
|
let audioNode = audioCtx.createMediaElementSource(audio)
|
||||||
audioCtx.audioWorklet.addModule('rec.js').then(() => {
|
audioCtx.audioWorklet.addModule('rec.js').then(() => {
|
||||||
recorderNode = new AudioWorkletNode(audioCtx, 'timed-recorder')
|
recorderNode = new AudioWorkletNode(audioCtx, 'timed-recorder')
|
||||||
audioNode.connect(recorderNode)
|
audioNode.connect(recorderNode) // recorderNode doesn't output anything
|
||||||
audioNode.connect(audioCtx.destination)
|
audioNode.connect(audioCtx.destination)
|
||||||
recorderNode.port.onmessage = event => {
|
recorderNode.port.onmessage = event => {
|
||||||
switch (event.data.message) {
|
switch (event.data.message) {
|
||||||
case 'finished':
|
case 'finished':
|
||||||
RecorderCallback(event.data.recording)
|
RecorderCallback(event.data.recording)
|
||||||
clip.innerHTML = '识别'
|
clip.innerHTML = 'Clip'
|
||||||
clip.disabled = false
|
clip.disabled = false
|
||||||
canvas.classList.remove('canvas-active')
|
canvas.classList.remove('canvas-active')
|
||||||
break
|
break
|
||||||
@ -289,6 +130,7 @@
|
|||||||
logs.write(event.data.message)
|
logs.write(event.data.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Attempt to get user's microphone and connect it to the AudioContext.
|
||||||
navigator.mediaDevices.getUserMedia({
|
navigator.mediaDevices.getUserMedia({
|
||||||
audio: {
|
audio: {
|
||||||
echoCancellation: false,
|
echoCancellation: false,
|
||||||
@ -300,7 +142,7 @@
|
|||||||
micSourceNode = audioCtx.createMediaStreamSource(micStream);
|
micSourceNode = audioCtx.createMediaStreamSource(micStream);
|
||||||
micSourceNode.connect(recorderNode)
|
micSourceNode.connect(recorderNode)
|
||||||
usemic.checked = true
|
usemic.checked = true
|
||||||
logs.write('[rec.js] 麦克风已连接')
|
logs.write('[rec.js] Microphone attached.')
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return true
|
return true
|
||||||
@ -319,20 +161,10 @@
|
|||||||
else
|
else
|
||||||
micSourceNode.connect(recorderNode)
|
micSourceNode.connect(recorderNode)
|
||||||
})
|
})
|
||||||
function escapeHtml(str) {
|
|
||||||
return String(str)
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''')
|
|
||||||
.replace(/\//g, '/');
|
|
||||||
}
|
|
||||||
file.addEventListener('change', event => {
|
file.addEventListener('change', event => {
|
||||||
file.files[0].arrayBuffer().then(
|
file.files[0].arrayBuffer().then(
|
||||||
async buffer => {
|
async buffer => {
|
||||||
const safeName = escapeHtml(file.files[0].name)
|
logs.write(`[index] File ${file.files[0].name} loaded.`)
|
||||||
logs.write(`[index] 文件 ${safeName} 已加载`)
|
|
||||||
audio.src = window.URL.createObjectURL(new Blob([buffer]))
|
audio.src = window.URL.createObjectURL(new Blob([buffer]))
|
||||||
clip.disabled = false
|
clip.disabled = false
|
||||||
})
|
})
|
||||||
@ -356,13 +188,12 @@
|
|||||||
UpdateCanvas()
|
UpdateCanvas()
|
||||||
let requestCtx = setInterval(() => {
|
let requestCtx = setInterval(() => {
|
||||||
try {
|
try {
|
||||||
if (InitAudioCtx()) {
|
if (InitAudioCtx()) { // Put this here so we don't have to deal with the 'user did not interact' thing
|
||||||
clearInterval(requestCtx)
|
clearInterval(requestCtx)
|
||||||
logs.write('[rec.js] 音频上下文已启动')
|
logs.write('[rec.js] Audio Context started.')
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Fail silently
|
// Fail silently
|
||||||
}
|
}
|
||||||
}, 100)
|
}, 100)
|
||||||
</script>
|
</script>
|
||||||
</html>
|
|
||||||
@ -5,320 +5,67 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>更新头像</title>
|
<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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-wrapper {
|
|
||||||
position: relative;
|
|
||||||
width: 160px;
|
|
||||||
height: 160px;
|
|
||||||
margin: 0 auto 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
border: 3px solid #fff;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.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);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-wrapper.loading .loading-overlay {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border: 3px solid #e0e0e0;
|
|
||||||
border-top-color: #333;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 0.8s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-btn {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
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"] {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div>
|
||||||
<h1>更新头像</h1>
|
<a href="/qrlogin-nocookie.html">
|
||||||
<p class="subtitle">选择一张图片作为您的新头像</p>
|
如果没登录,请先登录
|
||||||
|
|
||||||
<div class="avatar-wrapper" id="avatarWrapper">
|
|
||||||
<img id="avatar" class="avatar" src="" alt="头像" />
|
|
||||||
<div class="loading-overlay">
|
|
||||||
<div class="spinner"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label class="upload-btn">
|
|
||||||
选择图片
|
|
||||||
<input id="file" type="file" accept="image/*" />
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<a href="/qrlogin-nocookie.html" class="login-link">
|
|
||||||
还没有登录?点击登录
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<p class="hint">支持 JPG、PNG 格式,建议尺寸 200x200</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<input id="file" type="file" />
|
||||||
<div id="toast" class="toast"></div>
|
<img id="avatar" style="height: 200px; width: 200px; border-radius: 50%" />
|
||||||
|
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js
|
||||||
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
|
"></script>
|
||||||
<script>
|
<script>
|
||||||
main()
|
main()
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const fileInput = document.querySelector('input[type="file"]');
|
document.querySelector('input[type="file"]').addEventListener(
|
||||||
const avatarWrapper = document.getElementById('avatarWrapper');
|
'change',
|
||||||
|
function (e) {
|
||||||
fileInput.addEventListener('change', function(e) {
|
var file = this.files[0]
|
||||||
const file = this.files[0];
|
upload(file)
|
||||||
if (file) {
|
},
|
||||||
upload(file);
|
false,
|
||||||
}
|
)
|
||||||
}, false);
|
const res = await axios({
|
||||||
|
url: `/user/detail?uid=32953014×tamp=${Date.now()}`,
|
||||||
try {
|
withCredentials: true, //跨域的话必须设置
|
||||||
showToast('正在加载头像...', 'info');
|
})
|
||||||
avatarWrapper.classList.add('loading');
|
document.querySelector('#avatar').src = res.data.profile.avatarUrl
|
||||||
const res = await axios({
|
|
||||||
url: `/user/detail?uid=32953014×tamp=${Date.now()}`,
|
|
||||||
withCredentials: true
|
|
||||||
});
|
|
||||||
document.querySelector('#avatar').src = res.data.profile.avatarUrl;
|
|
||||||
hideToast();
|
|
||||||
} catch (error) {
|
|
||||||
hideToast();
|
|
||||||
showToast('加载头像失败,请刷新页面重试', 'error');
|
|
||||||
console.error('加载头像失败:', error);
|
|
||||||
} finally {
|
|
||||||
avatarWrapper.classList.remove('loading');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function upload(file) {
|
async function upload(file) {
|
||||||
const avatarWrapper = document.getElementById('avatarWrapper');
|
var formData = new FormData()
|
||||||
|
formData.append('imgFile', file)
|
||||||
if (!file.type.startsWith('image/')) {
|
const imgSize = await getImgSize(file)
|
||||||
showToast('请选择图片文件', 'error');
|
const res = await axios({
|
||||||
return;
|
method: 'post',
|
||||||
}
|
url: `/avatar/upload?cookie=${localStorage.getItem('cookie')}&imgSize=${imgSize.width
|
||||||
|
}&imgX=0&imgY=0×tamp=${Date.now()}`,
|
||||||
try {
|
headers: {
|
||||||
showToast('正在上传头像...', 'info');
|
'Content-Type': 'multipart/form-data',
|
||||||
avatarWrapper.classList.add('loading');
|
},
|
||||||
|
data: formData,
|
||||||
var formData = new FormData();
|
})
|
||||||
formData.append('imgFile', file);
|
document.querySelector('#avatar').src = res.data.data.url
|
||||||
const imgSize = await getImgSize(file);
|
|
||||||
|
|
||||||
const res = await axios({
|
|
||||||
method: 'post',
|
|
||||||
url: `/avatar/upload?cookie=${localStorage.getItem('cookie')}&imgSize=${imgSize.width}&imgX=0&imgY=0×tamp=${Date.now()}`,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'multipart/form-data',
|
|
||||||
},
|
|
||||||
data: formData,
|
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelector('#avatar').src = res.data.data.url;
|
|
||||||
showToast('头像更新成功!', 'success');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('上传失败:', error);
|
|
||||||
const errorMsg = error.response?.data?.message || error.message || '上传失败,请重试';
|
|
||||||
showToast(errorMsg, 'error');
|
|
||||||
} finally {
|
|
||||||
avatarWrapper.classList.remove('loading');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getImgSize(file) {
|
function getImgSize(file) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let reader = new FileReader();
|
let reader = new FileReader()
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file)
|
||||||
reader.onload = function(theFile) {
|
reader.onload = function (theFile) {
|
||||||
let image = new Image();
|
let image = new Image()
|
||||||
image.src = theFile.target.result;
|
image.src = theFile.target.result
|
||||||
image.onload = function() {
|
image.onload = function () {
|
||||||
resolve({
|
resolve({
|
||||||
width: this.width,
|
width: this.width,
|
||||||
height: this.height,
|
height: this.height,
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
image.onerror = function() {
|
}
|
||||||
reject(new Error('图片加载失败'));
|
})
|
||||||
};
|
|
||||||
};
|
|
||||||
reader.onerror = function() {
|
|
||||||
reject(new Error('文件读取失败'));
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function showToast(message, type = 'info') {
|
|
||||||
const toast = document.getElementById('toast');
|
|
||||||
toast.textContent = message;
|
|
||||||
toast.className = `toast ${type}`;
|
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
toast.classList.add('show');
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
toast.classList.remove('show');
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideToast() {
|
|
||||||
const toast = document.getElementById('toast');
|
|
||||||
toast.classList.remove('show');
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -4,135 +4,26 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>云盘上传</title>
|
<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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-section {
|
|
||||||
margin-bottom: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.songs-list {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.song-item {
|
|
||||||
padding: 12px 16px;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.song-item:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
text-align: center;
|
|
||||||
padding: 40px 20px;
|
|
||||||
color: #999;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading {
|
|
||||||
text-align: center;
|
|
||||||
padding: 20px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div>
|
||||||
<h1>云盘上传</h1>
|
<a href="/qrlogin-nocookie.html"> 如果没登录,请先登录 </a>
|
||||||
<a href="/qrlogin-nocookie.html" class="login-link">还没登录?点击登录</a>
|
</div>
|
||||||
|
<input id="file" type="file" multiple />
|
||||||
<div class="upload-section">
|
<div id="app">
|
||||||
<label class="upload-btn">
|
<ul>
|
||||||
选择文件(支持多选)
|
<li v-for="(item,index) in songs" :key="index">{{item.songName}}</li>
|
||||||
<input id="file" type="file" multiple accept="audio/*" />
|
</ul>
|
||||||
</label>
|
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
|
<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>
|
<script src="https://fastly.jsdelivr.net/npm/vue"></script>
|
||||||
<script>
|
<script>
|
||||||
const app = Vue.createApp({
|
const app = Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
songs: [],
|
songs: [],
|
||||||
loading: false,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@ -140,23 +31,19 @@
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getData() {
|
getData() {
|
||||||
this.loading = true
|
console.info('getdata')
|
||||||
|
const _this = this
|
||||||
axios({
|
axios({
|
||||||
url: `/user/cloud?time=${Date.now()}&cookie=${localStorage.getItem('cookie')}`,
|
url: `/user/cloud?time=${Date.now()}&cookie=${localStorage.getItem(
|
||||||
|
'cookie',
|
||||||
|
)}`,
|
||||||
|
}).then((res) => {
|
||||||
|
console.info(res.data)
|
||||||
|
_this.songs = res.data.data
|
||||||
})
|
})
|
||||||
.then((res) => {
|
|
||||||
this.songs = res.data.data || []
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error('获取云盘数据失败:', err)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
this.loading = false
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}).mount('#app')
|
}).mount('#app')
|
||||||
|
|
||||||
const fileUpdateTime = {}
|
const fileUpdateTime = {}
|
||||||
let fileLength = 0
|
let fileLength = 0
|
||||||
|
|
||||||
@ -164,46 +51,51 @@
|
|||||||
document
|
document
|
||||||
.querySelector('input[type="file"]')
|
.querySelector('input[type="file"]')
|
||||||
.addEventListener('change', function (e) {
|
.addEventListener('change', function (e) {
|
||||||
const files = this.files
|
console.info(this.files)
|
||||||
if (files.length === 0) return
|
let currentIndx = 0
|
||||||
|
fileLength = this.files.length
|
||||||
fileLength = files.length
|
for (const item of this.files) {
|
||||||
for (let i = 0; i < files.length; i++) {
|
currentIndx += 1
|
||||||
upload(files[i], i + 1)
|
upload(item, currentIndx)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
main()
|
main()
|
||||||
|
|
||||||
function upload(file, currentIndex) {
|
function upload(file, currentIndx) {
|
||||||
var formData = new FormData()
|
var formData = new FormData()
|
||||||
formData.append('songFile', file)
|
formData.append('songFile', file)
|
||||||
|
|
||||||
axios({
|
axios({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: `/cloud?time=${Date.now()}&cookie=${localStorage.getItem('cookie')}`,
|
url: `/cloud?time=${Date.now()}&cookie=${localStorage.getItem(
|
||||||
|
'cookie',
|
||||||
|
)}`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data',
|
'Content-Type': 'multipart/form-data',
|
||||||
},
|
},
|
||||||
data: formData,
|
data: formData,
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
console.log(`${file.name} 上传成功`)
|
console.info(`${file.name} 上传成功`)
|
||||||
if (currentIndex >= fileLength) {
|
if (currentIndx >= fileLength) {
|
||||||
console.log('所有文件上传完毕')
|
console.info('上传完毕')
|
||||||
}
|
}
|
||||||
app.getData()
|
app.getData()
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(async (err) => {
|
||||||
console.error(`${file.name} 上传失败:`, err)
|
console.info(err)
|
||||||
fileUpdateTime[file.name] = (fileUpdateTime[file.name] || 0) + 1
|
console.info(fileUpdateTime)
|
||||||
|
fileUpdateTime[file.name]
|
||||||
|
? (fileUpdateTime[file.name] += 1)
|
||||||
|
: (fileUpdateTime[file.name] = 1)
|
||||||
if (fileUpdateTime[file.name] >= 4) {
|
if (fileUpdateTime[file.name] >= 4) {
|
||||||
console.error(`文件 ${file.name} 上传失败次数过多,已停止重试`)
|
console.error(`丢,这首歌怎么都传不上:${file.name}`)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
console.error(`${file.name} 上传失败 ${fileUpdateTime[file.name]} 次,正在重试...`)
|
console.error(`${file.name} 失败 ${fileUpdateTime[file.name]} 次`)
|
||||||
upload(file, currentIndex)
|
|
||||||
}
|
}
|
||||||
|
// await login()
|
||||||
|
upload(file, currentIndx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -36,12 +36,12 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-BPRGR23JEG"></script>
|
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-139996012-1"></script>
|
||||||
<script>
|
<script>
|
||||||
window.dataLayer = window.dataLayer || [];
|
window.dataLayer = window.dataLayer || [];
|
||||||
function gtag() { dataLayer.push(arguments); }
|
function gtag(){dataLayer.push(arguments);}
|
||||||
gtag('js', new Date());
|
gtag('js', new Date());
|
||||||
|
|
||||||
gtag('config', 'G-BPRGR23JEG');
|
gtag('config', 'UA-139996012-1');
|
||||||
</script>
|
</script>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,181 +1,50 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>eapi 参数和返回内容解析</title>
|
<title>eapi 参数和返回内容解析</title>
|
||||||
<style>
|
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
|
||||||
* {
|
|
||||||
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: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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.decode-result {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-top: 10px;
|
||||||
|
height: 300px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app" class="container">
|
<div id="app" class="p-5 flex flex-col">
|
||||||
<h1>eapi 参数和返回内容解析</h1>
|
<h1 class="text-2xl font-bold mb-5">eapi 参数和返回内容解析</h1>
|
||||||
|
<textarea class="border border-gray-300 p-3 mb-5" v-model="hexString" rows="10"></textarea>
|
||||||
<div class="form-group">
|
<button @click="decrypt" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
|
||||||
<label for="hexString">十六进制字符串</label>
|
解密
|
||||||
<textarea id="hexString" v-model="hexString" rows="10"></textarea>
|
</button>
|
||||||
|
<div class="mt-3">
|
||||||
|
<input type="radio" id="format" name="format" v-model="isReq" value="true">
|
||||||
|
<label for="format" class="ml-2">请求数据request params(针对请求数据的 params)</label>
|
||||||
|
<input type="radio" id="noFormat" name="format" v-model="isReq" value="false" class="ml-5">
|
||||||
|
<label for="noFormat" class="ml-2">返回数据 response 二进制数据(针对返回内容解析)</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>解密结果:
|
||||||
|
<pre class="decode-result">{{ JSON.stringify(JSON.parse(result), null, 2) }}</pre>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="radio-group">
|
|
||||||
<div class="radio-item">
|
|
||||||
<input type="radio" id="req" name="format" v-model="isReq" value="true">
|
|
||||||
<label for="req">请求数据 request params</label>
|
|
||||||
</div>
|
|
||||||
<div class="radio-item">
|
|
||||||
<input type="radio" id="resp" name="format" v-model="isReq" value="false">
|
|
||||||
<label for="resp">返回数据 response 二进制数据</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button @click="decrypt">解密</button>
|
<div>
|
||||||
|
<p>使用例子:</p>
|
||||||
<div class="result-section">
|
<img src="/static/eapi_params.png" />
|
||||||
<label>解密结果:</label>
|
<img src="/static/eapi_response.png" />
|
||||||
<pre class="decode-result">{{ formatResult(result) }}</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="example-section">
|
|
||||||
<h2>使用示例</h2>
|
|
||||||
<img src="/static/eapi_params.png" alt="请求示例" />
|
|
||||||
<img src="/static/eapi_response.png" alt="响应示例" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -195,13 +64,6 @@
|
|||||||
this.decrypt()
|
this.decrypt()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
formatResult(result) {
|
|
||||||
try {
|
|
||||||
return JSON.stringify(JSON.parse(result), null, 2)
|
|
||||||
} catch (e) {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async decrypt() {
|
async decrypt() {
|
||||||
try {
|
try {
|
||||||
const res = await axios({
|
const res = await axios({
|
||||||
@ -215,7 +77,7 @@
|
|||||||
console.log(res.data);
|
console.log(res.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
alert(error?.response?.data?.message || '解密失败,数据格式错误')
|
alert(error?.response?.data?.message || '解密失败,数据格式错误')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,33 +7,33 @@
|
|||||||
<title>网易云音乐 API Enhanced</title>
|
<title>网易云音乐 API Enhanced</title>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--fg: #333;
|
--fg: #111827; /* gray-900 */
|
||||||
--muted: #666;
|
--muted: #6b7280; /* gray-500 */
|
||||||
--border: #ddd;
|
--border: #e5e7eb; /* gray-200 */
|
||||||
--bg: #f5f5f5;
|
--bg: #ffffff;
|
||||||
--panel: #ffffff;
|
--panel: #f9fafb; /* gray-50 */
|
||||||
--accent: #333;
|
--accent: #2563eb; /* blue-600 */
|
||||||
}
|
}
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
html, body { height: 100%; }
|
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; }
|
body { margin: 0; font-family: system-ui, -apple-system, Segoe UI, PingFang SC, Helvetica, Arial, sans-serif; color: var(--fg); background: var(--bg); line-height: 1.6; }
|
||||||
.container { max-width: 960px; margin: 40px auto; padding: 0 20px; }
|
.container { max-width: 960px; margin: 40px auto; padding: 0 20px; }
|
||||||
header.site-header { margin-bottom: 24px; }
|
header.site-header { margin-bottom: 24px; }
|
||||||
header.site-header h1 { font-size: 28px; font-weight: 600; margin: 0; }
|
header.site-header h1 { font-size: 28px; font-weight: 700; 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); }
|
.badge { display: inline-block; margin-left: 8px; padding: 2px 8px; border: 1px solid var(--border); border-radius: 14px; font-size: 12px; color: var(--muted); }
|
||||||
.sub { margin-top: 8px; color: var(--muted); font-size: 14px; }
|
.sub { margin-top: 6px; color: var(--muted); }
|
||||||
.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 { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 16px; margin-bottom: 16px; }
|
||||||
.block h2 { margin: 0 0 12px; font-size: 18px; font-weight: 600; }
|
.block h2 { margin: 0 0 10px; font-size: 18px; }
|
||||||
.kvs { display: grid; grid-template-columns: 140px 1fr; gap: 8px 16px; align-items: center; }
|
.kvs { display: grid; grid-template-columns: 140px 1fr; gap: 8px 16px; align-items: center; }
|
||||||
.kvs div:first-child { color: var(--muted); }
|
.kvs div:first-child { color: var(--muted); }
|
||||||
ul.links { list-style: none; padding: 0; margin: 0; }
|
ul.links { list-style: none; padding: 0; margin: 0; }
|
||||||
ul.links li { margin: 8px 0; }
|
ul.links li { margin: 6px 0; }
|
||||||
ul.links a { color: var(--fg); text-decoration: none; border-bottom: 1px dotted var(--border); transition: all 0.2s ease; }
|
ul.links a { color: var(--fg); text-decoration: none; border-bottom: 1px dotted var(--border); }
|
||||||
ul.links a:hover { color: var(--accent); border-bottom-color: var(--accent); }
|
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: auto; }
|
pre { margin: 0; background: #fff; border: 1px solid var(--border); border-radius: 6px; padding: 12px; overflow: auto; }
|
||||||
code { font-family: 'Courier New', monospace; font-size: 13px; }
|
code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; font-size: 13px; }
|
||||||
footer.site-footer { margin-top: 24px; padding-top: 12px; border-top: 1px solid var(--border); color: var(--muted); text-align: center; }
|
footer.site-footer { margin-top: 24px; padding-top: 12px; border-top: 1px solid var(--border); color: var(--muted); }
|
||||||
footer.site-footer a { color: var(--fg); text-decoration: none; transition: color 0.2s ease; }
|
footer.site-footer a { color: var(--fg); text-decoration: none; }
|
||||||
footer.site-footer a:hover { color: var(--accent); }
|
footer.site-footer a:hover { color: var(--accent); }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@ -1,321 +1,92 @@
|
|||||||
|
<!-- eslint-disable prettier/prettier -->
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh">
|
<html lang="zh">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>一起听 - 主机模式</title>
|
<title>一起听</title>
|
||||||
<script src="https://unpkg.com/petite-vue"></script>
|
<script src="https://unpkg.com/petite-vue"></script>
|
||||||
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
|
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
|
||||||
<style>
|
<link
|
||||||
* {
|
rel="stylesheet"
|
||||||
margin: 0;
|
href="https://unpkg.com/mdui@1.0.2/dist/css/mdui.min.css"
|
||||||
padding: 0;
|
/>
|
||||||
box-sizing: border-box;
|
<script src="https://unpkg.com/mdui@1.0.2/dist/js/mdui.min.js"></script>
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
padding: 12px 16px;
|
|
||||||
background: #f9f9f9;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
padding: 20px;
|
|
||||||
background: #f9f9f9;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section h3 {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="text"], input[type="number"] {
|
|
||||||
padding: 10px 14px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 14px;
|
|
||||||
outline: none;
|
|
||||||
width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus {
|
|
||||||
border-color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group label {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #555;
|
|
||||||
min-width: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.share-link {
|
|
||||||
padding: 12px;
|
|
||||||
background: #f0f0f0;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #666;
|
|
||||||
word-break: break-all;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-list {
|
|
||||||
list-style: none;
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 10px;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-item:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-avatar {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-name {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.track-list {
|
|
||||||
list-style: none;
|
|
||||||
max-height: 300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.track-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 10px;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.track-item:hover {
|
|
||||||
background: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.track-item:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.track-cover {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 4px;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.track-name {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
details {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
details summary {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #555;
|
|
||||||
padding: 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
details summary:hover {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
details[open] summary {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="container">
|
<body class="mdui-container">
|
||||||
<a href="/qrlogin.html" class="login-link">还没登录?点击登录</a>
|
<div>
|
||||||
|
<a href="/qrlogin.html"> 如果没登录,请先登录 </a>
|
||||||
|
</div>
|
||||||
<h1>一起听 - 主机模式</h1>
|
<h1>一起听 - 主机模式</h1>
|
||||||
|
<div>消息: {{message}}</div>
|
||||||
<div class="message">消息: {{message}}</div>
|
<audio id="player" autoplay controls></audio>
|
||||||
|
<br />
|
||||||
<audio id="player" class="audio-player" autoplay controls></audio>
|
<br />
|
||||||
|
<button v-if="!account.login" @click="login">获取登录状态</button>
|
||||||
<div v-if="!account.login">
|
<div>您的当前登录账号为: {{account.nickname}}</div>
|
||||||
<button class="btn" @click="login">获取登录状态</button>
|
<br />
|
||||||
</div>
|
<div v-if="account.login">
|
||||||
|
<button v-if="!roomInfo.roomId" @click="createRoom">创建房间</button>
|
||||||
<div class="section">
|
|
||||||
<h3>账号信息</h3>
|
|
||||||
<div>当前登录账号: {{account.nickname}}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="account.login" class="section">
|
|
||||||
<h3>房间管理</h3>
|
|
||||||
<div class="control-buttons">
|
|
||||||
<button v-if="!roomInfo.roomId" class="btn" @click="createRoom">创建房间</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>加入房间</summary>
|
<summary>加入房间</summary>
|
||||||
<div class="input-group">
|
<div><span>房间ID: </span><input v-model="roomInfo.roomId" /></div>
|
||||||
<label>房间ID:</label>
|
<div>
|
||||||
<input v-model="roomInfo.roomId" type="text" />
|
<span>邀请者 ID: </span><input v-model="roomInfo.inviterId" />
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group">
|
<button @click="joinRoom">点击加入</button>
|
||||||
<label>邀请者ID:</label>
|
|
||||||
<input v-model="roomInfo.inviterId" type="text" />
|
|
||||||
</div>
|
|
||||||
<button class="btn" @click="joinRoom">加入房间</button>
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<div v-if="roomInfo.roomId" style="margin-top: 16px;">
|
<div v-if="roomInfo.roomId">
|
||||||
<h4>分享链接</h4>
|
<div>
|
||||||
<div class="share-link">
|
分享链接为:
|
||||||
https://st.music.163.com/listen-together/share/?songId=1372188635&roomId={{roomInfo.roomId}}&inviterId={{roomInfo.inviterId}}
|
https://st.music.163.com/listen-together/share/?songId=1372188635&roomId={{roomInfo.roomId}}&inviterId={{roomInfo.inviterId}}
|
||||||
</div>
|
</div>
|
||||||
<button class="btn" @click="refreshRoom">刷新房间状态</button>
|
<br />
|
||||||
<button class="btn" @click="closeRoom">关闭房间</button>
|
<button @click="refreshRoom">刷新房间状态</button>
|
||||||
|
<div>在线用户:</div>
|
||||||
<h4 style="margin-top: 16px;">在线用户</h4>
|
<ul class="mdui-list">
|
||||||
<ul class="user-list">
|
<li
|
||||||
<li v-for="user in roomInfo.roomUsers" :key="user.userId" class="user-item">
|
v-for="user in roomInfo.roomUsers"
|
||||||
<img :src="user.avatarUrl" class="user-avatar" alt="avatar" />
|
class="mdui-list-item mdui-ripple"
|
||||||
<span class="user-name">{{user.nickname}}</span>
|
>
|
||||||
|
<div class="mdui-list-item-avatar">
|
||||||
|
<img :src="user.avatarUrl" />
|
||||||
|
</div>
|
||||||
|
<div class="mdui-list-item-content">{{user.nickname}}</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<button v-if="roomInfo.roomId" @click="closeRoom">关闭房间</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button @click="playTrack">播放</button>
|
||||||
<div class="section">
|
<button @click="pauseTrack">暂停</button>
|
||||||
<h3>播放控制</h3>
|
<button @click="seekTrack">同步进度</button>
|
||||||
<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>
|
<details>
|
||||||
<summary>播放列表</summary>
|
<summary>播放列表</summary>
|
||||||
<div class="section">
|
<br />
|
||||||
<div class="input-group">
|
<div>
|
||||||
<label>歌单ID:</label>
|
<span>歌单ID: </span><input v-model="playlistInfo.playlistId" />
|
||||||
<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>
|
</div>
|
||||||
|
<button @click="loadPlaylist">加载歌单到播放列表</button>
|
||||||
|
<span>{{playlistInfo.playlistName}}</span>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<div>歌单内容:</div>
|
||||||
|
<ul class="mdui-list">
|
||||||
|
<li
|
||||||
|
@click="gotoTrack(track.id)"
|
||||||
|
v-for="track in playlistInfo.playlistTracks"
|
||||||
|
class="mdui-list-item mdui-ripple"
|
||||||
|
>
|
||||||
|
<div class="mdui-list-item-avatar">
|
||||||
|
<img :src="track.al.picUrl" />
|
||||||
|
</div>
|
||||||
|
<div class="mdui-list-item-content">{{track.name}}</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
PetiteVue.createApp({
|
PetiteVue.createApp({
|
||||||
message: '请点击获取登录状态',
|
message: '请点击获取登录状态',
|
||||||
@ -355,7 +126,7 @@
|
|||||||
this.account.userId = res.data.data.profile.userId
|
this.account.userId = res.data.data.profile.userId
|
||||||
this.account.nickname = res.data.data.profile.nickname
|
this.account.nickname = res.data.data.profile.nickname
|
||||||
this.account.login = true
|
this.account.login = true
|
||||||
this.message = '成功登录,请创建房间'
|
this.message = '成功登录, 请创建房间'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
joinRoom: async function () {
|
joinRoom: async function () {
|
||||||
@ -441,7 +212,7 @@
|
|||||||
})
|
})
|
||||||
console.info(res)
|
console.info(res)
|
||||||
if (res.data.code != 200 || !res.data.data.inRoom) {
|
if (res.data.code != 200 || !res.data.data.inRoom) {
|
||||||
this.message = '房间状态获取失败,可能退出了房间'
|
this.message = '房间状态获取失败, 可能退出了房间'
|
||||||
} else {
|
} else {
|
||||||
this.roomInfo.roomUsers = res.data.data.roomInfo.roomUsers
|
this.roomInfo.roomUsers = res.data.data.roomInfo.roomUsers
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,143 +5,21 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>登录</title>
|
<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;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
|
||||||
<h1>登录</h1>
|
|
||||||
<p class="subtitle">使用手机号和密码登录网易云音乐</p>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="phone">手机号</label>
|
|
||||||
<input type="tel" id="phone" placeholder="请输入手机号" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="password">密码</label>
|
|
||||||
<input type="password" id="password" placeholder="请输入密码" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button id="loginBtn" class="btn" onclick="handleLogin()">登录</button>
|
|
||||||
|
|
||||||
<div id="result" class="result" style="display: none;"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
|
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
const phone = '' // 这里填手机号
|
||||||
|
const password = '' // 这里填密码
|
||||||
const fileUpdateTime = {}
|
const fileUpdateTime = {}
|
||||||
|
if (!phone || !password) {
|
||||||
|
const msg = '请设置你的手机号码和密码'
|
||||||
|
alert(msg)
|
||||||
|
throw new Error(msg)
|
||||||
|
}
|
||||||
|
|
||||||
async function login(phone, password) {
|
async function login() {
|
||||||
const res = await axios({
|
const res = await axios({
|
||||||
url: `/login/cellphone`,
|
url: `/login/cellphone`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
@ -152,67 +30,17 @@
|
|||||||
})
|
})
|
||||||
return res.data.cookie
|
return res.data.cookie
|
||||||
}
|
}
|
||||||
|
async function main() {
|
||||||
async function handleLogin() {
|
const cookieToken = await login()
|
||||||
const phoneInput = document.getElementById('phone')
|
const res = await axios({
|
||||||
const passwordInput = document.getElementById('password')
|
url: `/login/status`,
|
||||||
const loginBtn = document.getElementById('loginBtn')
|
method: 'post',
|
||||||
const resultDiv = document.getElementById('result')
|
data: {
|
||||||
|
cookie: cookieToken,
|
||||||
const phone = phoneInput.value.trim()
|
},
|
||||||
const password = passwordInput.value
|
})
|
||||||
|
|
||||||
if (!phone || !password) {
|
|
||||||
showResult('请输入手机号和密码', 'error')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loginBtn.disabled = true
|
|
||||||
loginBtn.textContent = '登录中...'
|
|
||||||
showResult('正在登录...', 'info')
|
|
||||||
|
|
||||||
try {
|
|
||||||
const cookieToken = await login(phone, password)
|
|
||||||
localStorage.setItem('cookie', cookieToken)
|
|
||||||
|
|
||||||
const res = await axios({
|
|
||||||
url: `/login/status`,
|
|
||||||
method: 'post',
|
|
||||||
data: {
|
|
||||||
cookie: cookieToken,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
showResult(`登录成功!\n${JSON.stringify(res.data, null, 2)}`, 'success')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('登录失败:', error)
|
|
||||||
const errorMsg = error.response?.data?.message || error.message || '登录失败,请重试'
|
|
||||||
showResult(`登录失败:${errorMsg}`, 'error')
|
|
||||||
} finally {
|
|
||||||
loginBtn.disabled = false
|
|
||||||
loginBtn.textContent = '登录'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
main()
|
||||||
function showResult(message, type = 'info') {
|
|
||||||
const resultDiv = document.getElementById('result')
|
|
||||||
resultDiv.style.display = 'block'
|
|
||||||
resultDiv.textContent = message
|
|
||||||
resultDiv.className = 'result ' + type
|
|
||||||
}
|
|
||||||
|
|
||||||
// 支持回车登录
|
|
||||||
document.getElementById('password').addEventListener('keypress', function(e) {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
handleLogin()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
document.getElementById('phone').addEventListener('keypress', function(e) {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
document.getElementById('password').focus()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
@ -5,297 +5,56 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>歌单封面上传</title>
|
<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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover-wrapper {
|
|
||||||
position: relative;
|
|
||||||
width: 180px;
|
|
||||||
height: 180px;
|
|
||||||
margin: 0 auto 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
border: 4px solid #fff;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.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);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading.active {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border: 4px solid #e0e0e0;
|
|
||||||
border-top-color: #333;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 0.8s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div>
|
||||||
<h1>歌单封面上传</h1>
|
<a href="/qrlogin-nocookie.html">
|
||||||
<p class="subtitle">上传自定义歌单封面图片</p>
|
如果没登录,请先登录
|
||||||
|
</a>
|
||||||
<a href="/qrlogin-nocookie.html" class="login-link">还没登录?点击登录</a>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="playlistId">歌单 ID</label>
|
|
||||||
<input type="text" id="playlistId" placeholder="请输入歌单ID" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="cover-wrapper">
|
|
||||||
<img id="playlist_cover" class="cover" src="" alt="歌单封面" />
|
|
||||||
<div class="loading" id="loading">
|
|
||||||
<div class="spinner"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label class="upload-btn">
|
|
||||||
选择封面图片
|
|
||||||
<input id="file" type="file" name="filename" accept="image/*" />
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div id="result" class="result" style="display: none;"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<input id="file" type="file" name="filename" />
|
||||||
|
<img id="playlist_cover" style="height: 200px; width: 200px; border-radius: 50%" />
|
||||||
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
|
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const loadingOverlay = document.getElementById('loading')
|
const playlist_id = ''
|
||||||
const playlistIdInput = document.getElementById('playlistId')
|
if (!playlist_id) {
|
||||||
const resultDiv = document.getElementById('result')
|
const msg = '请设置你的歌单id'
|
||||||
|
alert(msg)
|
||||||
function showLoading() {
|
throw new Error(msg)
|
||||||
loadingOverlay.classList.add('active')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideLoading() {
|
main()
|
||||||
loadingOverlay.classList.remove('active')
|
async function main() {
|
||||||
}
|
document.querySelector('input[type="file"]').addEventListener(
|
||||||
|
'change',
|
||||||
function showResult(message, type) {
|
function (e) {
|
||||||
resultDiv.textContent = message
|
var file = this.files[0]
|
||||||
resultDiv.className = 'result ' + type
|
upload(file)
|
||||||
resultDiv.style.display = 'block'
|
},
|
||||||
}
|
false,
|
||||||
|
)
|
||||||
function hideResult() {
|
const res = await axios({
|
||||||
resultDiv.style.display = 'none'
|
url: `/playlist/detail?id=${playlist_id}×tamp=${Date.now()}`,
|
||||||
}
|
|
||||||
|
|
||||||
async function loadPlaylistCover() {
|
|
||||||
const playlistId = playlistIdInput.value.trim()
|
|
||||||
if (!playlistId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
showLoading()
|
|
||||||
hideResult()
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await axios({
|
|
||||||
url: `/playlist/detail?id=${playlistId}×tamp=${Date.now()}`,
|
|
||||||
})
|
|
||||||
document.querySelector('#playlist_cover').src = res.data.playlist.coverImgUrl
|
|
||||||
hideResult()
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载封面失败:', error)
|
|
||||||
showResult('加载封面失败,请检查歌单ID', 'error')
|
|
||||||
} finally {
|
|
||||||
hideLoading()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听歌单ID输入变化
|
|
||||||
playlistIdInput.addEventListener('input', function() {
|
|
||||||
loadPlaylistCover()
|
|
||||||
})
|
|
||||||
|
|
||||||
// 监听文件选择
|
|
||||||
document
|
|
||||||
.querySelector('input[type="file"]')
|
|
||||||
.addEventListener('change', async function (e) {
|
|
||||||
const file = this.files[0]
|
|
||||||
const playlistId = playlistIdInput.value.trim()
|
|
||||||
|
|
||||||
if (!playlistId) {
|
|
||||||
showResult('请先输入歌单ID', 'error')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!file) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
showLoading()
|
|
||||||
hideResult()
|
|
||||||
|
|
||||||
try {
|
|
||||||
await upload(file, playlistId)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('上传失败:', error)
|
|
||||||
showResult('上传失败,请重试', 'error')
|
|
||||||
} finally {
|
|
||||||
hideLoading()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
document.querySelector('#playlist_cover').src = res.data.playlist.coverImgUrl
|
||||||
|
}
|
||||||
|
|
||||||
async function upload(file, playlistId) {
|
async function upload(file) {
|
||||||
var formData = new FormData()
|
var formData = new FormData()
|
||||||
formData.append('imgFile', file)
|
formData.append('imgFile', file)
|
||||||
const imgSize = await getImgSize(file)
|
const imgSize = await getImgSize(file)
|
||||||
const res = await axios({
|
const res = await axios({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: `/playlist/cover/update?id=${playlistId}&cookie=${localStorage.getItem('cookie')}&imgSize=${imgSize.width}&imgX=0&imgY=0×tamp=${Date.now()}`,
|
url: `/playlist/cover/update?id=${playlist_id}&cookie=${localStorage.getItem('cookie')}&imgSize=${imgSize.width
|
||||||
|
}&imgX=0&imgY=0×tamp=${Date.now()}`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data',
|
'Content-Type': 'multipart/form-data',
|
||||||
},
|
},
|
||||||
data: formData,
|
data: formData,
|
||||||
})
|
})
|
||||||
document.querySelector('#playlist_cover').src = res.data.data.url
|
document.querySelector('#playlist_cover').src = res.data.data.url
|
||||||
showResult('封面上传成功!', 'success')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getImgSize(file) {
|
function getImgSize(file) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let reader = new FileReader()
|
let reader = new FileReader()
|
||||||
@ -309,16 +68,10 @@
|
|||||||
height: this.height,
|
height: this.height,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
image.onerror = function() {
|
|
||||||
reject(new Error('图片加载失败'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reader.onerror = function() {
|
|
||||||
reject(new Error('文件读取失败'))
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -2,416 +2,261 @@
|
|||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>歌单导入工具</title>
|
<title>歌单导入工具</title>
|
||||||
<style>
|
<!-- 引入Bootstrap CSS -->
|
||||||
* {
|
<link href="https://fastly.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
margin: 0;
|
<!-- 引入Bootstrap JS -->
|
||||||
padding: 0;
|
<script src="https://fastly.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
box-sizing: border-box;
|
<!-- 引入axios用于发送异步请求 -->
|
||||||
}
|
<script src="https://fastly.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group input {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox-group {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox-group input[type="checkbox"] {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox-group label {
|
|
||||||
margin: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:disabled {
|
|
||||||
background: #f5f5f5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container mt-5">
|
||||||
<h1>歌单导入工具</h1>
|
<h1 class="mb-4">歌单导入工具</h1>
|
||||||
<p class="subtitle">请选择一种导入方式并填写相关信息</p>
|
<p>请选择一种导入方式并填写相关信息:</p>
|
||||||
|
|
||||||
<ul class="tabs" id="importTabs" role="tablist">
|
<!-- 表单开始 -->
|
||||||
<li role="presentation">
|
<form id="importForm" novalidate>
|
||||||
<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>
|
<ul class="nav nav-tabs mb-3" id="importTabs" role="tablist">
|
||||||
<li role="presentation">
|
<li class="nav-item" 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>
|
<button class="nav-link 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>
|
||||||
<li role="presentation">
|
<li class="nav-item" 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>
|
<button class="nav-link" id="text-tab" data-bs-toggle="tab" data-bs-target="#text" type="button" role="tab" aria-controls="text" aria-selected="false">文字导入</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" 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="tab-content active" id="importTabContent">
|
<!-- 选项卡面板 -->
|
||||||
<!-- 元数据导入 -->
|
<div class="tab-content" id="importTabContent">
|
||||||
<div class="tab-content active" id="metadata" role="tabpanel" aria-labelledby="metadata-tab">
|
<!-- 元数据导入 -->
|
||||||
<table>
|
<div class="tab-pane fade show active" id="metadata" role="tabpanel" aria-labelledby="metadata-tab">
|
||||||
<thead>
|
<table class="table table-bordered mb-3">
|
||||||
<tr>
|
<thead>
|
||||||
<th style="width: 33%">歌曲名称</th>
|
<tr>
|
||||||
<th style="width: 33%">艺术家</th>
|
<th scope="col">歌曲名称</th>
|
||||||
<th style="width: 33%">专辑</th>
|
<th scope="col">艺术家</th>
|
||||||
</tr>
|
<th scope="col">专辑</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody id="metadataTableBody">
|
</thead>
|
||||||
<tr>
|
<tbody id="metadataTableBody">
|
||||||
<td><input type="text" name="name[]" placeholder="歌曲名称"></td>
|
<!-- 默认添加一行 -->
|
||||||
<td><input type="text" name="artist[]" placeholder="艺术家"></td>
|
<tr>
|
||||||
<td><input type="text" name="album[]" placeholder="专辑"></td>
|
<td><input type="text" class="form-control" name="name[]" placeholder="歌曲名称"></td>
|
||||||
</tr>
|
<td><input type="text" class="form-control" name="artist[]" placeholder="艺术家"></td>
|
||||||
</tbody>
|
<td><input type="text" class="form-control" name="album[]" placeholder="专辑"></td>
|
||||||
</table>
|
</tr>
|
||||||
<button type="button" class="btn btn-secondary" id="addMetadataRow">增加歌曲</button>
|
</tbody>
|
||||||
</div>
|
</table>
|
||||||
<!-- 文字导入 -->
|
<button type="button" class="btn btn-secondary mb-3" id="addMetadataRow">增加歌曲</button>
|
||||||
<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>
|
||||||
<div class="form-group">
|
<!-- 文字导入 -->
|
||||||
<label for="playlistNameInput">歌单名称</label>
|
<div class="tab-pane fade" id="text" role="tabpanel" aria-labelledby="text-tab">
|
||||||
<input type="text" id="playlistNameInput" name="playlistName" placeholder="请输入歌单名">
|
<div class="mb-3">
|
||||||
|
<label for="textInput" class="form-label">文字:</label>
|
||||||
|
<textarea class="form-control" id="textInput" name="text" rows="5"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="playlistNameInput" class="form-label">歌单名:</label>
|
||||||
|
<input type="text" class="form-control" id="playlistNameInput" name="playlistName" placeholder="请输入歌单名">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<!-- 链接导入 -->
|
||||||
<!-- 链接导入 -->
|
<div class="tab-pane fade" id="link" role="tabpanel" aria-labelledby="link-tab">
|
||||||
<div class="tab-content" id="link" role="tabpanel" aria-labelledby="link-tab">
|
<div class="mb-3">
|
||||||
<div class="form-group">
|
<label for="linkInputs" class="form-label">链接:</label>
|
||||||
<label>链接列表</label>
|
<div id="linkInputsContainer">
|
||||||
<div id="linkInputsContainer">
|
<div class="input-group mb-3">
|
||||||
<div class="input-group">
|
<input type="text" class="form-control" id="linkInput0" name="linkInput0" placeholder="请输入链接">
|
||||||
<input type="text" id="linkInput0" name="linkInput0" placeholder="请输入链接">
|
<button type="button" class="btn btn-secondary removeLinkButton" data-index="0">×</button>
|
||||||
<button type="button" class="btn btn-secondary removeLinkButton" data-index="0">×</button>
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-secondary mb-3" id="addLinkButton">增加链接</button>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="playlistNameLinkInput" class="form-label">歌单名:</label>
|
||||||
|
<input type="text" class="form-control" id="playlistNameLinkInput" name="playlistName" placeholder="请输入歌单名">
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="checkbox-group">
|
<!-- 是否导入我喜欢的音乐 -->
|
||||||
<input type="checkbox" value="" id="importStarCheckbox">
|
<div class="form-check">
|
||||||
<label for="importStarCheckbox">
|
<input class="form-check-input" type="checkbox" value="" id="importStarCheckbox">
|
||||||
导入"我喜欢的音乐"
|
<label class="form-check-label" for="importStarCheckbox">
|
||||||
</label>
|
导入“我喜欢的音乐”
|
||||||
</div>
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn" id="submitBtn">导入歌曲</button>
|
<!-- 提交按钮 -->
|
||||||
</div>
|
<button type="submit" class="btn btn-primary mt-3">导入歌曲</button>
|
||||||
|
</form>
|
||||||
|
<!-- 表单结束 -->
|
||||||
|
|
||||||
<script src="https://fastly.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
<script>
|
||||||
<script>
|
// 动态增加链接输入框
|
||||||
// 选项卡切换
|
document.getElementById('addLinkButton').addEventListener('click', function() {
|
||||||
const tabBtns = document.querySelectorAll('.tab-btn');
|
var container = document.getElementById('linkInputsContainer');
|
||||||
const tabContents = document.querySelectorAll('.tab-content[id]');
|
var newIndex = container.childElementCount - 1; // 减去非输入框元素的数量
|
||||||
|
var newInput = document.createElement('input');
|
||||||
|
newInput.type = 'text';
|
||||||
|
newInput.className = 'form-control';
|
||||||
|
newInput.id = `linkInput${newIndex}`;
|
||||||
|
newInput.name = `linkInput${newIndex}`;
|
||||||
|
newInput.placeholder = '请输入链接';
|
||||||
|
|
||||||
tabBtns.forEach(btn => {
|
var removeButton = document.createElement('button');
|
||||||
btn.addEventListener('click', () => {
|
removeButton.type = 'button';
|
||||||
tabBtns.forEach(b => b.classList.remove('active'));
|
removeButton.className = 'btn btn-secondary removeLinkButton';
|
||||||
tabContents.forEach(c => c.classList.remove('active'));
|
removeButton.textContent = '×';
|
||||||
|
removeButton.dataset.index = newIndex.toString();
|
||||||
btn.classList.add('active');
|
removeButton.addEventListener('click', function() {
|
||||||
const targetId = btn.getAttribute('data-bs-target');
|
var group = this.closest('.input-group');
|
||||||
document.getElementById(targetId).classList.add('active');
|
container.removeChild(group);
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 动态增加链接输入框
|
|
||||||
document.getElementById('addLinkButton').addEventListener('click', function() {
|
|
||||||
var container = document.getElementById('linkInputsContainer');
|
|
||||||
var newIndex = container.children.length;
|
|
||||||
var newInput = document.createElement('input');
|
|
||||||
newInput.type = 'text';
|
|
||||||
newInput.className = '';
|
|
||||||
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;';
|
|
||||||
|
|
||||||
var removeButton = document.createElement('button');
|
|
||||||
removeButton.type = 'button';
|
|
||||||
removeButton.className = 'btn btn-secondary';
|
|
||||||
removeButton.textContent = '×';
|
|
||||||
removeButton.dataset.index = newIndex.toString();
|
|
||||||
removeButton.addEventListener('click', function() {
|
|
||||||
var group = this.closest('.input-group');
|
|
||||||
container.removeChild(group);
|
|
||||||
});
|
|
||||||
|
|
||||||
var inputGroup = document.createElement('div');
|
|
||||||
inputGroup.className = 'input-group';
|
|
||||||
inputGroup.appendChild(newInput);
|
|
||||||
inputGroup.appendChild(removeButton);
|
|
||||||
|
|
||||||
container.appendChild(inputGroup);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 动态增加元数据行
|
|
||||||
document.getElementById('addMetadataRow').addEventListener('click', function() {
|
|
||||||
var container = document.getElementById('metadataTableBody');
|
|
||||||
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>
|
|
||||||
`;
|
|
||||||
|
|
||||||
container.appendChild(newRow);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('submitBtn').addEventListener('click', async function() {
|
|
||||||
// 获取表单值
|
|
||||||
let text = document.getElementById('textInput').value;
|
|
||||||
let links = [];
|
|
||||||
let local = [];
|
|
||||||
let playlistName = '';
|
|
||||||
|
|
||||||
// 获取所有链接输入框的值
|
|
||||||
let linkInputs = document.querySelectorAll('#linkInputsContainer .input-group input[type="text"]');
|
|
||||||
linkInputs.forEach(function(input) {
|
|
||||||
if (input.value.trim() !== '') {
|
|
||||||
links.push(input.value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取元数据
|
|
||||||
let metadataRows = document.querySelectorAll('#metadataTableBody tr');
|
|
||||||
metadataRows.forEach(function(row) {
|
|
||||||
let name = row.querySelector('input[name="name[]"]').value;
|
|
||||||
let artist = row.querySelector('input[name="artist[]"]').value;
|
|
||||||
let album = row.querySelector('input[name="album[]"]').value;
|
|
||||||
if (name && artist && album) {
|
|
||||||
local.push({ name, artist, album });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 检查是否有且只有一个输入字段被填写
|
|
||||||
let filledCount = (text ? 1 : 0) + (links.length > 0 ? 1 : 0) + (local.length > 0 ? 1 : 0);
|
|
||||||
if (filledCount !== 1) {
|
|
||||||
alert("请确保仅填写了一个输入字段!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取歌单名
|
|
||||||
if (document.getElementById('importStarCheckbox').checked) {
|
|
||||||
playlistName = '我喜欢的音乐';
|
|
||||||
} else {
|
|
||||||
playlistName = document.getElementById('playlistNameInput').value ||
|
|
||||||
document.getElementById('playlistNameLinkInput').value ||
|
|
||||||
'导入音乐 ' + new Date().toLocaleString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建请求参数
|
|
||||||
let data = {};
|
|
||||||
if (text) {
|
|
||||||
data.text = text;
|
|
||||||
data.playlistName = playlistName;
|
|
||||||
} else if (links.length > 0) {
|
|
||||||
data.link = JSON.stringify(links);
|
|
||||||
data.playlistName = playlistName;
|
|
||||||
} else if (local.length > 0) {
|
|
||||||
data.local = JSON.stringify(local);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加额外参数
|
|
||||||
if (document.getElementById('importStarCheckbox').checked) {
|
|
||||||
data.importStarPlaylist = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await axios({
|
|
||||||
url: `/playlist/import/name/task/create?timestamp=${Date.now()}`,
|
|
||||||
method: 'post',
|
|
||||||
data: data,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let taskId = res.data?.data?.taskId
|
var inputGroup = document.createElement('div');
|
||||||
if (taskId) {
|
inputGroup.className = 'input-group mb-3';
|
||||||
alert(`任务创建成功!正在导入,请稍等;任务id:${taskId}`)
|
inputGroup.appendChild(newInput);
|
||||||
}
|
inputGroup.appendChild(removeButton);
|
||||||
} catch (error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
alert('导入失败,请检查您的输入或稍后再试。');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听复选框状态变化
|
container.appendChild(inputGroup);
|
||||||
document.getElementById('importStarCheckbox').addEventListener('change', function() {
|
|
||||||
let isChecked = this.checked;
|
|
||||||
let playlistNameInputs = document.querySelectorAll('[name="playlistName"]');
|
|
||||||
playlistNameInputs.forEach(function(input) {
|
|
||||||
input.disabled = isChecked;
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// 初始化时设置歌单名输入框的状态
|
// 动态增加元数据行
|
||||||
document.getElementById('importStarCheckbox').dispatchEvent(new Event('change'));
|
document.getElementById('addMetadataRow').addEventListener('click', function() {
|
||||||
</script>
|
var container = document.getElementById('metadataTableBody');
|
||||||
|
var newRow = document.createElement('tr');
|
||||||
|
|
||||||
|
var nameInput = document.createElement('input');
|
||||||
|
nameInput.type = 'text';
|
||||||
|
nameInput.className = 'form-control';
|
||||||
|
nameInput.name = 'name[]';
|
||||||
|
nameInput.placeholder = '歌曲名称';
|
||||||
|
|
||||||
|
var artistInput = document.createElement('input');
|
||||||
|
artistInput.type = 'text';
|
||||||
|
artistInput.className = 'form-control';
|
||||||
|
artistInput.name = 'artist[]';
|
||||||
|
artistInput.placeholder = '艺术家';
|
||||||
|
|
||||||
|
var albumInput = document.createElement('input');
|
||||||
|
albumInput.type = 'text';
|
||||||
|
albumInput.className = 'form-control';
|
||||||
|
albumInput.name = 'album[]';
|
||||||
|
albumInput.placeholder = '专辑';
|
||||||
|
|
||||||
|
newRow.innerHTML = `
|
||||||
|
<td>${nameInput.outerHTML}</td>
|
||||||
|
<td>${artistInput.outerHTML}</td>
|
||||||
|
<td>${albumInput.outerHTML}</td>
|
||||||
|
`;
|
||||||
|
|
||||||
|
container.appendChild(newRow);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('importForm').addEventListener('submit', async function(event) {
|
||||||
|
// 阻止默认行为
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// 获取表单值
|
||||||
|
let text = document.getElementById('textInput').value;
|
||||||
|
let links = [];
|
||||||
|
let local = [];
|
||||||
|
let playlistName = '';
|
||||||
|
|
||||||
|
// 获取所有链接输入框的值
|
||||||
|
let linkInputs = document.querySelectorAll('#linkInputsContainer .input-group .form-control');
|
||||||
|
linkInputs.forEach(function(input) {
|
||||||
|
if (input.value.trim() !== '') {
|
||||||
|
links.push(input.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取元数据
|
||||||
|
let metadataRows = document.querySelectorAll('#metadataTableBody tr');
|
||||||
|
metadataRows.forEach(function(row) {
|
||||||
|
let name = row.querySelector('input[name="name[]"]').value;
|
||||||
|
let artist = row.querySelector('input[name="artist[]"]').value;
|
||||||
|
let album = row.querySelector('input[name="album[]"]').value;
|
||||||
|
if (name && artist && album) {
|
||||||
|
local.push({ name, artist, album });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查是否有且只有一个输入字段被填写
|
||||||
|
let filledCount = (text ? 1 : 0) + (links.length > 0 ? 1 : 0) + (local.length > 0 ? 1 : 0);
|
||||||
|
if (filledCount !== 1) {
|
||||||
|
alert("请确保仅填写了一个输入字段!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取歌单名
|
||||||
|
if (document.getElementById('importStarCheckbox').checked) {
|
||||||
|
playlistName = '我喜欢的音乐';
|
||||||
|
} else {
|
||||||
|
playlistName = document.getElementById('playlistNameInput').value ||
|
||||||
|
document.getElementById('playlistNameLinkInput').value ||
|
||||||
|
'导入音乐 ' + new Date().toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建请求参数
|
||||||
|
let data = {};
|
||||||
|
if (text) {
|
||||||
|
data.text = text;
|
||||||
|
data.playlistName = playlistName;
|
||||||
|
} else if (links.length > 0) {
|
||||||
|
data.link = JSON.stringify(links);
|
||||||
|
data.playlistName = playlistName;
|
||||||
|
} else if (local.length > 0) {
|
||||||
|
data.local = JSON.stringify(local);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加额外参数
|
||||||
|
if (document.getElementById('importStarCheckbox').checked) {
|
||||||
|
data.importStarPlaylist = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await axios({
|
||||||
|
url: `/playlist/import/name/task/create?timestamp=${Date.now()}`,
|
||||||
|
method: 'post',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
|
||||||
|
let taskId = res.data?.data?.taskId
|
||||||
|
if (taskId) {
|
||||||
|
alert(`任务创建成功! 正在导入, 请稍等; 任务id:${taskId}`)
|
||||||
|
// const res2 = await axios({
|
||||||
|
// url: `/playlist/import/task/status?timestamp=${Date.now()}`,
|
||||||
|
// method: 'post',
|
||||||
|
// data: {
|
||||||
|
// id: taskId
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// alert(JSON.stringify(res2.data, null, 2));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('导入失败,请检查您的输入或稍后再试。');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听复选框状态变化
|
||||||
|
document.getElementById('importStarCheckbox').addEventListener('change', function() {
|
||||||
|
let isChecked = this.checked;
|
||||||
|
let playlistNameInputs = document.querySelectorAll('[name="playlistName"]');
|
||||||
|
playlistNameInputs.forEach(function(input) {
|
||||||
|
input.disabled = isChecked;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化时设置歌单名输入框的状态
|
||||||
|
document.getElementById('importStarCheckbox').dispatchEvent(new Event('change'));
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -5,164 +5,45 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>二维码登录</title>
|
<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: 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
#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;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-all;
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status {
|
|
||||||
margin-top: 16px;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status.waiting {
|
|
||||||
background: #fef3c7;
|
|
||||||
color: #92400e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status.success {
|
|
||||||
background: #d1fae5;
|
|
||||||
color: #065f46;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status.error {
|
|
||||||
background: #fee2e2;
|
|
||||||
color: #991b1b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hint {
|
|
||||||
margin-top: 16px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<img id="qrImg" />
|
||||||
<h1>二维码登录</h1>
|
<div id="info" class="info"></div>
|
||||||
<p class="subtitle">使用网易云音乐App扫描二维码登录</p>
|
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js
|
||||||
|
"></script>
|
||||||
<div class="qr-wrapper">
|
|
||||||
<img id="qrImg" src="" alt="二维码加载中..." />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="status" class="status waiting">等待扫描...</div>
|
|
||||||
|
|
||||||
<div id="info" class="info"></div>
|
|
||||||
|
|
||||||
<p class="hint">请打开网易云音乐App,扫描上方二维码完成登录</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
async function login() {
|
async function login() {
|
||||||
let timer
|
let timer
|
||||||
const statusDiv = document.getElementById('status')
|
let timestamp = Date.now()
|
||||||
const cookie = localStorage.getItem('cookie')
|
const cookie = localStorage.getItem('cookie')
|
||||||
|
|
||||||
updateStatus('加载二维码...', 'waiting')
|
|
||||||
getLoginStatus(cookie)
|
getLoginStatus(cookie)
|
||||||
|
const res = await axios({
|
||||||
|
url: `/login/qr/key?timestamp=${Date.now()}`,
|
||||||
|
})
|
||||||
|
const key = res.data.data.unikey
|
||||||
|
const res2 = await axios({
|
||||||
|
url: `/login/qr/create?key=${key}&platform=web&qrimg=true×tamp=${Date.now()}`,
|
||||||
|
})
|
||||||
|
document.querySelector('#qrImg').src = res2.data.data.qrimg
|
||||||
|
|
||||||
try {
|
timer = setInterval(async () => {
|
||||||
const res = await axios({
|
const statusRes = await checkStatus(key)
|
||||||
url: `/login/qr/key?timestamp=${Date.now()}`,
|
if (statusRes.code === 800) {
|
||||||
})
|
alert('二维码已过期,请重新获取')
|
||||||
const key = res.data.data.unikey
|
clearInterval(timer)
|
||||||
|
}
|
||||||
const res2 = await axios({
|
if (statusRes.code === 803) {
|
||||||
url: `/login/qr/create?key=${key}&platform=web&qrimg=true×tamp=${Date.now()}`,
|
// 这一步会返回cookie
|
||||||
})
|
clearInterval(timer)
|
||||||
document.querySelector('#qrImg').src = res2.data.data.qrimg
|
alert('授权登录成功')
|
||||||
updateStatus('请扫描二维码', 'waiting')
|
await getLoginStatus(statusRes.cookie)
|
||||||
|
localStorage.setItem('cookie', statusRes.cookie)
|
||||||
timer = setInterval(async () => {
|
}
|
||||||
const statusRes = await checkStatus(key)
|
}, 3000)
|
||||||
if (statusRes.code === 800) {
|
|
||||||
updateStatus('二维码已过期,请刷新页面', 'error')
|
|
||||||
clearInterval(timer)
|
|
||||||
} else if (statusRes.code === 801) {
|
|
||||||
updateStatus('二维码已扫描,请在手机上确认', 'waiting')
|
|
||||||
} else if (statusRes.code === 802) {
|
|
||||||
updateStatus('登录成功,正在保存信息...', 'waiting')
|
|
||||||
} else if (statusRes.code === 803) {
|
|
||||||
clearInterval(timer)
|
|
||||||
updateStatus('授权登录成功!', 'success')
|
|
||||||
await getLoginStatus(statusRes.cookie)
|
|
||||||
localStorage.setItem('cookie', statusRes.cookie)
|
|
||||||
}
|
|
||||||
}, 3000)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('登录失败:', error)
|
|
||||||
updateStatus('二维码加载失败,请刷新页面重试', 'error')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
login()
|
||||||
|
|
||||||
async function checkStatus(key) {
|
async function checkStatus(key) {
|
||||||
const res = await axios({
|
const res = await axios({
|
||||||
@ -170,30 +51,22 @@
|
|||||||
})
|
})
|
||||||
return res.data
|
return res.data
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getLoginStatus(cookie = '') {
|
async function getLoginStatus(cookie = '') {
|
||||||
try {
|
const res = await axios({
|
||||||
const res = await axios({
|
url: `/login/status?timestamp=${Date.now()}`,
|
||||||
url: `/login/status?timestamp=${Date.now()}`,
|
method: 'post',
|
||||||
method: 'post',
|
data: {
|
||||||
data: {
|
cookie,
|
||||||
cookie,
|
},
|
||||||
},
|
})
|
||||||
})
|
document.querySelector('#info').innerText = JSON.stringify(res.data, null, 2)
|
||||||
document.querySelector('#info').textContent = JSON.stringify(res.data, null, 2)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取登录状态失败:', error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateStatus(message, type) {
|
|
||||||
const statusDiv = document.getElementById('status')
|
|
||||||
statusDiv.textContent = message
|
|
||||||
statusDiv.className = 'status ' + type
|
|
||||||
}
|
|
||||||
|
|
||||||
login()
|
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
.info {
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -5,164 +5,44 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>二维码登录</title>
|
<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: 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
#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;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-all;
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status {
|
|
||||||
margin-top: 16px;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status.waiting {
|
|
||||||
background: #fef3c7;
|
|
||||||
color: #92400e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status.success {
|
|
||||||
background: #d1fae5;
|
|
||||||
color: #065f46;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status.error {
|
|
||||||
background: #fee2e2;
|
|
||||||
color: #991b1b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hint {
|
|
||||||
margin-top: 16px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<img id="qrImg" />
|
||||||
<h1>二维码登录</h1>
|
<div id="info" class="info"></div>
|
||||||
<p class="subtitle">使用网易云音乐App扫描二维码登录</p>
|
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js
|
||||||
|
"></script>
|
||||||
<div class="qr-wrapper">
|
|
||||||
<img id="qrImg" src="" alt="二维码加载中..." />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="status" class="status waiting">等待扫描...</div>
|
|
||||||
|
|
||||||
<div id="info" class="info"></div>
|
|
||||||
|
|
||||||
<p class="hint">请打开网易云音乐App,扫描上方二维码完成登录</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
|
|
||||||
<script>
|
<script>
|
||||||
async function login() {
|
async function login() {
|
||||||
let timer
|
let timer
|
||||||
const statusDiv = document.getElementById('status')
|
let timestamp = Date.now()
|
||||||
const cookie = localStorage.getItem('cookie')
|
const cookie = localStorage.getItem('cookie')
|
||||||
|
|
||||||
updateStatus('加载二维码...', 'waiting')
|
|
||||||
getLoginStatus(cookie)
|
getLoginStatus(cookie)
|
||||||
|
const res = await axios({
|
||||||
|
url: `/login/qr/key?timestamp=${Date.now()}`,
|
||||||
|
})
|
||||||
|
const key = res.data.data.unikey
|
||||||
|
const res2 = await axios({
|
||||||
|
url: `/login/qr/create?key=${key}&platform=web&qrimg=true×tamp=${Date.now()}&ua=pc`,
|
||||||
|
})
|
||||||
|
document.querySelector('#qrImg').src = res2.data.data.qrimg
|
||||||
|
|
||||||
try {
|
timer = setInterval(async () => {
|
||||||
const res = await axios({
|
const statusRes = await checkStatus(key)
|
||||||
url: `/login/qr/key?timestamp=${Date.now()}`,
|
if (statusRes.code === 800) {
|
||||||
})
|
alert('二维码已过期,请重新获取')
|
||||||
const key = res.data.data.unikey
|
clearInterval(timer)
|
||||||
|
}
|
||||||
const res2 = await axios({
|
if (statusRes.code === 803) {
|
||||||
url: `/login/qr/create?key=${key}&platform=web&qrimg=true×tamp=${Date.now()}&ua=pc`,
|
// 这一步会返回cookie
|
||||||
})
|
clearInterval(timer)
|
||||||
document.querySelector('#qrImg').src = res2.data.data.qrimg
|
alert('授权登录成功')
|
||||||
updateStatus('请扫描二维码', 'waiting')
|
await getLoginStatus(statusRes.cookie)
|
||||||
|
localStorage.setItem('cookie', statusRes.cookie)
|
||||||
timer = setInterval(async () => {
|
}
|
||||||
const statusRes = await checkStatus(key)
|
}, 3000)
|
||||||
if (statusRes.code === 800) {
|
|
||||||
updateStatus('二维码已过期,请刷新页面', 'error')
|
|
||||||
clearInterval(timer)
|
|
||||||
} else if (statusRes.code === 801) {
|
|
||||||
updateStatus('二维码已扫描,请在手机上确认', 'waiting')
|
|
||||||
} else if (statusRes.code === 802) {
|
|
||||||
updateStatus('登录成功,正在保存信息...', 'waiting')
|
|
||||||
} else if (statusRes.code === 803) {
|
|
||||||
clearInterval(timer)
|
|
||||||
updateStatus('授权登录成功!', 'success')
|
|
||||||
await getLoginStatus(statusRes.cookie)
|
|
||||||
localStorage.setItem('cookie', statusRes.cookie)
|
|
||||||
}
|
|
||||||
}, 3000)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('登录失败:', error)
|
|
||||||
updateStatus('二维码加载失败,请刷新页面重试', 'error')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
login()
|
||||||
|
|
||||||
async function checkStatus(key) {
|
async function checkStatus(key) {
|
||||||
const res = await axios({
|
const res = await axios({
|
||||||
@ -170,30 +50,22 @@
|
|||||||
})
|
})
|
||||||
return res.data
|
return res.data
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getLoginStatus(cookie = '') {
|
async function getLoginStatus(cookie = '') {
|
||||||
try {
|
const res = await axios({
|
||||||
const res = await axios({
|
url: `/login/status?timestamp=${Date.now()}&ua=pc`,
|
||||||
url: `/login/status?timestamp=${Date.now()}&ua=pc`,
|
method: 'post',
|
||||||
method: 'post',
|
data: {
|
||||||
data: {
|
cookie,
|
||||||
cookie,
|
},
|
||||||
},
|
})
|
||||||
})
|
document.querySelector('#info').innerText = JSON.stringify(res.data, null, 2)
|
||||||
document.querySelector('#info').textContent = JSON.stringify(res.data, null, 2)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取登录状态失败:', error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateStatus(message, type) {
|
|
||||||
const statusDiv = document.getElementById('status')
|
|
||||||
statusDiv.textContent = message
|
|
||||||
statusDiv.className = 'status ' + type
|
|
||||||
}
|
|
||||||
|
|
||||||
login()
|
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
.info {
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -5,136 +5,79 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>音乐解灰测试</title>
|
<title>音乐解灰测试</title>
|
||||||
<style>
|
<style>
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
min-height: 100vh;
|
|
||||||
background: #f5f5f5;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 20px auto;
|
||||||
background: white;
|
padding: 0 20px;
|
||||||
border-radius: 12px;
|
|
||||||
padding: 32px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
}
|
||||||
|
.container {
|
||||||
h1 {
|
background-color: #f5f5f5;
|
||||||
font-size: 24px;
|
padding: 20px;
|
||||||
font-weight: 600;
|
border-radius: 8px;
|
||||||
color: #333;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group {
|
.form-group {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
.source-options {
|
||||||
label {
|
display: flex;
|
||||||
display: block;
|
flex-wrap: wrap;
|
||||||
font-size: 14px;
|
gap: 10px;
|
||||||
font-weight: 500;
|
margin-bottom: 15px;
|
||||||
color: #555;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
}
|
||||||
|
.source-option {
|
||||||
input {
|
display: flex;
|
||||||
width: 100%;
|
align-items: center;
|
||||||
padding: 12px 14px;
|
gap: 5px;
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 15px;
|
|
||||||
outline: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input:focus {
|
|
||||||
border-color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background: #333;
|
background-color: #4CAF50;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 14px 28px;
|
padding: 10px 20px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 6px;
|
border-radius: 4px;
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 500;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
background: #555;
|
background-color: #45a049;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:disabled {
|
|
||||||
background: #999;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
#result {
|
#result {
|
||||||
margin-top: 24px;
|
margin-top: 20px;
|
||||||
padding: 16px;
|
padding: 10px;
|
||||||
background: #f9f9f9;
|
border: 1px solid #ddd;
|
||||||
border-radius: 6px;
|
border-radius: 4px;
|
||||||
border: 1px solid #eee;
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
font-size: 13px;
|
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-all;
|
|
||||||
min-height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hint {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #999;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>音乐解灰测试</h1>
|
<h1>音乐解灰测试</h1>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="songId">音乐 ID</label>
|
<label for="songId">音乐 ID:</label>
|
||||||
<input type="number" id="songId" placeholder="请输入音乐ID" />
|
<input type="number" id="songId" placeholder="请输入音乐ID" required>
|
||||||
<div class="hint">例如: 1372188635</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="sources">音源列表(可选)</label>
|
<label for="sources">音源列表:</label>
|
||||||
<input type="text" id="sources" placeholder="请输入音源" />
|
<input type="text" id="sources" placeholder="请输入音源(非必填)">
|
||||||
<div class="hint">例如: kuwo, kugou, migu</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<button onclick="testSong()">开始测试</button>
|
||||||
<button id="testBtn" onclick="testSong()">开始测试</button>
|
|
||||||
|
|
||||||
<div id="result"></div>
|
<div id="result"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
async function testSong() {
|
async function testSong() {
|
||||||
const songId = document.getElementById('songId').value;
|
const songId = document.getElementById('songId').value;
|
||||||
const sources = document.getElementById('sources').value;
|
|
||||||
const testBtn = document.getElementById('testBtn');
|
|
||||||
const resultDiv = document.getElementById('result');
|
|
||||||
|
|
||||||
if (!songId) {
|
if (!songId) {
|
||||||
alert('请输入音乐ID');
|
alert('请输入音乐ID');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
testBtn.disabled = true;
|
const sources = document.getElementById('sources').value;
|
||||||
testBtn.textContent = '测试中...';
|
|
||||||
|
|
||||||
|
|
||||||
|
const resultDiv = document.getElementById('result');
|
||||||
resultDiv.textContent = '正在请求...';
|
resultDiv.textContent = '正在请求...';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -143,9 +86,6 @@
|
|||||||
resultDiv.textContent = JSON.stringify(data, null, 2);
|
resultDiv.textContent = JSON.stringify(data, null, 2);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
resultDiv.textContent = `请求失败: ${error.message}`;
|
resultDiv.textContent = `请求失败: ${error.message}`;
|
||||||
} finally {
|
|
||||||
testBtn.disabled = false;
|
|
||||||
testBtn.textContent = '开始测试';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -4,230 +4,36 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>播客上传声音</title>
|
<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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
gap: 24px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voice-list {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voice-item {
|
|
||||||
padding: 12px 16px;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voice-item:hover {
|
|
||||||
border-color: #333;
|
|
||||||
background: #f9f9f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voice-item.active {
|
|
||||||
border-color: #333;
|
|
||||||
background: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voice-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voice-cover {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
border-radius: 4px;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voice-name {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voice-tracks {
|
|
||||||
margin-top: 8px;
|
|
||||||
padding-left: 62px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voice-track {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #666;
|
|
||||||
padding: 4px 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
text-align: center;
|
|
||||||
padding: 40px 20px;
|
|
||||||
color: #999;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading {
|
|
||||||
text-align: center;
|
|
||||||
padding: 20px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div>
|
||||||
<h1>播客上传声音</h1>
|
<a href="/qrlogin-nocookie.html"> 如果没登录,请先登录 </a>
|
||||||
<a href="/qrlogin-nocookie.html" class="login-link">还没登录?点击登录</a>
|
</div>
|
||||||
|
<div id="app">
|
||||||
<div class="content">
|
<ul>
|
||||||
<div class="voice-list">
|
<li
|
||||||
<h3 style="font-size: 16px; font-weight: 600; color: #333; margin-bottom: 16px;">选择播客列表</h3>
|
v-for="(item,index) in voicelist"
|
||||||
<div v-if="loading" class="loading">加载中...</div>
|
@click="currentVoiceIndex=index"
|
||||||
<div v-else-if="voicelist.length > 0">
|
:class="{active:currentVoiceIndex===index}"
|
||||||
<div
|
>
|
||||||
v-for="(item, index) in voicelist"
|
<img :src="item.coverUrl" style="width: 50px; width: 50px" />
|
||||||
:key="index"
|
<ul>
|
||||||
@click="currentVoiceIndex = index"
|
<li v-for="(item2,index) in item.voiceListData">
|
||||||
:class="{ active: currentVoiceIndex === index }"
|
{{item2.voiceName}}
|
||||||
class="voice-item"
|
</li>
|
||||||
>
|
</ul>
|
||||||
<div class="voice-header">
|
{{item.voiceListName}}
|
||||||
<img :src="item.coverUrl" class="voice-cover" alt="cover" />
|
</li>
|
||||||
<span class="voice-name">{{ item.voiceListName }}</span>
|
</ul>
|
||||||
</div>
|
<input v-model="songName" placeholder="请输入声音名称" />
|
||||||
<div class="voice-tracks" v-if="item.voiceListData">
|
<input v-model="description" placeholder="请输入介绍" />
|
||||||
<div
|
<input type="file" name="songFile" />
|
||||||
v-for="(item2, index2) in item.voiceListData"
|
<button @click="submit">上传</button>
|
||||||
:key="index2"
|
|
||||||
class="voice-track"
|
|
||||||
>
|
|
||||||
{{ item2.voiceName }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</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>选择文件</label>
|
|
||||||
<input type="file" name="songFile" accept="audio/*" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="btn" @click="submit">上传</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
|
<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>
|
<script src="https://fastly.jsdelivr.net/npm/vue"></script>
|
||||||
<script>
|
<script>
|
||||||
Vue.createApp({
|
Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
@ -237,7 +43,6 @@
|
|||||||
voicelist: [],
|
voicelist: [],
|
||||||
cookieToken: '',
|
cookieToken: '',
|
||||||
currentVoiceIndex: 0,
|
currentVoiceIndex: 0,
|
||||||
loading: false,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@ -245,6 +50,12 @@
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
currentVoice() {
|
currentVoice() {
|
||||||
|
// {
|
||||||
|
// voiceListId: '',
|
||||||
|
// coverImgId: '',
|
||||||
|
// categoryId: '',
|
||||||
|
// secondCategoryId: '',
|
||||||
|
// }
|
||||||
return this.voicelist[this.currentVoiceIndex]
|
return this.voicelist[this.currentVoiceIndex]
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -252,49 +63,27 @@
|
|||||||
submit() {
|
submit() {
|
||||||
console.info('submit')
|
console.info('submit')
|
||||||
const file = document.querySelector('input[type=file]').files[0]
|
const file = document.querySelector('input[type=file]').files[0]
|
||||||
if (!file) {
|
|
||||||
alert('请选择文件')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.upload(file)
|
this.upload(file)
|
||||||
},
|
},
|
||||||
|
|
||||||
async getData() {
|
async getData() {
|
||||||
this.loading = true
|
const res = await axios({
|
||||||
try {
|
url: `/voicelist/search?cookie=${localStorage.getItem('cookie')}`,
|
||||||
const res = await axios({
|
})
|
||||||
url: `/voicelist/search?cookie=${localStorage.getItem('cookie')}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
console.info(res.data.data)
|
console.info(res.data.data)
|
||||||
this.voicelist = res.data.data.list || []
|
this.voicelist = res.data.data.list
|
||||||
this.voicelist.forEach(async (i) => {
|
this.voicelist.map(async (i) => {
|
||||||
try {
|
const res2 = await axios({
|
||||||
const res2 = await axios({
|
url: `/voicelist/list?voiceListId=${i.voiceListId}&limit=5`,
|
||||||
url: `/voicelist/list?voiceListId=${i.voiceListId}&limit=5`,
|
|
||||||
})
|
|
||||||
i.voiceListData = res2.data.data.list || []
|
|
||||||
console.info(res2)
|
|
||||||
} catch (err) {
|
|
||||||
console.error('获取播客详情失败:', err)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
} catch (err) {
|
i.voiceListData = res2.data.data.list
|
||||||
console.error('获取播客列表失败:', err)
|
console.info(res2)
|
||||||
} finally {
|
})
|
||||||
this.loading = false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
upload(file) {
|
upload(file) {
|
||||||
if (!this.currentVoice) {
|
|
||||||
alert('请先选择播客列表')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var formData = new FormData()
|
var formData = new FormData()
|
||||||
formData.append('songFile', file)
|
formData.append('songFile', file)
|
||||||
|
|
||||||
axios({
|
axios({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: `/voice/upload?time=${Date.now()}&cookie=${localStorage.getItem(
|
url: `/voice/upload?time=${Date.now()}&cookie=${localStorage.getItem(
|
||||||
@ -313,14 +102,26 @@
|
|||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
alert(`${file.name} 上传成功`)
|
alert(`${file.name} 上传成功`)
|
||||||
|
if (currentIndx >= fileLength) {
|
||||||
|
console.info('上传完毕')
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(async (err) => {
|
||||||
console.error('上传失败:', err)
|
console.info(err)
|
||||||
alert('上传失败,请重试')
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}).mount('body')
|
}).mount('#app')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
ul li {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li.active {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user