feat: 跨平台持久化与版本管理优化

- Cookie 存储从 temp_dir 迁移至 Tauri app_data_dir,兼容 Linux
- 简单统一风格,UI优化
- recentLocal 播放历史持久化到 localStorage
- 添加设置界面可以修改简单的设置
This commit is contained in:
2026-05-12 09:58:07 +08:00
parent 463e8e95b6
commit 7847a9f6b2
28 changed files with 1592 additions and 535 deletions

View File

@ -2,8 +2,9 @@ import { defineStore } from 'pinia';
import { ref , watch } from 'vue';
import { invoke } from '@tauri-apps/api/core';
import { normalizeSong } from '../utils/song';
import { useSettingsStore } from './settings';
import { useUserStore } from './user';
// 设置播放模式,目前只有顺序循环,后续可扩展
export type PlayMode = 'loop' | 'shuffle' | 'repeat-one';
export interface Song {
@ -33,6 +34,22 @@ export function setupCacheProgressListener() {
// 在 store 定义外调用 setupCacheProgressListener(),或者在应用入口调用
function loadRecentLocal(): Song[] {
try {
const raw = localStorage.getItem('recent_local');
if (raw) return JSON.parse(raw);
} catch {}
return [];
}
function loadLikedIdsFromStorage(): Set<number> {
try {
const raw = localStorage.getItem('liked_ids');
if (raw) return new Set(JSON.parse(raw));
} catch {}
return new Set();
}
export const usePlayerStore = defineStore('player', () => {
const currentSong = ref<Song | null>(null);
const playing = ref(false);
@ -44,6 +61,56 @@ export const usePlayerStore = defineStore('player', () => {
let tickInterval: ReturnType<typeof setInterval> | null = null;
const recentLocal = ref<Song[]>(loadRecentLocal());
const MAX_RECENT = 200;
const likedIds = ref<Set<number>>(loadLikedIdsFromStorage());
function isLiked(songId: number): boolean {
return likedIds.value.has(songId);
}
async function loadLikedIds() {
const userStore = useUserStore();
if (!userStore.isLoggedIn) return;
try {
const json: string = await invoke('likelist', { uid: userStore.user!.userId });
const data = JSON.parse(json);
const ids: number[] = data.ids || data.data?.ids || [];
likedIds.value = new Set(ids);
} catch { /* 忽略 */ }
}
async function toggleLike(songId: number) {
const wasLiked = likedIds.value.has(songId);
const newLike = !wasLiked;
try {
await invoke('like_song', { query: { id: songId, like: newLike ? 'true' : 'false' } });
if (newLike) {
likedIds.value.add(songId);
} else {
likedIds.value.delete(songId);
}
likedIds.value = new Set(likedIds.value);
} catch { /* 忽略 */ }
}
function addRecent(song: Song) {
recentLocal.value = recentLocal.value.filter(s => s.id !== song.id);
recentLocal.value.unshift(song);
if (recentLocal.value.length > MAX_RECENT) {
recentLocal.value = recentLocal.value.slice(0, MAX_RECENT);
}
}
watch(recentLocal, (val) => {
localStorage.setItem('recent_local', JSON.stringify(val));
}, { deep: true });
watch(likedIds, (val) => {
localStorage.setItem('liked_ids', JSON.stringify([...val]));
}, { deep: true });
const isFmMode = ref(false);
let fmNextCallback: (() => void) | null = null;
@ -62,7 +129,7 @@ export const usePlayerStore = defineStore('player', () => {
// 如果缺少时长,尝试从详情接口获取
if (!song.dt || song.dt === 0) {
try {
const jsonStr: string = await invoke('get_song_detail', { id: Number(song.id) });
const jsonStr: string = await invoke('get_song_detail', { id: String(song.id) });
const data = JSON.parse(jsonStr);
const full = data.songs?.[0];
if (full) {
@ -80,13 +147,15 @@ export const usePlayerStore = defineStore('player', () => {
currentSong.value = song;
try {
const url: string = await invoke('get_song_url', { id: Number(song.id) });
const settings = useSettingsStore();
const url: string = await invoke('get_song_url', { query: { id: Number(song.id), level: settings.audioQuality } });
if (!url) throw new Error('无播放源');
await invoke('play_audio', { url });
playing.value = true;
duration.value = (song.dt || 0) / 1000;
currentTime.value = 0;
startTick();
addRecent(song);
} catch (e) {
console.error('FM播放失败', e);
playing.value = false;
@ -122,8 +191,8 @@ export const usePlayerStore = defineStore('player', () => {
currentTime.value = 0;
duration.value = (song.dt || 0) / 1000;
// 获取 URL 并播放
const url: string = await invoke('get_song_url', { id: Number(song.id) });
const settings = useSettingsStore();
const url: string = await invoke('get_song_url', { query: { id: Number(song.id), level: settings.audioQuality } });
if (!url) {
console.error('未获取到有效播放地址', song);
return;
@ -132,6 +201,7 @@ export const usePlayerStore = defineStore('player', () => {
await invoke('play_audio', { url });
playing.value = true;
startTick();
addRecent(song);
} catch (e) {
console.error('播放失败', e);
playing.value = false;
@ -279,6 +349,10 @@ export const usePlayerStore = defineStore('player', () => {
showRoamDrawer.value = false;
}
function toggleRoamDrawer() {
showRoamDrawer.value = !showRoamDrawer.value;
}
async function loadFirstFmSong() {
try {
const jsonStr: string = await invoke('personal_fm');
@ -388,9 +462,17 @@ watch(playing, (val) => {
removeFromQueue,
clearQueue,
recentLocal,
likedIds,
isLiked,
loadLikedIds,
toggleLike,
showRoamDrawer,
openRoamDrawer,
closeRoamDrawer,
toggleRoamDrawer,
loadFirstFmSong,
fmSong,