feat: 声匣 —— 网易云 / QQ 音乐自托管下载服务

由 163-music-dl CLI 重构为 Web 服务:
- 下载流水线、歌词与标签逻辑沿用旧版(node-id3 / ffmpeg)
- 网易云(chksz 接口 + music.163.com 元数据)与 QQ 音乐(VIP Cookie)provider
- QQ 扫码登录与 musickey 自动续期
- SQLite 持久化的下载队列,SSE 实时进度
- 曲库工具(M3U / ZIP / 回收站 / 补全标签)、历史统计与导出、访问密码
- Vite 8 + Vue 3 前端,浅色 / 深色
- Docker 部署配置(飞牛 NAS)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jocay
2026-10-10 22:05:30 +08:00
commit 21945cc54f
73 changed files with 12507 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
<script setup lang="ts">
// 线性图标,1.6px 描边,跟随 currentColor
defineProps<{ name: string; size?: number }>();
</script>
<script lang="ts">
const paths = {
play: '<path d="M8 5.5v13l11-6.5z" fill="currentColor" stroke="none"/>',
pause: '<rect x="7" y="5.5" width="3.4" height="13" rx="1" fill="currentColor" stroke="none"/><rect x="13.6" y="5.5" width="3.4" height="13" rx="1" fill="currentColor" stroke="none"/>',
download: '<path d="M12 4v11m0 0-4.5-4.5M12 15l4.5-4.5M5 19.5h14"/>',
check: '<path d="m5 12.5 4.5 4.5L19 7.5"/>',
x: '<path d="M6 6l12 12M18 6 6 18"/>',
retry: '<path d="M4.5 12a7.5 7.5 0 1 0 2.2-5.3M4.5 4.5v4h4"/>',
folder: '<path d="M3.5 7.5a2 2 0 0 1 2-2h4l2 2h7a2 2 0 0 1 2 2v7.5a2 2 0 0 1-2 2h-13a2 2 0 0 1-2-2z"/>',
music: '<path d="M9 18V6l10-2v12"/><circle cx="6.5" cy="18" r="2.5"/><circle cx="16.5" cy="16" r="2.5"/>',
file: '<path d="M7 3.5h7l4 4v13H7z"/><path d="M14 3.5v4h4"/>',
trash: '<path d="M5 7h14M10 7V5h4v2M7 7l1 12.5h8L17 7"/>',
zip: '<path d="M7 3.5h10v17H7z"/><path d="M12 3.5v2m0 2v2m0 2v2"/><rect x="10.5" y="14" width="3" height="3" rx=".6"/>',
list: '<path d="M9 7h11M9 12h11M9 17h11M4.5 7h.01M4.5 12h.01M4.5 17h.01"/>',
search: '<circle cx="11" cy="11" r="6.5"/><path d="m20 20-4.4-4.4"/>',
chevron: '<path d="m9 6 6 6-6 6"/>',
back: '<path d="m15 6-6 6 6 6"/>',
tag: '<path d="M4 4h7.5l8.5 8.5-7.5 7.5L4 11.5z"/><circle cx="8.5" cy="8.5" r="1.3"/>',
pauseCircle: '<circle cx="12" cy="12" r="8.5"/><path d="M10 9v6M14 9v6"/>',
playCircle: '<circle cx="12" cy="12" r="8.5"/><path d="m10.2 8.8 5 3.2-5 3.2z"/>',
volume: '<path d="M4.5 9.5h3l4.5-4v13l-4.5-4h-3z"/><path d="M15.5 9a4 4 0 0 1 0 6M18 6.5a7.5 7.5 0 0 1 0 11"/>',
link: '<path d="M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1"/><path d="M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1"/>',
user: '<circle cx="12" cy="8.5" r="3.8"/><path d="M4.5 20c1.2-3.8 4.2-5.5 7.5-5.5s6.3 1.7 7.5 5.5"/>',
disc: '<circle cx="12" cy="12" r="8.5"/><circle cx="12" cy="12" r="2.2"/>',
export: '<path d="M12 15V4m0 0L7.5 8.5M12 4l4.5 4.5M5 14v5.5h14V14"/>',
plus: '<path d="M12 5v14M5 12h14"/>',
logout: '<path d="M14 4.5H6.5v15H14M10.5 12H20m0 0-3.5-3.5M20 12l-3.5 3.5"/>',
} as const;
</script>
<template>
<svg
class="icon"
:width="size ?? 18"
:height="size ?? 18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.6"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
v-html="paths[name as keyof typeof paths]"
/>
</template>
<style scoped>
.icon {
flex-shrink: 0;
display: block;
}
</style>