Files
Nekosonic-Music/src/composables/useDownload.ts
Atdunbg e40f82cc51 refactor: 创建统一 API 封装层,前端不再直接调用 invoke
- 新增 src/api.ts,按职责分为 MusicApi/AudioApi/DeviceApi/DownloadApi/AppApi 五个命名空间
- 替换 15 个文件中所有 invoke 调用为 API 层方法
- 后端接口变更只需修改 api.ts 一处,便于后期迭代维护
2026-05-29 22:02:42 +08:00

135 lines
3.6 KiB
TypeScript

import { reactive, watch } from 'vue';
import { listen } from '@tauri-apps/api/event';
import { useSettingsStore } from '../stores/settings';
import { showToast } from '../composables/useToast';
import { getCoverUrl, type Song } from '../utils/song';
import { DownloadApi } from '../api';
interface DownloadTask {
id: number;
name: string;
progress: number;
}
const downloadingIds = reactive<Set<number>>(new Set());
const tasks = reactive<DownloadTask[]>([]);
const localSongIds = reactive<Set<number>>(new Set());
let listenerSetup = false;
let storeSetup = false;
async function setupDownloadListener() {
if (listenerSetup) return;
listenerSetup = true;
await listen<{ id: number; progress: number; name: string }>('download-progress', (event) => {
const { id, progress, name } = event.payload;
if (progress >= 100) {
const idx = tasks.findIndex(t => t.id === id);
if (idx >= 0) {
tasks.splice(idx, 1);
downloadingIds.delete(id);
showToast(`${name} 下载完成`, 'success');
}
} else {
const task = tasks.find(t => t.id === id);
if (task) {
task.progress = progress;
}
}
});
}
async function refreshLocalIds() {
try {
const settings = useSettingsStore();
const list: { id: number }[] = await DownloadApi.listLocalSongs(settings.downloadPath || null);
localSongIds.clear();
for (const s of list) {
localSongIds.add(s.id);
}
} catch { /* 忽略 */ }
}
function ensureStoreSetup() {
if (storeSetup) return;
storeSetup = true;
const settings = useSettingsStore();
refreshLocalIds();
watch(() => settings.downloadPath, () => {
refreshLocalIds();
});
}
function isDownloaded(songId: number): boolean {
return localSongIds.has(songId);
}
function isDownloading(songId: number): boolean {
return downloadingIds.has(songId);
}
function getDownloadProgress(songId: number): number {
const task = tasks.find(t => t.id === songId);
return task?.progress ?? 0;
}
async function downloadSong(song: Song) {
if (downloadingIds.has(song.id)) return;
if (localSongIds.has(song.id)) {
showToast(`${song.name} 已下载`, 'info');
return;
}
const settings = useSettingsStore();
const artist = song.ar?.map(a => a.name).join(' / ') || '未知';
const albumName = song.al?.name || null;
const durationVal = song.dt || null;
const coverUrl = getCoverUrl(song) || null;
downloadingIds.add(song.id);
tasks.push({ id: song.id, name: song.name, progress: 0 });
try {
await DownloadApi.downloadSong({
id: song.id,
name: song.name,
artist,
album: albumName,
duration: durationVal,
coverUrl,
level: settings.audioQuality,
downloadPath: settings.downloadPath || null,
});
localSongIds.add(song.id);
} catch (e: any) {
downloadingIds.delete(song.id);
const idx = tasks.findIndex(t => t.id === song.id);
if (idx >= 0) tasks.splice(idx, 1);
if (e === '文件已存在') {
localSongIds.add(song.id);
showToast(`${song.name} 已下载`, 'info');
} else if (e === 'VIP歌曲无法下载') {
showToast(`${song.name} 为 VIP 歌曲,无法下载`, 'error');
} else if (typeof e === 'string' && e.includes('VIP')) {
showToast(`${song.name} 需要 VIP 权限才能下载`, 'error');
} else {
showToast(`下载失败: ${e}`, 'error');
}
}
}
export function useDownload() {
setupDownloadListener();
ensureStoreSetup();
return {
downloadingIds,
tasks,
localSongIds,
isDownloaded,
isDownloading,
getDownloadProgress,
downloadSong,
refreshLocalIds,
};
}