refactor(download): 重构下载系统,支持暂停/恢复/取消,修复歌词加载

- 新建 DownloadManager 类(主进程),每个任务独立 AbortController 控制
- 新建 Pinia useDownloadStore 作为渲染进程单一数据源
- 支持暂停/恢复/取消下载,支持断点续传(Range header)
- 批量下载全部完成后发送汇总系统通知,单首不重复通知
- 并发数可配置(1-5),队列持久化(重启后恢复)
- 修复下载列表不全、封面加载失败、通知重复等 bug
- 修复本地/下载歌曲歌词加载:优先从 ID3/FLAC 元数据提取,API 作为 fallback
- 删除 useDownloadStatus.ts,统一状态管理
- DownloadDrawer/DownloadPage 全面重写,移除 @apply 违规
- 新增 5 语言 i18n 键值(暂停/恢复/取消/排队中等)
This commit is contained in:
alger
2026-03-27 23:00:39 +08:00
parent 59f71148af
commit bc46024499
20 changed files with 1861 additions and 1578 deletions
+50 -3
View File
@@ -188,10 +188,10 @@ const setupMusicWatchers = () => {
if (playMusic.value.lyric && typeof playMusic.value.lyric === 'object') {
playMusic.value.lyric.hasWordByWord = hasWordByWord;
}
} else {
} else if (lyricData && typeof lyricData === 'object' && lyricData.lrcArray?.length > 0) {
// 使用现有的歌词数据结构
const rawLrc = lyricData?.lrcArray || [];
lrcTimeArray.value = lyricData?.lrcTimeArray || [];
const rawLrc = lyricData.lrcArray || [];
lrcTimeArray.value = lyricData.lrcTimeArray || [];
try {
const { translateLyrics } = await import('@/services/lyricTranslation');
@@ -200,6 +200,53 @@ const setupMusicWatchers = () => {
console.error('翻译歌词失败,使用原始歌词:', e);
lrcArray.value = rawLrc as any;
}
} else if (isElectron && playMusic.value.playMusicUrl?.startsWith('local:///')) {
// 从下载/本地文件的 ID3/FLAC 元数据中提取嵌入歌词
try {
let filePath = decodeURIComponent(
playMusic.value.playMusicUrl.replace('local:///', '')
);
// 处理 Windows 路径:/C:/... → C:/...
if (/^\/[a-zA-Z]:\//.test(filePath)) {
filePath = filePath.slice(1);
}
const embeddedLyrics = await window.api.getEmbeddedLyrics(filePath);
if (embeddedLyrics) {
const {
lrcArray: parsedLrcArray,
lrcTimeArray: parsedTimeArray,
hasWordByWord
} = await parseLyricsString(embeddedLyrics);
lrcArray.value = parsedLrcArray;
lrcTimeArray.value = parsedTimeArray;
if (playMusic.value.lyric && typeof playMusic.value.lyric === 'object') {
(playMusic.value.lyric as any).hasWordByWord = hasWordByWord;
}
} else {
// 无嵌入歌词 — 若有数字 ID,尝试 API 兜底
const songId = playMusic.value.id;
if (songId && typeof songId === 'number') {
try {
const { getMusicLrc } = await import('@/api/music');
const res = await getMusicLrc(songId);
if (res?.data?.lrc?.lyric) {
const { lrcArray: apiLrcArray, lrcTimeArray: apiTimeArray } =
await parseLyricsString(res.data.lrc.lyric);
lrcArray.value = apiLrcArray;
lrcTimeArray.value = apiTimeArray;
}
} catch (apiErr) {
console.error('API lyrics fallback failed:', apiErr);
}
}
}
} catch (err) {
console.error('Failed to extract embedded lyrics:', err);
}
} else {
// 无歌词数据
lrcArray.value = [];
lrcTimeArray.value = [];
}
// 当歌词数据更新时,如果歌词窗口打开,则发送数据
if (isElectron && isLyricWindowOpen.value) {