mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-05-17 10:27:30 +08:00
refactor: 重构历史记录
This commit is contained in:
@@ -240,13 +240,14 @@ const playAlbum = async (album: any) => {
|
||||
const playerCore = usePlayerCoreStore();
|
||||
const playlistStore = usePlaylistStore();
|
||||
|
||||
const albumCover = data.album?.picUrl || album.picUrl;
|
||||
const playlist = data.songs.map((s: any) => ({
|
||||
id: s.id,
|
||||
name: s.name,
|
||||
picUrl: s.al?.picUrl || album.picUrl,
|
||||
source: 'netease',
|
||||
song: s,
|
||||
...s,
|
||||
picUrl: s.al?.picUrl || albumCover,
|
||||
playLoading: false
|
||||
}));
|
||||
|
||||
|
||||
@@ -148,13 +148,13 @@
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { useMusicHistory } from '@/hooks/MusicHistoryHook';
|
||||
import { usePlayerStore } from '@/store/modules/player';
|
||||
import { usePlayHistoryStore } from '@/store/modules/playHistory';
|
||||
import type { SongResult } from '@/types/music';
|
||||
import { setAnimationClass } from '@/utils';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { musicList } = useMusicHistory();
|
||||
const playHistoryStore = usePlayHistoryStore();
|
||||
const playerStore = usePlayerStore();
|
||||
const loading = ref(true);
|
||||
|
||||
@@ -220,7 +220,7 @@ const processHistoryData = () => {
|
||||
const oneYearAgo = Date.now() - 365 * 24 * 60 * 60 * 1000;
|
||||
|
||||
// 遍历音乐历史记录
|
||||
musicList.value.forEach((music: SongResult & { count?: number }) => {
|
||||
playHistoryStore.musicHistory.forEach((music: SongResult & { count?: number }) => {
|
||||
// 假设每次播放都记录在当前时间,我们根据 count 分散到最近的日期
|
||||
const playCount = music.count || 1;
|
||||
const now = Date.now();
|
||||
@@ -300,14 +300,14 @@ const mostPlayedSong = computed<{
|
||||
artist: string;
|
||||
playCount: number;
|
||||
} | null>(() => {
|
||||
if (musicList.value.length === 0) return null;
|
||||
if (playHistoryStore.musicHistory.length === 0) return null;
|
||||
|
||||
const songPlayCounts = new Map<
|
||||
string | number,
|
||||
{ id: string | number; name: string; artist: string; playCount: number }
|
||||
>();
|
||||
|
||||
musicList.value.forEach((music: SongResult & { count?: number }) => {
|
||||
playHistoryStore.musicHistory.forEach((music: SongResult & { count?: number }) => {
|
||||
const id = music.id;
|
||||
const count = music.count || 1;
|
||||
const name = music.name || 'Unknown';
|
||||
@@ -365,14 +365,17 @@ const latestNightSong = computed<{
|
||||
artist: string;
|
||||
time: string;
|
||||
} | null>(() => {
|
||||
if (musicList.value.length === 0) return null;
|
||||
if (playHistoryStore.musicHistory.length === 0) return null;
|
||||
|
||||
// 模拟一些播放时间数据(实际应该从历史记录中获取)
|
||||
// 这里简化处理,随机选择一首歌作为凌晨播放
|
||||
const nightSongs = musicList.value.filter(() => Math.random() > 0.8);
|
||||
const nightSongs = playHistoryStore.musicHistory.filter(() => Math.random() > 0.8);
|
||||
|
||||
if (nightSongs.length === 0 && musicList.value.length > 0) {
|
||||
const randomSong = musicList.value[Math.floor(Math.random() * musicList.value.length)];
|
||||
if (nightSongs.length === 0 && playHistoryStore.musicHistory.length > 0) {
|
||||
const randomSong =
|
||||
playHistoryStore.musicHistory[
|
||||
Math.floor(Math.random() * playHistoryStore.musicHistory.length)
|
||||
];
|
||||
const randomHour = Math.floor(Math.random() * 6); // 0-5点
|
||||
const randomMinute = Math.floor(Math.random() * 60);
|
||||
|
||||
@@ -402,7 +405,7 @@ const latestNightSong = computed<{
|
||||
|
||||
// 播放歌曲
|
||||
const handlePlaySong = async (songId: string | number) => {
|
||||
const song = musicList.value.find((music) => music.id === songId);
|
||||
const song = playHistoryStore.musicHistory.find((music) => music.id === songId);
|
||||
if (song) {
|
||||
await playerStore.setPlay(song);
|
||||
playerStore.setPlayMusic(true);
|
||||
|
||||
@@ -266,12 +266,8 @@ import AlbumItem from '@/components/common/AlbumItem.vue';
|
||||
import { navigateToMusicList } from '@/components/common/MusicListNavigator';
|
||||
import PlaylistItem from '@/components/common/PlaylistItem.vue';
|
||||
import SongItem from '@/components/common/SongItem.vue';
|
||||
import { useAlbumHistory } from '@/hooks/AlbumHistoryHook';
|
||||
import { useMusicHistory } from '@/hooks/MusicHistoryHook';
|
||||
import { usePlaylistHistory } from '@/hooks/PlaylistHistoryHook';
|
||||
import { usePodcastHistory } from '@/hooks/PodcastHistoryHook';
|
||||
import { usePodcastRadioHistory } from '@/hooks/PodcastRadioHistoryHook';
|
||||
import { usePlayerStore } from '@/store/modules/player';
|
||||
import { usePlayHistoryStore } from '@/store/modules/playHistory';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import type { SongResult } from '@/types/music';
|
||||
import { isMobile, setAnimationClass, setAnimationDelay } from '@/utils';
|
||||
@@ -291,11 +287,7 @@ interface HistoryRecord extends Partial<SongResult> {
|
||||
const { t } = useI18n();
|
||||
const message = useMessage();
|
||||
const router = useRouter();
|
||||
const { delMusic, musicList } = useMusicHistory();
|
||||
const { delPlaylist, playlistList } = usePlaylistHistory();
|
||||
const { delAlbum, albumList } = useAlbumHistory();
|
||||
const { delPodcast, podcastList } = usePodcastHistory();
|
||||
const { delPodcastRadio, podcastRadioList } = usePodcastRadioHistory();
|
||||
const playHistoryStore = usePlayHistoryStore();
|
||||
const userStore = useUserStore();
|
||||
const scrollbarRef = ref();
|
||||
const loading = ref(false);
|
||||
@@ -407,29 +399,29 @@ const getCurrentList = (): any[] => {
|
||||
if (currentCategory.value === 'songs') {
|
||||
switch (currentTab.value) {
|
||||
case 'local':
|
||||
return musicList.value;
|
||||
return playHistoryStore.musicHistory;
|
||||
case 'cloud':
|
||||
return cloudRecords.value.filter((item) => item.id);
|
||||
}
|
||||
} else if (currentCategory.value === 'playlists') {
|
||||
switch (currentTab.value) {
|
||||
case 'local':
|
||||
return playlistList.value;
|
||||
return playHistoryStore.playlistHistory;
|
||||
case 'cloud':
|
||||
return cloudPlaylists.value;
|
||||
}
|
||||
} else if (currentCategory.value === 'albums') {
|
||||
switch (currentTab.value) {
|
||||
case 'local':
|
||||
return albumList.value;
|
||||
return playHistoryStore.albumHistory;
|
||||
case 'cloud':
|
||||
return cloudAlbums.value;
|
||||
}
|
||||
} else if (currentCategory.value === 'podcasts') {
|
||||
if (currentPodcastSubTab.value === 'episodes') {
|
||||
return podcastList.value;
|
||||
return playHistoryStore.podcastHistory;
|
||||
} else {
|
||||
return podcastRadioList.value;
|
||||
return playHistoryStore.podcastRadioHistory;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
@@ -499,13 +491,13 @@ const handleAlbumClick = async (item: any) => {
|
||||
|
||||
// 删除歌单记录
|
||||
const handleDelPlaylist = (item: any) => {
|
||||
delPlaylist(item);
|
||||
playHistoryStore.delPlaylist(item);
|
||||
displayList.value = displayList.value.filter((playlist) => playlist.id !== item.id);
|
||||
};
|
||||
|
||||
// 删除专辑记录
|
||||
const handleDelAlbum = (item: any) => {
|
||||
delAlbum(item);
|
||||
playHistoryStore.delAlbum(item);
|
||||
displayList.value = displayList.value.filter((album) => album.id !== item.id);
|
||||
};
|
||||
|
||||
@@ -538,12 +530,12 @@ const handlePodcastRadioClick = (item: any) => {
|
||||
};
|
||||
|
||||
const handleDelPodcast = (item: any) => {
|
||||
delPodcast(item);
|
||||
playHistoryStore.delPodcast(item);
|
||||
displayList.value = displayList.value.filter((p) => p.id !== item.id);
|
||||
};
|
||||
|
||||
const handleDelPodcastRadio = (item: any) => {
|
||||
delPodcastRadio(item);
|
||||
playHistoryStore.delPodcastRadio(item);
|
||||
displayList.value = displayList.value.filter((r) => r.id !== item.id);
|
||||
};
|
||||
|
||||
@@ -563,28 +555,47 @@ const loadHistoryData = async () => {
|
||||
|
||||
// 根据分类处理不同的数据
|
||||
if (currentCategory.value === 'songs') {
|
||||
// 处理歌曲数据
|
||||
const neteaseItems = currentPageItems.filter((item) => item.source !== 'bilibili');
|
||||
// 区分本地歌曲和网易云歌曲
|
||||
const localItems: any[] = [];
|
||||
const neteaseItems: any[] = [];
|
||||
|
||||
currentPageItems.forEach((item) => {
|
||||
if (item.playMusicUrl?.startsWith('local://') || typeof item.id === 'string') {
|
||||
localItems.push(item);
|
||||
} else if (item.source !== 'bilibili') {
|
||||
neteaseItems.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
// 获取网易云歌曲详情
|
||||
let neteaseSongs: SongResult[] = [];
|
||||
if (neteaseItems.length > 0) {
|
||||
const currentIds = neteaseItems.map((item) => item.id as number);
|
||||
const res = await getMusicDetail(currentIds);
|
||||
if (res.data.songs) {
|
||||
neteaseSongs = res.data.songs.map((song: SongResult) => {
|
||||
const historyItem = neteaseItems.find((item) => item.id === song.id);
|
||||
return {
|
||||
...song,
|
||||
picUrl: song.al?.picUrl || '',
|
||||
count: historyItem?.count || 0,
|
||||
source: 'netease'
|
||||
};
|
||||
});
|
||||
try {
|
||||
const currentIds = neteaseItems.map((item) => item.id as number);
|
||||
const res = await getMusicDetail(currentIds);
|
||||
if (res.data.songs) {
|
||||
neteaseSongs = res.data.songs.map((song: SongResult) => {
|
||||
const historyItem = neteaseItems.find((item) => item.id === song.id);
|
||||
return {
|
||||
...song,
|
||||
picUrl: song.al?.picUrl || '',
|
||||
count: historyItem?.count || 0,
|
||||
source: 'netease'
|
||||
};
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取网易云歌曲详情失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 按原始顺序合并结果
|
||||
const newSongs = currentPageItems
|
||||
.map((item) => {
|
||||
if (item.playMusicUrl?.startsWith('local://') || typeof item.id === 'string') {
|
||||
// 本地歌曲直接使用历史记录中的数据
|
||||
return item as SongResult;
|
||||
}
|
||||
return neteaseSongs.find((song) => song.id === item.id);
|
||||
})
|
||||
.filter((song): song is SongResult => !!song);
|
||||
@@ -660,7 +671,13 @@ onMounted(async () => {
|
||||
|
||||
// 监听历史列表变化,变化时重置并重新加载
|
||||
watch(
|
||||
[musicList, playlistList, albumList, podcastList, podcastRadioList],
|
||||
() => [
|
||||
playHistoryStore.musicHistory,
|
||||
playHistoryStore.playlistHistory,
|
||||
playHistoryStore.albumHistory,
|
||||
playHistoryStore.podcastHistory,
|
||||
playHistoryStore.podcastRadioHistory
|
||||
],
|
||||
async () => {
|
||||
if (hasLoaded.value) {
|
||||
currentPage.value = 1;
|
||||
@@ -673,8 +690,7 @@ watch(
|
||||
|
||||
// 重写删除方法,需要同时更新 displayList
|
||||
const handleDelMusic = async (item: SongResult) => {
|
||||
delMusic(item);
|
||||
musicList.value = musicList.value.filter((music) => music.id !== item.id);
|
||||
playHistoryStore.delMusic(item);
|
||||
displayList.value = displayList.value.filter((music) => music.id !== item.id);
|
||||
};
|
||||
|
||||
|
||||
@@ -181,13 +181,14 @@ const playAlbum = async (album: any) => {
|
||||
const playerCore = usePlayerCoreStore();
|
||||
const playlistStore = usePlaylistStore();
|
||||
|
||||
const albumCover = data.album?.picUrl || album.picUrl;
|
||||
const playlist = data.songs.map((s: any) => ({
|
||||
id: s.id,
|
||||
name: s.name,
|
||||
picUrl: s.al?.picUrl || album.picUrl,
|
||||
source: 'netease',
|
||||
song: s,
|
||||
...s,
|
||||
picUrl: s.al?.picUrl || albumCover,
|
||||
playLoading: false
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<div class="list-item group cursor-pointer" :style="{ animationDelay }" @click="$emit('click')">
|
||||
<div
|
||||
class="home-list-card group cursor-pointer"
|
||||
:style="{ animationDelay }"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<!-- Cover -->
|
||||
<div
|
||||
class="relative aspect-square overflow-hidden rounded-2xl bg-neutral-100 shadow-sm transition-all duration-300 ease-out group-hover:shadow-xl dark:bg-neutral-800"
|
||||
@@ -191,7 +195,7 @@ const extractColor = async () => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.list-item {
|
||||
.home-list-card {
|
||||
animation: itemFadeIn 0.5s ease-out backwards;
|
||||
}
|
||||
|
||||
|
||||
@@ -177,6 +177,14 @@
|
||||
<i class="ri-download-line mr-1" />
|
||||
{{ t('favorite.download', { count: selectedSongs.length }) }}
|
||||
</button>
|
||||
<button
|
||||
class="px-4 py-1.5 rounded-full bg-primary/10 text-primary text-xs font-bold hover:bg-primary/20 transition-all"
|
||||
:disabled="selectedSongs.length === 0"
|
||||
@click="handleAddToPlaylist"
|
||||
>
|
||||
<i class="ri-play-list-add-line mr-1" />
|
||||
{{ t('comp.musicList.addToPlaylist') }}
|
||||
</button>
|
||||
<button
|
||||
class="text-xs text-neutral-400 hover:text-neutral-600"
|
||||
@click="cancelSelect"
|
||||
@@ -271,10 +279,9 @@ import {
|
||||
} from '@/api/music';
|
||||
import PlayBottom from '@/components/common/PlayBottom.vue';
|
||||
import SongItem from '@/components/common/SongItem.vue';
|
||||
import { useAlbumHistory } from '@/hooks/AlbumHistoryHook';
|
||||
import { usePlaylistHistory } from '@/hooks/PlaylistHistoryHook';
|
||||
import { useDownload } from '@/hooks/useDownload';
|
||||
import { useMusicStore, usePlayerStore, useRecommendStore, useUserStore } from '@/store';
|
||||
import { usePlayHistoryStore } from '@/store/modules/playHistory';
|
||||
import { SongResult } from '@/types/music';
|
||||
import { calculateAnimationDelay, getImgUrl, isElectron, isMobile } from '@/utils';
|
||||
import { getLoginErrorMessage, hasPermission } from '@/utils/auth';
|
||||
@@ -290,8 +297,7 @@ const musicStore = useMusicStore();
|
||||
const recommendStore = useRecommendStore();
|
||||
const userStore = useUserStore();
|
||||
const message = useMessage();
|
||||
const { addPlaylist } = usePlaylistHistory();
|
||||
const { addAlbum } = useAlbumHistory();
|
||||
const playHistoryStore = usePlayHistoryStore();
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
@@ -441,9 +447,11 @@ const resetListState = () => {
|
||||
|
||||
const formatSong = (item: any) => {
|
||||
if (!item) return null;
|
||||
// 专辑歌曲的 al.picUrl 可能为空,使用专辑封面兜底
|
||||
const picUrl = item.al?.picUrl || item.picUrl || (isAlbum.value ? getCoverImgUrl.value : '');
|
||||
return {
|
||||
...item,
|
||||
picUrl: item.al?.picUrl || item.picUrl,
|
||||
picUrl,
|
||||
song: {
|
||||
artists: item.ar || item.artists,
|
||||
name: item.name,
|
||||
@@ -595,7 +603,7 @@ const loadMoreSongs = async () => {
|
||||
const saveHistory = () => {
|
||||
if (!listInfo.value?.id) return;
|
||||
if (isAlbum.value) {
|
||||
addAlbum({
|
||||
playHistoryStore.addAlbum({
|
||||
id: listInfo.value.id,
|
||||
name: listInfo.value.name || '',
|
||||
picUrl: getCoverImgUrl.value,
|
||||
@@ -603,7 +611,7 @@ const saveHistory = () => {
|
||||
artist: listInfo.value.artist
|
||||
});
|
||||
} else if (route.query.type === 'playlist') {
|
||||
addPlaylist({
|
||||
playHistoryStore.addPlaylist({
|
||||
id: listInfo.value.id,
|
||||
name: listInfo.value.name || '',
|
||||
coverImgUrl: getCoverImgUrl.value,
|
||||
@@ -680,6 +688,26 @@ const handleBatchDownload = async () => {
|
||||
cancelSelect();
|
||||
};
|
||||
|
||||
const handleAddToPlaylist = () => {
|
||||
const songs = selectedSongs.value
|
||||
.map((id) => filteredSongs.value.find((s) => s.id === id))
|
||||
.filter((s) => s)
|
||||
.map((s) => formatSong(s))
|
||||
.filter((s) => s) as SongResult[];
|
||||
if (songs.length === 0) return;
|
||||
|
||||
const currentList = playerStore.playList;
|
||||
const newSongs = songs.filter((s) => !currentList.some((item) => item.id === s.id));
|
||||
if (newSongs.length === 0) {
|
||||
message.warning(t('comp.musicList.songsAlreadyInPlaylist'));
|
||||
return;
|
||||
}
|
||||
|
||||
playerStore.setPlayList([...currentList, ...newSongs], true);
|
||||
message.success(t('comp.musicList.addToPlaylistSuccess', { count: newSongs.length }));
|
||||
cancelSelect();
|
||||
};
|
||||
|
||||
const toggleLayout = () => {
|
||||
isCompactLayout.value = !isCompactLayout.value;
|
||||
localStorage.setItem('musicListLayout', isCompactLayout.value ? 'compact' : 'normal');
|
||||
|
||||
@@ -298,8 +298,8 @@ import {
|
||||
} from '@/api/podcast';
|
||||
import CategorySelector from '@/components/common/CategorySelector.vue';
|
||||
import RadioCard from '@/components/podcast/RadioCard.vue';
|
||||
import { usePodcastHistory } from '@/hooks/PodcastHistoryHook';
|
||||
import { usePlayerStore, usePlaylistStore, useUserStore } from '@/store';
|
||||
import { usePlayHistoryStore } from '@/store/modules/playHistory';
|
||||
import type { DjCategory, DjProgram, DjRadio } from '@/types/podcast';
|
||||
import { calculateAnimationDelay, formatNumber, getImgUrl, secondToMinute } from '@/utils';
|
||||
import { mapDjProgramToSongResult } from '@/utils/podcastUtils';
|
||||
@@ -316,7 +316,7 @@ const route = useRoute();
|
||||
const playlistStore = usePlaylistStore();
|
||||
const playerStore = usePlayerStore();
|
||||
const userStore = useUserStore();
|
||||
const { podcastList, clearPodcastHistory } = usePodcastHistory();
|
||||
const playHistoryStore = usePlayHistoryStore();
|
||||
|
||||
const contentScrollbarRef = ref();
|
||||
const recommendedSection = ref<HTMLElement | null>(null);
|
||||
@@ -350,7 +350,7 @@ const displayRecentPrograms = computed(() => {
|
||||
if (userStore.user) {
|
||||
return recentPrograms.value.slice(0, 5);
|
||||
}
|
||||
return podcastList.value.slice(0, 5);
|
||||
return playHistoryStore.podcastHistory.slice(0, 5);
|
||||
});
|
||||
|
||||
const subscribedIdSet = computed(() => new Set(subscribedRadios.value.map((radio) => radio.id)));
|
||||
@@ -439,7 +439,7 @@ const scrollToRecommended = async () => {
|
||||
};
|
||||
|
||||
const clearLocalHistory = () => {
|
||||
clearPodcastHistory();
|
||||
playHistoryStore.clearPodcastHistory();
|
||||
};
|
||||
|
||||
const handleSubscribe = async (radio: DjRadio) => {
|
||||
|
||||
Reference in New Issue
Block a user