mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-28 10:57:23 +08:00
refactor: 重构历史记录
This commit is contained in:
@@ -26,7 +26,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import type { AlbumHistoryItem } from '@/hooks/AlbumHistoryHook';
|
import type { AlbumHistoryItem } from '@/store/modules/playHistory';
|
||||||
import { getImgUrl } from '@/utils';
|
import { getImgUrl } from '@/utils';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import type { PlaylistHistoryItem } from '@/hooks/PlaylistHistoryHook';
|
import type { PlaylistHistoryItem } from '@/store/modules/playHistory';
|
||||||
import { getImgUrl } from '@/utils';
|
import { getImgUrl } from '@/utils';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ import { useRouter } from 'vue-router';
|
|||||||
|
|
||||||
import { navigateToMusicList } from '@/components/common/MusicListNavigator';
|
import { navigateToMusicList } from '@/components/common/MusicListNavigator';
|
||||||
import MvPlayer from '@/components/MvPlayer.vue';
|
import MvPlayer from '@/components/MvPlayer.vue';
|
||||||
import { usePodcastRadioHistory } from '@/hooks/PodcastRadioHistoryHook';
|
|
||||||
import { usePlayerStore } from '@/store/modules/player';
|
import { usePlayerStore } from '@/store/modules/player';
|
||||||
|
import { usePlayHistoryStore } from '@/store/modules/playHistory';
|
||||||
import { IMvItem } from '@/types/mv';
|
import { IMvItem } from '@/types/mv';
|
||||||
import { getImgUrl } from '@/utils';
|
import { getImgUrl } from '@/utils';
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ const showPop = ref(false);
|
|||||||
|
|
||||||
const playerStore = usePlayerStore();
|
const playerStore = usePlayerStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { addPodcastRadio } = usePodcastRadioHistory();
|
const playHistoryStore = usePlayHistoryStore();
|
||||||
|
|
||||||
const getCurrentMv = () => {
|
const getCurrentMv = () => {
|
||||||
return {
|
return {
|
||||||
@@ -117,7 +117,7 @@ const handleClick = async () => {
|
|||||||
} else if (props.item.type === 'mv') {
|
} else if (props.item.type === 'mv') {
|
||||||
handleShowMv();
|
handleShowMv();
|
||||||
} else if (props.item.type === 'djRadio') {
|
} else if (props.item.type === 'djRadio') {
|
||||||
addPodcastRadio({
|
playHistoryStore.addPodcastRadio({
|
||||||
id: props.item.id,
|
id: props.item.id,
|
||||||
name: props.item.name,
|
name: props.item.name,
|
||||||
picUrl: props.item.picUrl,
|
picUrl: props.item.picUrl,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { usePodcastHistory } from '@/hooks/PodcastHistoryHook';
|
|
||||||
import { usePlayerStore } from '@/store';
|
import { usePlayerStore } from '@/store';
|
||||||
|
import { usePlayHistoryStore } from '@/store/modules/playHistory';
|
||||||
import type { SongResult } from '@/types/music';
|
import type { SongResult } from '@/types/music';
|
||||||
import type { DjProgram } from '@/types/podcast';
|
import type { DjProgram } from '@/types/podcast';
|
||||||
import { formatNumber, getImgUrl, secondToMinute } from '@/utils';
|
import { formatNumber, getImgUrl, secondToMinute } from '@/utils';
|
||||||
@@ -11,7 +11,7 @@ defineProps<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const playerStore = usePlayerStore();
|
const playerStore = usePlayerStore();
|
||||||
const { addPodcast } = usePodcastHistory();
|
const playHistoryStore = usePlayHistoryStore();
|
||||||
|
|
||||||
const formatDate = (timestamp: number): string => {
|
const formatDate = (timestamp: number): string => {
|
||||||
const date = new Date(timestamp);
|
const date = new Date(timestamp);
|
||||||
@@ -98,7 +98,7 @@ const playProgram = async (program: DjProgram) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await playerStore.setPlay(songData);
|
await playerStore.setPlay(songData);
|
||||||
addPodcast(program);
|
playHistoryStore.addPodcast(program);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('播放节目失败:', error);
|
console.error('播放节目失败:', error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
import { useLocalStorage } from '@vueuse/core';
|
|
||||||
import { ref, watch } from 'vue';
|
|
||||||
|
|
||||||
// 专辑历史记录类型
|
|
||||||
export interface AlbumHistoryItem {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
picUrl?: string;
|
|
||||||
size?: number; // 歌曲数量
|
|
||||||
artist?: {
|
|
||||||
name: string;
|
|
||||||
id: number;
|
|
||||||
};
|
|
||||||
count?: number; // 播放次数
|
|
||||||
lastPlayTime?: number; // 最后播放时间
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useAlbumHistory = () => {
|
|
||||||
const albumHistory = useLocalStorage<AlbumHistoryItem[]>('albumHistory', []);
|
|
||||||
|
|
||||||
const addAlbum = (album: AlbumHistoryItem) => {
|
|
||||||
const index = albumHistory.value.findIndex((item) => item.id === album.id);
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
if (index !== -1) {
|
|
||||||
// 如果已存在,更新播放次数和时间,并移到最前面
|
|
||||||
albumHistory.value[index].count = (albumHistory.value[index].count || 0) + 1;
|
|
||||||
albumHistory.value[index].lastPlayTime = now;
|
|
||||||
albumHistory.value.unshift(albumHistory.value.splice(index, 1)[0]);
|
|
||||||
} else {
|
|
||||||
// 如果不存在,添加新记录
|
|
||||||
albumHistory.value.unshift({
|
|
||||||
...album,
|
|
||||||
count: 1,
|
|
||||||
lastPlayTime: now
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const delAlbum = (album: AlbumHistoryItem) => {
|
|
||||||
const index = albumHistory.value.findIndex((item) => item.id === album.id);
|
|
||||||
if (index !== -1) {
|
|
||||||
albumHistory.value.splice(index, 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const albumList = ref(albumHistory.value);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => albumHistory.value,
|
|
||||||
() => {
|
|
||||||
albumList.value = albumHistory.value;
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
albumHistory,
|
|
||||||
albumList,
|
|
||||||
addAlbum,
|
|
||||||
delAlbum
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import { useLocalStorage } from '@vueuse/core';
|
|
||||||
|
|
||||||
import type { SongResult } from '@/types/music';
|
|
||||||
|
|
||||||
export const useMusicHistory = () => {
|
|
||||||
const musicHistory = useLocalStorage<SongResult[]>('musicHistory', []);
|
|
||||||
|
|
||||||
const addMusic = (music: SongResult) => {
|
|
||||||
const index = musicHistory.value.findIndex((item) => item.id === music.id);
|
|
||||||
if (index !== -1) {
|
|
||||||
musicHistory.value[index].count = (musicHistory.value[index].count || 0) + 1;
|
|
||||||
musicHistory.value.unshift(musicHistory.value.splice(index, 1)[0]);
|
|
||||||
} else {
|
|
||||||
musicHistory.value.unshift({ ...music, count: 1 });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const delMusic = (music: SongResult) => {
|
|
||||||
const index = musicHistory.value.findIndex((item) => item.id === music.id);
|
|
||||||
if (index !== -1) {
|
|
||||||
musicHistory.value.splice(index, 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const musicList = ref(musicHistory.value);
|
|
||||||
watch(
|
|
||||||
() => musicHistory.value,
|
|
||||||
() => {
|
|
||||||
musicList.value = musicHistory.value;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
musicHistory,
|
|
||||||
musicList,
|
|
||||||
addMusic,
|
|
||||||
delMusic
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import { useLocalStorage } from '@vueuse/core';
|
|
||||||
import { ref, watch } from 'vue';
|
|
||||||
|
|
||||||
// 歌单历史记录类型
|
|
||||||
export interface PlaylistHistoryItem {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
coverImgUrl?: string;
|
|
||||||
picUrl?: string; // 兼容字段
|
|
||||||
trackCount?: number;
|
|
||||||
playCount?: number;
|
|
||||||
creator?: {
|
|
||||||
nickname: string;
|
|
||||||
userId: number;
|
|
||||||
};
|
|
||||||
count?: number; // 播放次数
|
|
||||||
lastPlayTime?: number; // 最后播放时间
|
|
||||||
}
|
|
||||||
|
|
||||||
export const usePlaylistHistory = () => {
|
|
||||||
const playlistHistory = useLocalStorage<PlaylistHistoryItem[]>('playlistHistory', []);
|
|
||||||
|
|
||||||
const addPlaylist = (playlist: PlaylistHistoryItem) => {
|
|
||||||
const index = playlistHistory.value.findIndex((item) => item.id === playlist.id);
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
if (index !== -1) {
|
|
||||||
// 如果已存在,更新播放次数和时间,并移到最前面
|
|
||||||
playlistHistory.value[index].count = (playlistHistory.value[index].count || 0) + 1;
|
|
||||||
playlistHistory.value[index].lastPlayTime = now;
|
|
||||||
playlistHistory.value.unshift(playlistHistory.value.splice(index, 1)[0]);
|
|
||||||
} else {
|
|
||||||
// 如果不存在,添加新记录
|
|
||||||
playlistHistory.value.unshift({
|
|
||||||
...playlist,
|
|
||||||
count: 1,
|
|
||||||
lastPlayTime: now
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const delPlaylist = (playlist: PlaylistHistoryItem) => {
|
|
||||||
const index = playlistHistory.value.findIndex((item) => item.id === playlist.id);
|
|
||||||
if (index !== -1) {
|
|
||||||
playlistHistory.value.splice(index, 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const playlistList = ref(playlistHistory.value);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => playlistHistory.value,
|
|
||||||
() => {
|
|
||||||
playlistList.value = playlistHistory.value;
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
playlistHistory,
|
|
||||||
playlistList,
|
|
||||||
addPlaylist,
|
|
||||||
delPlaylist
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import { useLocalStorage } from '@vueuse/core';
|
|
||||||
import { ref, watch } from 'vue';
|
|
||||||
|
|
||||||
import type { DjProgram } from '@/types/podcast';
|
|
||||||
|
|
||||||
export const usePodcastHistory = () => {
|
|
||||||
const podcastHistory = useLocalStorage<DjProgram[]>('podcastHistory', []);
|
|
||||||
|
|
||||||
const addPodcast = (program: DjProgram) => {
|
|
||||||
const index = podcastHistory.value.findIndex((item) => item.id === program.id);
|
|
||||||
if (index !== -1) {
|
|
||||||
podcastHistory.value.unshift(podcastHistory.value.splice(index, 1)[0]);
|
|
||||||
} else {
|
|
||||||
podcastHistory.value.unshift(program);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (podcastHistory.value.length > 100) {
|
|
||||||
podcastHistory.value.pop();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const delPodcast = (program: DjProgram) => {
|
|
||||||
const index = podcastHistory.value.findIndex((item) => item.id === program.id);
|
|
||||||
if (index !== -1) {
|
|
||||||
podcastHistory.value.splice(index, 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const clearPodcastHistory = () => {
|
|
||||||
podcastHistory.value = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
const podcastList = ref(podcastHistory.value);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => podcastHistory.value,
|
|
||||||
() => {
|
|
||||||
podcastList.value = podcastHistory.value;
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
podcastHistory,
|
|
||||||
podcastList,
|
|
||||||
addPodcast,
|
|
||||||
delPodcast,
|
|
||||||
clearPodcastHistory
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import { useLocalStorage } from '@vueuse/core';
|
|
||||||
import { ref, watch } from 'vue';
|
|
||||||
|
|
||||||
export type PodcastRadioHistoryItem = {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
picUrl: string;
|
|
||||||
desc?: string;
|
|
||||||
dj?: {
|
|
||||||
nickname: string;
|
|
||||||
userId: number;
|
|
||||||
};
|
|
||||||
count?: number;
|
|
||||||
lastPlayTime?: number;
|
|
||||||
type?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const usePodcastRadioHistory = () => {
|
|
||||||
const podcastRadioHistory = useLocalStorage<PodcastRadioHistoryItem[]>('podcastRadioHistory', []);
|
|
||||||
|
|
||||||
const addPodcastRadio = (radio: PodcastRadioHistoryItem) => {
|
|
||||||
const index = podcastRadioHistory.value.findIndex((item) => item.id === radio.id);
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
if (index !== -1) {
|
|
||||||
const existing = podcastRadioHistory.value.splice(index, 1)[0];
|
|
||||||
existing.count = (existing.count || 0) + 1;
|
|
||||||
existing.lastPlayTime = now;
|
|
||||||
podcastRadioHistory.value.unshift(existing);
|
|
||||||
} else {
|
|
||||||
podcastRadioHistory.value.unshift({
|
|
||||||
...radio,
|
|
||||||
count: 1,
|
|
||||||
lastPlayTime: now
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (podcastRadioHistory.value.length > 100) {
|
|
||||||
podcastRadioHistory.value.pop();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const delPodcastRadio = (radio: PodcastRadioHistoryItem) => {
|
|
||||||
const index = podcastRadioHistory.value.findIndex((item) => item.id === radio.id);
|
|
||||||
if (index !== -1) {
|
|
||||||
podcastRadioHistory.value.splice(index, 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const podcastRadioList = ref(podcastRadioHistory.value);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => podcastRadioHistory.value,
|
|
||||||
() => {
|
|
||||||
podcastRadioList.value = podcastRadioHistory.value;
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
podcastRadioHistory,
|
|
||||||
podcastRadioList,
|
|
||||||
addPodcastRadio,
|
|
||||||
delPodcastRadio
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,336 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import type { SongResult } from '@/types/music';
|
||||||
|
import type { DjProgram } from '@/types/podcast';
|
||||||
|
|
||||||
|
// ==================== 类型定义 ====================
|
||||||
|
|
||||||
|
// 歌单历史记录
|
||||||
|
export type PlaylistHistoryItem = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
coverImgUrl?: string;
|
||||||
|
picUrl?: string;
|
||||||
|
trackCount?: number;
|
||||||
|
playCount?: number;
|
||||||
|
creator?: {
|
||||||
|
nickname: string;
|
||||||
|
userId: number;
|
||||||
|
};
|
||||||
|
count?: number;
|
||||||
|
lastPlayTime?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 专辑历史记录
|
||||||
|
export type AlbumHistoryItem = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
picUrl?: string;
|
||||||
|
size?: number;
|
||||||
|
artist?: {
|
||||||
|
name: string;
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
count?: number;
|
||||||
|
lastPlayTime?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 播客电台历史记录
|
||||||
|
export type PodcastRadioHistoryItem = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
picUrl: string;
|
||||||
|
desc?: string;
|
||||||
|
dj?: {
|
||||||
|
nickname: string;
|
||||||
|
userId: number;
|
||||||
|
};
|
||||||
|
count?: number;
|
||||||
|
lastPlayTime?: number;
|
||||||
|
type?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 历史记录最大条数
|
||||||
|
const MAX_HISTORY_SIZE = 500;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 播放记录统一管理 Store
|
||||||
|
* 使用 Pinia 单例模式,解决多实例不同步问题
|
||||||
|
* 适配:音乐、播客、本地音乐、歌单、专辑
|
||||||
|
*/
|
||||||
|
export const usePlayHistoryStore = defineStore(
|
||||||
|
'playHistory',
|
||||||
|
() => {
|
||||||
|
// ==================== 状态 ====================
|
||||||
|
const musicHistory = ref<SongResult[]>([]);
|
||||||
|
const podcastHistory = ref<DjProgram[]>([]);
|
||||||
|
const playlistHistory = ref<PlaylistHistoryItem[]>([]);
|
||||||
|
const albumHistory = ref<AlbumHistoryItem[]>([]);
|
||||||
|
const podcastRadioHistory = ref<PodcastRadioHistoryItem[]>([]);
|
||||||
|
|
||||||
|
// ==================== 音乐记录 ====================
|
||||||
|
|
||||||
|
const addMusic = (music: SongResult): void => {
|
||||||
|
const index = musicHistory.value.findIndex((item) => item.id === music.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
musicHistory.value[index].count = (musicHistory.value[index].count || 0) + 1;
|
||||||
|
musicHistory.value.unshift(musicHistory.value.splice(index, 1)[0]);
|
||||||
|
} else {
|
||||||
|
musicHistory.value.unshift({ ...music, count: 1 });
|
||||||
|
}
|
||||||
|
if (musicHistory.value.length > MAX_HISTORY_SIZE) {
|
||||||
|
musicHistory.value.pop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const delMusic = (music: SongResult): void => {
|
||||||
|
const index = musicHistory.value.findIndex((item) => item.id === music.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
musicHistory.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 播客节目记录 ====================
|
||||||
|
|
||||||
|
const addPodcast = (program: DjProgram): void => {
|
||||||
|
const index = podcastHistory.value.findIndex((item) => item.id === program.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
podcastHistory.value.unshift(podcastHistory.value.splice(index, 1)[0]);
|
||||||
|
} else {
|
||||||
|
podcastHistory.value.unshift(program);
|
||||||
|
}
|
||||||
|
if (podcastHistory.value.length > MAX_HISTORY_SIZE) {
|
||||||
|
podcastHistory.value.pop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const delPodcast = (program: DjProgram): void => {
|
||||||
|
const index = podcastHistory.value.findIndex((item) => item.id === program.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
podcastHistory.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 歌单记录 ====================
|
||||||
|
|
||||||
|
const addPlaylist = (playlist: PlaylistHistoryItem): void => {
|
||||||
|
const index = playlistHistory.value.findIndex((item) => item.id === playlist.id);
|
||||||
|
const now = Date.now();
|
||||||
|
if (index !== -1) {
|
||||||
|
playlistHistory.value[index].count = (playlistHistory.value[index].count || 0) + 1;
|
||||||
|
playlistHistory.value[index].lastPlayTime = now;
|
||||||
|
playlistHistory.value.unshift(playlistHistory.value.splice(index, 1)[0]);
|
||||||
|
} else {
|
||||||
|
playlistHistory.value.unshift({ ...playlist, count: 1, lastPlayTime: now });
|
||||||
|
}
|
||||||
|
if (playlistHistory.value.length > MAX_HISTORY_SIZE) {
|
||||||
|
playlistHistory.value.pop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const delPlaylist = (playlist: PlaylistHistoryItem): void => {
|
||||||
|
const index = playlistHistory.value.findIndex((item) => item.id === playlist.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
playlistHistory.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 专辑记录 ====================
|
||||||
|
|
||||||
|
const addAlbum = (album: AlbumHistoryItem): void => {
|
||||||
|
const index = albumHistory.value.findIndex((item) => item.id === album.id);
|
||||||
|
const now = Date.now();
|
||||||
|
if (index !== -1) {
|
||||||
|
albumHistory.value[index].count = (albumHistory.value[index].count || 0) + 1;
|
||||||
|
albumHistory.value[index].lastPlayTime = now;
|
||||||
|
albumHistory.value.unshift(albumHistory.value.splice(index, 1)[0]);
|
||||||
|
} else {
|
||||||
|
albumHistory.value.unshift({ ...album, count: 1, lastPlayTime: now });
|
||||||
|
}
|
||||||
|
if (albumHistory.value.length > MAX_HISTORY_SIZE) {
|
||||||
|
albumHistory.value.pop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const delAlbum = (album: AlbumHistoryItem): void => {
|
||||||
|
const index = albumHistory.value.findIndex((item) => item.id === album.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
albumHistory.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 播客电台记录 ====================
|
||||||
|
|
||||||
|
const addPodcastRadio = (radio: PodcastRadioHistoryItem): void => {
|
||||||
|
const index = podcastRadioHistory.value.findIndex((item) => item.id === radio.id);
|
||||||
|
const now = Date.now();
|
||||||
|
if (index !== -1) {
|
||||||
|
const existing = podcastRadioHistory.value.splice(index, 1)[0];
|
||||||
|
existing.count = (existing.count || 0) + 1;
|
||||||
|
existing.lastPlayTime = now;
|
||||||
|
podcastRadioHistory.value.unshift(existing);
|
||||||
|
} else {
|
||||||
|
podcastRadioHistory.value.unshift({ ...radio, count: 1, lastPlayTime: now });
|
||||||
|
}
|
||||||
|
if (podcastRadioHistory.value.length > MAX_HISTORY_SIZE) {
|
||||||
|
podcastRadioHistory.value.pop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const delPodcastRadio = (radio: PodcastRadioHistoryItem): void => {
|
||||||
|
const index = podcastRadioHistory.value.findIndex((item) => item.id === radio.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
podcastRadioHistory.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 清空操作 ====================
|
||||||
|
|
||||||
|
const clearMusicHistory = (): void => {
|
||||||
|
musicHistory.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearPodcastHistory = (): void => {
|
||||||
|
podcastHistory.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearPlaylistHistory = (): void => {
|
||||||
|
playlistHistory.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearAlbumHistory = (): void => {
|
||||||
|
albumHistory.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearPodcastRadioHistory = (): void => {
|
||||||
|
podcastRadioHistory.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearAll = (): void => {
|
||||||
|
clearMusicHistory();
|
||||||
|
clearPodcastHistory();
|
||||||
|
clearPlaylistHistory();
|
||||||
|
clearAlbumHistory();
|
||||||
|
clearPodcastRadioHistory();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 数据迁移 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从旧的 localStorage 数据迁移到 Pinia store
|
||||||
|
* 只在首次启动时执行一次
|
||||||
|
*/
|
||||||
|
const migrateFromLocalStorage = (): void => {
|
||||||
|
const migrated = localStorage.getItem('playHistory-migrated');
|
||||||
|
if (migrated) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 迁移音乐记录
|
||||||
|
const oldMusic = localStorage.getItem('musicHistory');
|
||||||
|
if (oldMusic) {
|
||||||
|
const parsed = JSON.parse(oldMusic);
|
||||||
|
if (Array.isArray(parsed) && parsed.length > 0 && musicHistory.value.length === 0) {
|
||||||
|
musicHistory.value = parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 迁移播客记录
|
||||||
|
const oldPodcast = localStorage.getItem('podcastHistory');
|
||||||
|
if (oldPodcast) {
|
||||||
|
const parsed = JSON.parse(oldPodcast);
|
||||||
|
if (Array.isArray(parsed) && parsed.length > 0 && podcastHistory.value.length === 0) {
|
||||||
|
podcastHistory.value = parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 迁移歌单记录
|
||||||
|
const oldPlaylist = localStorage.getItem('playlistHistory');
|
||||||
|
if (oldPlaylist) {
|
||||||
|
const parsed = JSON.parse(oldPlaylist);
|
||||||
|
if (Array.isArray(parsed) && parsed.length > 0 && playlistHistory.value.length === 0) {
|
||||||
|
playlistHistory.value = parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 迁移专辑记录
|
||||||
|
const oldAlbum = localStorage.getItem('albumHistory');
|
||||||
|
if (oldAlbum) {
|
||||||
|
const parsed = JSON.parse(oldAlbum);
|
||||||
|
if (Array.isArray(parsed) && parsed.length > 0 && albumHistory.value.length === 0) {
|
||||||
|
albumHistory.value = parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 迁移播客电台记录
|
||||||
|
const oldRadio = localStorage.getItem('podcastRadioHistory');
|
||||||
|
if (oldRadio) {
|
||||||
|
const parsed = JSON.parse(oldRadio);
|
||||||
|
if (
|
||||||
|
Array.isArray(parsed) &&
|
||||||
|
parsed.length > 0 &&
|
||||||
|
podcastRadioHistory.value.length === 0
|
||||||
|
) {
|
||||||
|
podcastRadioHistory.value = parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('playHistory-migrated', '1');
|
||||||
|
console.log('[PlayHistory] 数据迁移完成');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[PlayHistory] 数据迁移失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 状态
|
||||||
|
musicHistory,
|
||||||
|
podcastHistory,
|
||||||
|
playlistHistory,
|
||||||
|
albumHistory,
|
||||||
|
podcastRadioHistory,
|
||||||
|
|
||||||
|
// 音乐
|
||||||
|
addMusic,
|
||||||
|
delMusic,
|
||||||
|
clearMusicHistory,
|
||||||
|
|
||||||
|
// 播客节目
|
||||||
|
addPodcast,
|
||||||
|
delPodcast,
|
||||||
|
clearPodcastHistory,
|
||||||
|
|
||||||
|
// 歌单
|
||||||
|
addPlaylist,
|
||||||
|
delPlaylist,
|
||||||
|
clearPlaylistHistory,
|
||||||
|
|
||||||
|
// 专辑
|
||||||
|
addAlbum,
|
||||||
|
delAlbum,
|
||||||
|
clearAlbumHistory,
|
||||||
|
|
||||||
|
// 播客电台
|
||||||
|
addPodcastRadio,
|
||||||
|
delPodcastRadio,
|
||||||
|
clearPodcastRadioHistory,
|
||||||
|
|
||||||
|
// 通用
|
||||||
|
clearAll,
|
||||||
|
migrateFromLocalStorage
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
persist: {
|
||||||
|
key: 'play-history-store',
|
||||||
|
storage: localStorage,
|
||||||
|
pick: [
|
||||||
|
'musicHistory',
|
||||||
|
'podcastHistory',
|
||||||
|
'playlistHistory',
|
||||||
|
'albumHistory',
|
||||||
|
'podcastRadioHistory'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -13,6 +13,7 @@ import { computed } from 'vue';
|
|||||||
import { useFavoriteStore } from './favorite';
|
import { useFavoriteStore } from './favorite';
|
||||||
import { useIntelligenceModeStore } from './intelligenceMode';
|
import { useIntelligenceModeStore } from './intelligenceMode';
|
||||||
import { usePlayerCoreStore } from './playerCore';
|
import { usePlayerCoreStore } from './playerCore';
|
||||||
|
import { usePlayHistoryStore } from './playHistory';
|
||||||
import { usePlaylistStore } from './playlist';
|
import { usePlaylistStore } from './playlist';
|
||||||
import { type SleepTimerInfo, SleepTimerType, useSleepTimerStore } from './sleepTimer';
|
import { type SleepTimerInfo, SleepTimerType, useSleepTimerStore } from './sleepTimer';
|
||||||
|
|
||||||
@@ -61,6 +62,10 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
* 初始化播放状态(从 localStorage 恢复)
|
* 初始化播放状态(从 localStorage 恢复)
|
||||||
*/
|
*/
|
||||||
const initializePlayState = async () => {
|
const initializePlayState = async () => {
|
||||||
|
// 从旧的 localStorage 迁移播放记录到 Pinia store
|
||||||
|
const playHistoryStore = usePlayHistoryStore();
|
||||||
|
playHistoryStore.migrateFromLocalStorage();
|
||||||
|
|
||||||
await playerCore.initializePlayState();
|
await playerCore.initializePlayState();
|
||||||
await playlist.initializePlaylist();
|
await playlist.initializePlaylist();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import { computed, ref } from 'vue';
|
|||||||
|
|
||||||
import i18n from '@/../i18n/renderer';
|
import i18n from '@/../i18n/renderer';
|
||||||
import { getParsingMusicUrl } from '@/api/music';
|
import { getParsingMusicUrl } from '@/api/music';
|
||||||
import { useMusicHistory } from '@/hooks/MusicHistoryHook';
|
|
||||||
import { usePodcastHistory } from '@/hooks/PodcastHistoryHook';
|
|
||||||
import { useLyrics, useSongDetail } from '@/hooks/usePlayerHooks';
|
import { useLyrics, useSongDetail } from '@/hooks/usePlayerHooks';
|
||||||
import { audioService } from '@/services/audioService';
|
import { audioService } from '@/services/audioService';
|
||||||
import { playbackRequestManager } from '@/services/playbackRequestManager';
|
import { playbackRequestManager } from '@/services/playbackRequestManager';
|
||||||
@@ -17,8 +15,8 @@ import type { Platform, SongResult } from '@/types/music';
|
|||||||
import { getImgUrl } from '@/utils';
|
import { getImgUrl } from '@/utils';
|
||||||
import { getImageLinearBackground } from '@/utils/linearColor';
|
import { getImageLinearBackground } from '@/utils/linearColor';
|
||||||
|
|
||||||
const musicHistory = useMusicHistory();
|
import { usePlayHistoryStore } from './playHistory';
|
||||||
const podcastHistory = usePodcastHistory();
|
|
||||||
const { message } = createDiscreteApi(['message']);
|
const { message } = createDiscreteApi(['message']);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -243,6 +241,7 @@ export const usePlayerCoreStore = defineStore(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const originalMusic = { ...music };
|
const originalMusic = { ...music };
|
||||||
|
|
||||||
const { loadLrc } = useLyrics();
|
const { loadLrc } = useLyrics();
|
||||||
const { getSongDetail } = useSongDetail();
|
const { getSongDetail } = useSongDetail();
|
||||||
|
|
||||||
@@ -290,12 +289,13 @@ export const usePlayerCoreStore = defineStore(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 添加到历史记录
|
// 添加到历史记录
|
||||||
|
const playHistoryStore = usePlayHistoryStore();
|
||||||
if (music.isPodcast) {
|
if (music.isPodcast) {
|
||||||
if (music.program) {
|
if (music.program) {
|
||||||
podcastHistory.addPodcast(music.program);
|
playHistoryStore.addPodcast(music.program);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
musicHistory.addMusic(music);
|
playHistoryStore.addMusic(music);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取歌曲详情
|
// 获取歌曲详情
|
||||||
|
|||||||
@@ -240,13 +240,14 @@ const playAlbum = async (album: any) => {
|
|||||||
const playerCore = usePlayerCoreStore();
|
const playerCore = usePlayerCoreStore();
|
||||||
const playlistStore = usePlaylistStore();
|
const playlistStore = usePlaylistStore();
|
||||||
|
|
||||||
|
const albumCover = data.album?.picUrl || album.picUrl;
|
||||||
const playlist = data.songs.map((s: any) => ({
|
const playlist = data.songs.map((s: any) => ({
|
||||||
id: s.id,
|
id: s.id,
|
||||||
name: s.name,
|
name: s.name,
|
||||||
picUrl: s.al?.picUrl || album.picUrl,
|
|
||||||
source: 'netease',
|
source: 'netease',
|
||||||
song: s,
|
song: s,
|
||||||
...s,
|
...s,
|
||||||
|
picUrl: s.al?.picUrl || albumCover,
|
||||||
playLoading: false
|
playLoading: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -148,13 +148,13 @@
|
|||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import { useMusicHistory } from '@/hooks/MusicHistoryHook';
|
|
||||||
import { usePlayerStore } from '@/store/modules/player';
|
import { usePlayerStore } from '@/store/modules/player';
|
||||||
|
import { usePlayHistoryStore } from '@/store/modules/playHistory';
|
||||||
import type { SongResult } from '@/types/music';
|
import type { SongResult } from '@/types/music';
|
||||||
import { setAnimationClass } from '@/utils';
|
import { setAnimationClass } from '@/utils';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { musicList } = useMusicHistory();
|
const playHistoryStore = usePlayHistoryStore();
|
||||||
const playerStore = usePlayerStore();
|
const playerStore = usePlayerStore();
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
|
|
||||||
@@ -220,7 +220,7 @@ const processHistoryData = () => {
|
|||||||
const oneYearAgo = Date.now() - 365 * 24 * 60 * 60 * 1000;
|
const oneYearAgo = Date.now() - 365 * 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
// 遍历音乐历史记录
|
// 遍历音乐历史记录
|
||||||
musicList.value.forEach((music: SongResult & { count?: number }) => {
|
playHistoryStore.musicHistory.forEach((music: SongResult & { count?: number }) => {
|
||||||
// 假设每次播放都记录在当前时间,我们根据 count 分散到最近的日期
|
// 假设每次播放都记录在当前时间,我们根据 count 分散到最近的日期
|
||||||
const playCount = music.count || 1;
|
const playCount = music.count || 1;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@@ -300,14 +300,14 @@ const mostPlayedSong = computed<{
|
|||||||
artist: string;
|
artist: string;
|
||||||
playCount: number;
|
playCount: number;
|
||||||
} | null>(() => {
|
} | null>(() => {
|
||||||
if (musicList.value.length === 0) return null;
|
if (playHistoryStore.musicHistory.length === 0) return null;
|
||||||
|
|
||||||
const songPlayCounts = new Map<
|
const songPlayCounts = new Map<
|
||||||
string | number,
|
string | number,
|
||||||
{ id: string | number; name: string; artist: string; playCount: 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 id = music.id;
|
||||||
const count = music.count || 1;
|
const count = music.count || 1;
|
||||||
const name = music.name || 'Unknown';
|
const name = music.name || 'Unknown';
|
||||||
@@ -365,14 +365,17 @@ const latestNightSong = computed<{
|
|||||||
artist: string;
|
artist: string;
|
||||||
time: string;
|
time: string;
|
||||||
} | null>(() => {
|
} | 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) {
|
if (nightSongs.length === 0 && playHistoryStore.musicHistory.length > 0) {
|
||||||
const randomSong = musicList.value[Math.floor(Math.random() * musicList.value.length)];
|
const randomSong =
|
||||||
|
playHistoryStore.musicHistory[
|
||||||
|
Math.floor(Math.random() * playHistoryStore.musicHistory.length)
|
||||||
|
];
|
||||||
const randomHour = Math.floor(Math.random() * 6); // 0-5点
|
const randomHour = Math.floor(Math.random() * 6); // 0-5点
|
||||||
const randomMinute = Math.floor(Math.random() * 60);
|
const randomMinute = Math.floor(Math.random() * 60);
|
||||||
|
|
||||||
@@ -402,7 +405,7 @@ const latestNightSong = computed<{
|
|||||||
|
|
||||||
// 播放歌曲
|
// 播放歌曲
|
||||||
const handlePlaySong = async (songId: string | number) => {
|
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) {
|
if (song) {
|
||||||
await playerStore.setPlay(song);
|
await playerStore.setPlay(song);
|
||||||
playerStore.setPlayMusic(true);
|
playerStore.setPlayMusic(true);
|
||||||
|
|||||||
@@ -266,12 +266,8 @@ import AlbumItem from '@/components/common/AlbumItem.vue';
|
|||||||
import { navigateToMusicList } from '@/components/common/MusicListNavigator';
|
import { navigateToMusicList } from '@/components/common/MusicListNavigator';
|
||||||
import PlaylistItem from '@/components/common/PlaylistItem.vue';
|
import PlaylistItem from '@/components/common/PlaylistItem.vue';
|
||||||
import SongItem from '@/components/common/SongItem.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 { usePlayerStore } from '@/store/modules/player';
|
||||||
|
import { usePlayHistoryStore } from '@/store/modules/playHistory';
|
||||||
import { useUserStore } from '@/store/modules/user';
|
import { useUserStore } from '@/store/modules/user';
|
||||||
import type { SongResult } from '@/types/music';
|
import type { SongResult } from '@/types/music';
|
||||||
import { isMobile, setAnimationClass, setAnimationDelay } from '@/utils';
|
import { isMobile, setAnimationClass, setAnimationDelay } from '@/utils';
|
||||||
@@ -291,11 +287,7 @@ interface HistoryRecord extends Partial<SongResult> {
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { delMusic, musicList } = useMusicHistory();
|
const playHistoryStore = usePlayHistoryStore();
|
||||||
const { delPlaylist, playlistList } = usePlaylistHistory();
|
|
||||||
const { delAlbum, albumList } = useAlbumHistory();
|
|
||||||
const { delPodcast, podcastList } = usePodcastHistory();
|
|
||||||
const { delPodcastRadio, podcastRadioList } = usePodcastRadioHistory();
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const scrollbarRef = ref();
|
const scrollbarRef = ref();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
@@ -407,29 +399,29 @@ const getCurrentList = (): any[] => {
|
|||||||
if (currentCategory.value === 'songs') {
|
if (currentCategory.value === 'songs') {
|
||||||
switch (currentTab.value) {
|
switch (currentTab.value) {
|
||||||
case 'local':
|
case 'local':
|
||||||
return musicList.value;
|
return playHistoryStore.musicHistory;
|
||||||
case 'cloud':
|
case 'cloud':
|
||||||
return cloudRecords.value.filter((item) => item.id);
|
return cloudRecords.value.filter((item) => item.id);
|
||||||
}
|
}
|
||||||
} else if (currentCategory.value === 'playlists') {
|
} else if (currentCategory.value === 'playlists') {
|
||||||
switch (currentTab.value) {
|
switch (currentTab.value) {
|
||||||
case 'local':
|
case 'local':
|
||||||
return playlistList.value;
|
return playHistoryStore.playlistHistory;
|
||||||
case 'cloud':
|
case 'cloud':
|
||||||
return cloudPlaylists.value;
|
return cloudPlaylists.value;
|
||||||
}
|
}
|
||||||
} else if (currentCategory.value === 'albums') {
|
} else if (currentCategory.value === 'albums') {
|
||||||
switch (currentTab.value) {
|
switch (currentTab.value) {
|
||||||
case 'local':
|
case 'local':
|
||||||
return albumList.value;
|
return playHistoryStore.albumHistory;
|
||||||
case 'cloud':
|
case 'cloud':
|
||||||
return cloudAlbums.value;
|
return cloudAlbums.value;
|
||||||
}
|
}
|
||||||
} else if (currentCategory.value === 'podcasts') {
|
} else if (currentCategory.value === 'podcasts') {
|
||||||
if (currentPodcastSubTab.value === 'episodes') {
|
if (currentPodcastSubTab.value === 'episodes') {
|
||||||
return podcastList.value;
|
return playHistoryStore.podcastHistory;
|
||||||
} else {
|
} else {
|
||||||
return podcastRadioList.value;
|
return playHistoryStore.podcastRadioHistory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
@@ -499,13 +491,13 @@ const handleAlbumClick = async (item: any) => {
|
|||||||
|
|
||||||
// 删除歌单记录
|
// 删除歌单记录
|
||||||
const handleDelPlaylist = (item: any) => {
|
const handleDelPlaylist = (item: any) => {
|
||||||
delPlaylist(item);
|
playHistoryStore.delPlaylist(item);
|
||||||
displayList.value = displayList.value.filter((playlist) => playlist.id !== item.id);
|
displayList.value = displayList.value.filter((playlist) => playlist.id !== item.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 删除专辑记录
|
// 删除专辑记录
|
||||||
const handleDelAlbum = (item: any) => {
|
const handleDelAlbum = (item: any) => {
|
||||||
delAlbum(item);
|
playHistoryStore.delAlbum(item);
|
||||||
displayList.value = displayList.value.filter((album) => album.id !== item.id);
|
displayList.value = displayList.value.filter((album) => album.id !== item.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -538,12 +530,12 @@ const handlePodcastRadioClick = (item: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDelPodcast = (item: any) => {
|
const handleDelPodcast = (item: any) => {
|
||||||
delPodcast(item);
|
playHistoryStore.delPodcast(item);
|
||||||
displayList.value = displayList.value.filter((p) => p.id !== item.id);
|
displayList.value = displayList.value.filter((p) => p.id !== item.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelPodcastRadio = (item: any) => {
|
const handleDelPodcastRadio = (item: any) => {
|
||||||
delPodcastRadio(item);
|
playHistoryStore.delPodcastRadio(item);
|
||||||
displayList.value = displayList.value.filter((r) => r.id !== item.id);
|
displayList.value = displayList.value.filter((r) => r.id !== item.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -563,28 +555,47 @@ const loadHistoryData = async () => {
|
|||||||
|
|
||||||
// 根据分类处理不同的数据
|
// 根据分类处理不同的数据
|
||||||
if (currentCategory.value === 'songs') {
|
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[] = [];
|
let neteaseSongs: SongResult[] = [];
|
||||||
if (neteaseItems.length > 0) {
|
if (neteaseItems.length > 0) {
|
||||||
const currentIds = neteaseItems.map((item) => item.id as number);
|
try {
|
||||||
const res = await getMusicDetail(currentIds);
|
const currentIds = neteaseItems.map((item) => item.id as number);
|
||||||
if (res.data.songs) {
|
const res = await getMusicDetail(currentIds);
|
||||||
neteaseSongs = res.data.songs.map((song: SongResult) => {
|
if (res.data.songs) {
|
||||||
const historyItem = neteaseItems.find((item) => item.id === song.id);
|
neteaseSongs = res.data.songs.map((song: SongResult) => {
|
||||||
return {
|
const historyItem = neteaseItems.find((item) => item.id === song.id);
|
||||||
...song,
|
return {
|
||||||
picUrl: song.al?.picUrl || '',
|
...song,
|
||||||
count: historyItem?.count || 0,
|
picUrl: song.al?.picUrl || '',
|
||||||
source: 'netease'
|
count: historyItem?.count || 0,
|
||||||
};
|
source: 'netease'
|
||||||
});
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取网易云歌曲详情失败:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 按原始顺序合并结果
|
||||||
const newSongs = currentPageItems
|
const newSongs = currentPageItems
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
|
if (item.playMusicUrl?.startsWith('local://') || typeof item.id === 'string') {
|
||||||
|
// 本地歌曲直接使用历史记录中的数据
|
||||||
|
return item as SongResult;
|
||||||
|
}
|
||||||
return neteaseSongs.find((song) => song.id === item.id);
|
return neteaseSongs.find((song) => song.id === item.id);
|
||||||
})
|
})
|
||||||
.filter((song): song is SongResult => !!song);
|
.filter((song): song is SongResult => !!song);
|
||||||
@@ -660,7 +671,13 @@ onMounted(async () => {
|
|||||||
|
|
||||||
// 监听历史列表变化,变化时重置并重新加载
|
// 监听历史列表变化,变化时重置并重新加载
|
||||||
watch(
|
watch(
|
||||||
[musicList, playlistList, albumList, podcastList, podcastRadioList],
|
() => [
|
||||||
|
playHistoryStore.musicHistory,
|
||||||
|
playHistoryStore.playlistHistory,
|
||||||
|
playHistoryStore.albumHistory,
|
||||||
|
playHistoryStore.podcastHistory,
|
||||||
|
playHistoryStore.podcastRadioHistory
|
||||||
|
],
|
||||||
async () => {
|
async () => {
|
||||||
if (hasLoaded.value) {
|
if (hasLoaded.value) {
|
||||||
currentPage.value = 1;
|
currentPage.value = 1;
|
||||||
@@ -673,8 +690,7 @@ watch(
|
|||||||
|
|
||||||
// 重写删除方法,需要同时更新 displayList
|
// 重写删除方法,需要同时更新 displayList
|
||||||
const handleDelMusic = async (item: SongResult) => {
|
const handleDelMusic = async (item: SongResult) => {
|
||||||
delMusic(item);
|
playHistoryStore.delMusic(item);
|
||||||
musicList.value = musicList.value.filter((music) => music.id !== item.id);
|
|
||||||
displayList.value = displayList.value.filter((music) => music.id !== item.id);
|
displayList.value = displayList.value.filter((music) => music.id !== item.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -181,13 +181,14 @@ const playAlbum = async (album: any) => {
|
|||||||
const playerCore = usePlayerCoreStore();
|
const playerCore = usePlayerCoreStore();
|
||||||
const playlistStore = usePlaylistStore();
|
const playlistStore = usePlaylistStore();
|
||||||
|
|
||||||
|
const albumCover = data.album?.picUrl || album.picUrl;
|
||||||
const playlist = data.songs.map((s: any) => ({
|
const playlist = data.songs.map((s: any) => ({
|
||||||
id: s.id,
|
id: s.id,
|
||||||
name: s.name,
|
name: s.name,
|
||||||
picUrl: s.al?.picUrl || album.picUrl,
|
|
||||||
source: 'netease',
|
source: 'netease',
|
||||||
song: s,
|
song: s,
|
||||||
...s,
|
...s,
|
||||||
|
picUrl: s.al?.picUrl || albumCover,
|
||||||
playLoading: false
|
playLoading: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
<template>
|
<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 -->
|
<!-- Cover -->
|
||||||
<div
|
<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"
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.list-item {
|
.home-list-card {
|
||||||
animation: itemFadeIn 0.5s ease-out backwards;
|
animation: itemFadeIn 0.5s ease-out backwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -177,6 +177,14 @@
|
|||||||
<i class="ri-download-line mr-1" />
|
<i class="ri-download-line mr-1" />
|
||||||
{{ t('favorite.download', { count: selectedSongs.length }) }}
|
{{ t('favorite.download', { count: selectedSongs.length }) }}
|
||||||
</button>
|
</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
|
<button
|
||||||
class="text-xs text-neutral-400 hover:text-neutral-600"
|
class="text-xs text-neutral-400 hover:text-neutral-600"
|
||||||
@click="cancelSelect"
|
@click="cancelSelect"
|
||||||
@@ -271,10 +279,9 @@ import {
|
|||||||
} from '@/api/music';
|
} from '@/api/music';
|
||||||
import PlayBottom from '@/components/common/PlayBottom.vue';
|
import PlayBottom from '@/components/common/PlayBottom.vue';
|
||||||
import SongItem from '@/components/common/SongItem.vue';
|
import SongItem from '@/components/common/SongItem.vue';
|
||||||
import { useAlbumHistory } from '@/hooks/AlbumHistoryHook';
|
|
||||||
import { usePlaylistHistory } from '@/hooks/PlaylistHistoryHook';
|
|
||||||
import { useDownload } from '@/hooks/useDownload';
|
import { useDownload } from '@/hooks/useDownload';
|
||||||
import { useMusicStore, usePlayerStore, useRecommendStore, useUserStore } from '@/store';
|
import { useMusicStore, usePlayerStore, useRecommendStore, useUserStore } from '@/store';
|
||||||
|
import { usePlayHistoryStore } from '@/store/modules/playHistory';
|
||||||
import { SongResult } from '@/types/music';
|
import { SongResult } from '@/types/music';
|
||||||
import { calculateAnimationDelay, getImgUrl, isElectron, isMobile } from '@/utils';
|
import { calculateAnimationDelay, getImgUrl, isElectron, isMobile } from '@/utils';
|
||||||
import { getLoginErrorMessage, hasPermission } from '@/utils/auth';
|
import { getLoginErrorMessage, hasPermission } from '@/utils/auth';
|
||||||
@@ -290,8 +297,7 @@ const musicStore = useMusicStore();
|
|||||||
const recommendStore = useRecommendStore();
|
const recommendStore = useRecommendStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
const { addPlaylist } = usePlaylistHistory();
|
const playHistoryStore = usePlayHistoryStore();
|
||||||
const { addAlbum } = useAlbumHistory();
|
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
@@ -441,9 +447,11 @@ const resetListState = () => {
|
|||||||
|
|
||||||
const formatSong = (item: any) => {
|
const formatSong = (item: any) => {
|
||||||
if (!item) return null;
|
if (!item) return null;
|
||||||
|
// 专辑歌曲的 al.picUrl 可能为空,使用专辑封面兜底
|
||||||
|
const picUrl = item.al?.picUrl || item.picUrl || (isAlbum.value ? getCoverImgUrl.value : '');
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
picUrl: item.al?.picUrl || item.picUrl,
|
picUrl,
|
||||||
song: {
|
song: {
|
||||||
artists: item.ar || item.artists,
|
artists: item.ar || item.artists,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
@@ -595,7 +603,7 @@ const loadMoreSongs = async () => {
|
|||||||
const saveHistory = () => {
|
const saveHistory = () => {
|
||||||
if (!listInfo.value?.id) return;
|
if (!listInfo.value?.id) return;
|
||||||
if (isAlbum.value) {
|
if (isAlbum.value) {
|
||||||
addAlbum({
|
playHistoryStore.addAlbum({
|
||||||
id: listInfo.value.id,
|
id: listInfo.value.id,
|
||||||
name: listInfo.value.name || '',
|
name: listInfo.value.name || '',
|
||||||
picUrl: getCoverImgUrl.value,
|
picUrl: getCoverImgUrl.value,
|
||||||
@@ -603,7 +611,7 @@ const saveHistory = () => {
|
|||||||
artist: listInfo.value.artist
|
artist: listInfo.value.artist
|
||||||
});
|
});
|
||||||
} else if (route.query.type === 'playlist') {
|
} else if (route.query.type === 'playlist') {
|
||||||
addPlaylist({
|
playHistoryStore.addPlaylist({
|
||||||
id: listInfo.value.id,
|
id: listInfo.value.id,
|
||||||
name: listInfo.value.name || '',
|
name: listInfo.value.name || '',
|
||||||
coverImgUrl: getCoverImgUrl.value,
|
coverImgUrl: getCoverImgUrl.value,
|
||||||
@@ -680,6 +688,26 @@ const handleBatchDownload = async () => {
|
|||||||
cancelSelect();
|
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 = () => {
|
const toggleLayout = () => {
|
||||||
isCompactLayout.value = !isCompactLayout.value;
|
isCompactLayout.value = !isCompactLayout.value;
|
||||||
localStorage.setItem('musicListLayout', isCompactLayout.value ? 'compact' : 'normal');
|
localStorage.setItem('musicListLayout', isCompactLayout.value ? 'compact' : 'normal');
|
||||||
|
|||||||
@@ -298,8 +298,8 @@ import {
|
|||||||
} from '@/api/podcast';
|
} from '@/api/podcast';
|
||||||
import CategorySelector from '@/components/common/CategorySelector.vue';
|
import CategorySelector from '@/components/common/CategorySelector.vue';
|
||||||
import RadioCard from '@/components/podcast/RadioCard.vue';
|
import RadioCard from '@/components/podcast/RadioCard.vue';
|
||||||
import { usePodcastHistory } from '@/hooks/PodcastHistoryHook';
|
|
||||||
import { usePlayerStore, usePlaylistStore, useUserStore } from '@/store';
|
import { usePlayerStore, usePlaylistStore, useUserStore } from '@/store';
|
||||||
|
import { usePlayHistoryStore } from '@/store/modules/playHistory';
|
||||||
import type { DjCategory, DjProgram, DjRadio } from '@/types/podcast';
|
import type { DjCategory, DjProgram, DjRadio } from '@/types/podcast';
|
||||||
import { calculateAnimationDelay, formatNumber, getImgUrl, secondToMinute } from '@/utils';
|
import { calculateAnimationDelay, formatNumber, getImgUrl, secondToMinute } from '@/utils';
|
||||||
import { mapDjProgramToSongResult } from '@/utils/podcastUtils';
|
import { mapDjProgramToSongResult } from '@/utils/podcastUtils';
|
||||||
@@ -316,7 +316,7 @@ const route = useRoute();
|
|||||||
const playlistStore = usePlaylistStore();
|
const playlistStore = usePlaylistStore();
|
||||||
const playerStore = usePlayerStore();
|
const playerStore = usePlayerStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const { podcastList, clearPodcastHistory } = usePodcastHistory();
|
const playHistoryStore = usePlayHistoryStore();
|
||||||
|
|
||||||
const contentScrollbarRef = ref();
|
const contentScrollbarRef = ref();
|
||||||
const recommendedSection = ref<HTMLElement | null>(null);
|
const recommendedSection = ref<HTMLElement | null>(null);
|
||||||
@@ -350,7 +350,7 @@ const displayRecentPrograms = computed(() => {
|
|||||||
if (userStore.user) {
|
if (userStore.user) {
|
||||||
return recentPrograms.value.slice(0, 5);
|
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)));
|
const subscribedIdSet = computed(() => new Set(subscribedRadios.value.map((radio) => radio.id)));
|
||||||
@@ -439,7 +439,7 @@ const scrollToRecommended = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const clearLocalHistory = () => {
|
const clearLocalHistory = () => {
|
||||||
clearPodcastHistory();
|
playHistoryStore.clearPodcastHistory();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubscribe = async (radio: DjRadio) => {
|
const handleSubscribe = async (radio: DjRadio) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user