refactor(playHistory): 持久化重写,统一防抖落盘与序列化兜底

把 playHistory 接入 utils/debouncedStorage 与 utils/persistedSong,
配合 add* 方法重构与 clearAll 同步落盘,闭合 localStorage 配额防护。

- musicHistory 类型从 SongResult 收敛到 MusicHistoryItem(精简子集),
  导出 MinifiedDjProgram、stripBase64Covers,给 podcast/playlist/album/
  podcastRadio 历史也做顶层 picUrl/coverImgUrl/coverUrl 的 base64 兜底
- serializePlayHistoryState 提取为模块级函数,给 persistedstate.serializer
  与 clearAll 同步落盘共用,避免格式漂移;isPodcast/program 字段必须
  保留——playbackController.playTrack 用 isPodcast 决定写哪条历史
- 5 个 add* 全部重写成单步 ref 重赋值,避免 splice/pop/unshift 多次
  触发 watch 与持久化;命中已有条目时累加 count + 刷新 lastPlayTime,
  picUrl/al 用新数据覆盖(封面可能换了短引用)
- clearAll 增加 flushDebouncedStorage + 同步 setItem 空状态,防止
  kill -9 落在 2s 防抖窗口里导致旧历史残留
- heatmap/index.vue 类型切到 MusicHistoryItem,移除 music.artists
  兜底(minifySong 已合并 ar/artists,只剩 ar)
This commit is contained in:
chengww
2026-05-17 23:08:22 +08:00
parent 537e280fdd
commit 761884f23a
2 changed files with 170 additions and 111 deletions
+9 -7
View File
@@ -152,6 +152,7 @@ import { usePlayerStore } from '@/store/modules/player';
import { usePlayHistoryStore } from '@/store/modules/playHistory';
import type { SongResult } from '@/types/music';
import { setAnimationClass } from '@/utils';
import type { MusicHistoryItem } from '@/utils/persistedSong';
const { t } = useI18n();
const playHistoryStore = usePlayHistoryStore();
@@ -220,7 +221,7 @@ const processHistoryData = () => {
const oneYearAgo = Date.now() - 365 * 24 * 60 * 60 * 1000;
// 遍历音乐历史记录
playHistoryStore.musicHistory.forEach((music: SongResult & { count?: number }) => {
playHistoryStore.musicHistory.forEach((music: MusicHistoryItem) => {
// 假设每次播放都记录在当前时间,我们根据 count 分散到最近的日期
const playCount = music.count || 1;
const now = Date.now();
@@ -251,7 +252,7 @@ const processHistoryData = () => {
dailyMap[dateKey].songs.set(songId, {
id: music.id,
name: music.name || 'Unknown',
artist: music.ar?.[0]?.name || music.artists?.[0]?.name || 'Unknown Artist',
artist: music.ar?.[0]?.name || 'Unknown Artist',
playCount: 1
});
}
@@ -307,11 +308,11 @@ const mostPlayedSong = computed<{
{ id: string | number; name: string; artist: string; playCount: number }
>();
playHistoryStore.musicHistory.forEach((music: SongResult & { count?: number }) => {
playHistoryStore.musicHistory.forEach((music: MusicHistoryItem) => {
const id = music.id;
const count = music.count || 1;
const name = music.name || 'Unknown';
const artist = music.ar?.[0]?.name || music.artists?.[0]?.name || 'Unknown Artist';
const artist = music.ar?.[0]?.name || 'Unknown Artist';
if (songPlayCounts.has(id)) {
songPlayCounts.get(id)!.playCount += count;
@@ -382,7 +383,7 @@ const latestNightSong = computed<{
return {
id: randomSong.id,
name: randomSong.name || 'Unknown',
artist: randomSong.ar?.[0]?.name || randomSong.artists?.[0]?.name || 'Unknown Artist',
artist: randomSong.ar?.[0]?.name || 'Unknown Artist',
time: `凌晨 ${randomHour.toString().padStart(2, '0')}:${randomMinute.toString().padStart(2, '0')}`
};
}
@@ -395,7 +396,7 @@ const latestNightSong = computed<{
return {
id: song.id,
name: song.name || 'Unknown',
artist: song.ar?.[0]?.name || song.artists?.[0]?.name || 'Unknown Artist',
artist: song.ar?.[0]?.name || 'Unknown Artist',
time: `凌晨 ${randomHour.toString().padStart(2, '0')}:${randomMinute.toString().padStart(2, '0')}`
};
}
@@ -407,7 +408,8 @@ const latestNightSong = computed<{
const handlePlaySong = async (songId: string | number) => {
const song = playHistoryStore.musicHistory.find((music) => music.id === songId);
if (song) {
await playerStore.setPlay(song);
// MusicHistoryItem 是 SongResult 的精简子集,setPlay 内部用 optional chain 访问字段
await playerStore.setPlay(song as SongResult);
playerStore.setPlayMusic(true);
}
};