From 33ab93aa339187bb9d052039ba7320ad9c27e5c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B2=E6=82=A6?= Date: Fri, 9 Jan 2026 09:55:06 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D(core):=20=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E5=BC=BA=E5=88=B6=E6=AD=8C=E6=9B=B2ID=E4=B8=BA=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=E4=B8=B2=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 引入`normalizeSongId`工具函数以确保应用内数据类型统一: - 规范化API响应数据(搜索、排行榜、歌单) - 初始化时清理LocalStorage数据(歌单、收藏、当前歌曲) - 同步远程数据时进行类型规范化,避免类型不匹配 - 修复`toggleLike`和播放状态中潜在的ID比较错误 --- index.html | 59 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/index.html b/index.html index 8636778..f097d77 100644 --- a/index.html +++ b/index.html @@ -231,6 +231,14 @@ return getSongDurationSeconds(song); }; + // --- ID Normalization --- + const normalizeSongId = (song) => { + if (!song || song.id === undefined || song.id === null) return song; + const id = String(song.id); + return song.id === id ? song : { ...song, id }; + }; + + const normalizeSongList = (songs) => Array.isArray(songs) ? songs.map(normalizeSongId) : []; // --- API Services --- const api = { @@ -238,8 +246,11 @@ try { const res = await fetch(`${API_BASE}/api/?type=search&keyword=${encodeURIComponent(keyword)}&source=${source}&page=${page}`); const data = await res.json(); - // Return full data object to get total and results - return data.code === 200 ? data.data : { results: [], total: 0 }; + if (data.code === 200) { + const payload = data.data || {}; + return { ...payload, results: normalizeSongList(payload.results) }; + } + return { results: [], total: 0 }; } catch (e) { console.error("Search failed", e); return { results: [], total: 0 }; @@ -306,7 +317,8 @@ const res = await fetch(`${API_BASE}/api/?source=${source}&id=${id}&type=toplist`); const data = await res.json(); if (data.code === 200) { - return Array.isArray(data.data) ? data.data : (data.data.list || data.data.tracks || []); + const list = Array.isArray(data.data) ? data.data : (data.data.list || data.data.tracks || []); + return normalizeSongList(list); } return []; } catch (e) { @@ -319,7 +331,7 @@ const res = await fetch(`${API_BASE}/api/?type=playlist&id=${id}&source=${source}`); const data = await res.json(); if (data.code === 200 && data.data && Array.isArray(data.data.list)) { - return data.data.list; + return normalizeSongList(data.data.list); } return []; } catch (e) { @@ -906,18 +918,21 @@ const observerTarget = useRef(null); // Player State - const [playlist, setPlaylist] = useState(() => JSON.parse(localStorage.getItem('th_playlist')) || []); - const [currentSong, setCurrentSong] = useState(() => JSON.parse(localStorage.getItem('th_current')) || null); + const [playlist, setPlaylist] = useState(() => normalizeSongList(JSON.parse(localStorage.getItem('th_playlist')) || [])); + const [currentSong, setCurrentSong] = useState(() => { + const cached = JSON.parse(localStorage.getItem('th_current')); + return cached ? normalizeSongId(cached) : null; + }); const [isPlaying, setIsPlaying] = useState(false); const [duration, setDuration] = useState(0); const [currentTime, setCurrentTime] = useState(0); const [lyrics, setLyrics] = useState([]); const [mode, setMode] = useState('loop'); // loop, one, shuffle const [volume, setVolume] = useState(1); - const [favorites, setFavorites] = useState(() => JSON.parse(localStorage.getItem('th_favorites')) || []); + const [favorites, setFavorites] = useState(() => normalizeSongList(JSON.parse(localStorage.getItem('th_favorites')) || [])); // Add lastSyncedFavorites to track the state of favorites at the last successful sync // This allows us to determine what the user actually changed (added or removed) - const [lastSyncedFavorites, setLastSyncedFavorites] = useState(() => JSON.parse(localStorage.getItem('th_favorites_synced')) || []); + const [lastSyncedFavorites, setLastSyncedFavorites] = useState(() => normalizeSongList(JSON.parse(localStorage.getItem('th_favorites_synced')) || [])); const [quality, setQuality] = useState(() => localStorage.getItem('th_quality') || '320k'); const [syncToken, setSyncToken] = useState(() => localStorage.getItem('th_sync_token') || ''); const [lastSuccessToken, setLastSuccessToken] = useState(() => localStorage.getItem('th_last_success_token') || ''); @@ -1011,7 +1026,7 @@ if (!syncToken || syncToken !== lastSuccessToken) return; try { // Get latest remote state - const remoteFavorites = await syncService.get('favorites', syncToken) || []; + const remoteFavorites = normalizeSongList(await syncService.get('favorites', syncToken) || []); // Calculate Diff: What did the user do locally since last sync? // Added: In Local but not in LastSynced @@ -1060,26 +1075,26 @@ if (isSwitchingToken) { // Mode: Switch Account/Token -> Remote Overwrites Local try { - const cloudFavorites = await syncService.get('favorites', syncToken); - + const cloudFavorites = normalizeSongList(await syncService.get('favorites', syncToken) || []); + // If cloud has data, use it. If null/empty, we assume new empty account. - const newFavorites = Array.isArray(cloudFavorites) ? cloudFavorites : []; - + const newFavorites = cloudFavorites; + setFavorites(newFavorites); setLastSyncedFavorites(newFavorites); setLastSuccessToken(syncToken); - + } catch (e) { console.error("Token switch sync failed", e); throw e; // Let UI show error } } else { // Mode: Regular Sync -> Merge / Union - + // Pull Remote - const cloudFavorites = await syncService.get('favorites', syncToken); - - if (cloudFavorites && Array.isArray(cloudFavorites)) { + const cloudFavorites = normalizeSongList(await syncService.get('favorites', syncToken) || []); + + if (cloudFavorites.length > 0) { // Merge: Union const merged = [...favorites]; cloudFavorites.forEach(cloudSong => { @@ -1400,11 +1415,13 @@ }; const toggleLike = (song) => { - const exists = favorites.find(s => s.id === song.id); + const normalized = normalizeSongId(song); + if (!normalized) return; + const exists = favorites.find(s => s.id === normalized.id); if (exists) { - setFavorites(favorites.filter(s => s.id !== song.id)); + setFavorites(favorites.filter(s => s.id !== normalized.id)); } else { - setFavorites([song, ...favorites]); + setFavorites([normalized, ...favorites]); } };