mirror of
https://github.com/atdunbg/Nekosonic-Music.git
synced 2026-06-22 00:58:51 +08:00
新功能: - 亮色主题:新增浅色外观模式,7种主题色各有对应亮色变体 - 封面主色背景:漫游抽屉自动提取封面主色,PlayerBar跟随继承 - 发现页重做:多类型搜索(歌曲/歌手/专辑)+搜索建议+搜索历史 - 漫游页重做:进入即播放,布局改为封面+歌名+播放/下一首/减少推荐 - 减少推荐:FM模式下可标记不推荐歌曲或歌手 - 列表风格统一:播放指示器跳动动画+hover播放图标+图标统一使用Lucide 修复: - 专辑页艺术家过多时窗口缩小竖排,改为自动换行 - FM播放时退出登录后首页仍可点击下一首 - 本地音乐播放时缓冲进度条未重置 - 亮色主题下多处文字不可见 - 退出FM模式时状态未正确清理 - 暗色模式下关闭抽屉时PlayerBar闪烁亮色(改用opacity过渡) - player.ts tickInterval双变量状态不同步,统一为clearTick/setTick 变更: - 移除播放列表按钮数字角标 - 主页卡片标题固定白色不随主题变化 - 全项目空catch块格式统一 - 清理冗余注释和代码
137 lines
3.7 KiB
TypeScript
137 lines
3.7 KiB
TypeScript
import { reactive, watch } from 'vue';
|
|
import { invoke } from '@tauri-apps/api/core';
|
|
import { listen } from '@tauri-apps/api/event';
|
|
import { useSettingsStore } from '../stores/settings';
|
|
import { showToast } from '../composables/useToast';
|
|
import { getCoverUrl, type Song } from '../utils/song';
|
|
|
|
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 invoke('list_local_songs', { downloadPath: 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 invoke('download_song', {
|
|
query: {
|
|
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,
|
|
};
|
|
}
|