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
+80
View File
@@ -0,0 +1,80 @@
<script setup lang="ts">
import { toasts } from '../lib/store.ts';
</script>
<template>
<div class="toasts" aria-live="polite">
<TransitionGroup name="toast">
<div v-for="t in toasts" :key="t.id" class="toast" :class="t.kind">
<i class="mark" />
<span>{{ t.text }}</span>
</div>
</TransitionGroup>
</div>
</template>
<style scoped>
.toasts {
position: fixed;
right: 20px;
top: 20px;
z-index: 50;
display: flex;
flex-direction: column;
gap: 8px;
pointer-events: none;
}
.toast {
display: flex;
align-items: center;
gap: 10px;
max-width: 380px;
padding: 10px 16px 10px 14px;
border: 1px solid var(--line);
border-radius: var(--radius);
background: var(--surface);
box-shadow: var(--shadow);
font-size: 13px;
pointer-events: auto;
}
.mark {
width: 6px;
height: 6px;
flex-shrink: 0;
border-radius: 50%;
background: var(--ink-3);
}
.ok .mark {
background: var(--ok);
}
.error .mark {
background: var(--danger);
}
.toast-enter-active,
.toast-leave-active {
transition: all 220ms var(--ease);
}
.toast-enter-from {
opacity: 0;
transform: translateY(-6px);
}
.toast-leave-to {
opacity: 0;
transform: translateX(10px);
}
@media (max-width: 760px) {
.toasts {
left: 12px;
right: 12px;
top: 12px;
}
}
</style>