diff --git a/src/renderer/components/common/MusicListNavigator.ts b/src/renderer/components/common/MusicListNavigator.ts index f5fef27..bb4a64b 100644 --- a/src/renderer/components/common/MusicListNavigator.ts +++ b/src/renderer/components/common/MusicListNavigator.ts @@ -18,22 +18,28 @@ export function navigateToMusicList( canRemove?: boolean; } ) { - const musicStore = useMusicStore(); - const { id, type, name, songList, listInfo, canRemove = false } = options; + const musicStore = useMusicStore(); + const { id, type, name, songList, listInfo, canRemove = false } = options; - // 保存数据到状态管理 - musicStore.setCurrentMusicList(songList, name, listInfo, canRemove); + // 如果是每日推荐,不需要设置 musicStore,直接从 recommendStore 获取 + if (type !== 'dailyRecommend') { + musicStore.setCurrentMusicList(songList, name, listInfo, canRemove); + } else { + // 确保 musicStore 的数据被清空,避免显示旧的列表 + musicStore.clearCurrentMusicList(); + } - // 路由跳转 - if (id) { + // 路由跳转 + if (id) { router.push({ name: 'musicList', params: { id }, query: { type } }); - } else { + } else { router.push({ - name: 'musicList' + name: 'musicList', + query: { type: 'dailyRecommend' } }); - } + } } diff --git a/src/renderer/components/home/TopBanner.vue b/src/renderer/components/home/TopBanner.vue index 92325e7..89dce4e 100644 --- a/src/renderer/components/home/TopBanner.vue +++ b/src/renderer/components/home/TopBanner.vue @@ -130,14 +130,13 @@ import { computed, onMounted, ref, watchEffect } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRouter } from 'vue-router'; -import { getDayRecommend, getHotSinger } from '@/api/home'; +import { getHotSinger } from '@/api/home'; import { getListDetail } from '@/api/list'; import { getMusicDetail } from '@/api/music'; import { getUserPlaylist } from '@/api/user'; import { navigateToMusicList } from '@/components/common/MusicListNavigator'; import { useArtist } from '@/hooks/useArtist'; -import { usePlayerStore, useUserStore } from '@/store'; -import { IDayRecommend } from '@/types/day_recommend'; +import { usePlayerStore, useRecommendStore, useUserStore } from '@/store'; import { Playlist } from '@/types/list'; import type { IListDetail } from '@/types/listDetail'; import { SongResult } from '@/types/music'; @@ -152,13 +151,21 @@ import { const userStore = useUserStore(); const playerStore = usePlayerStore(); +const recommendStore = useRecommendStore(); const router = useRouter(); const { t } = useI18n(); // 歌手信息 const hotSingerData = ref(); -const dayRecommendData = ref(); +const dayRecommendData = computed(() => { + if (recommendStore.dailyRecommendSongs.length > 0) { + return { + dailySongs: recommendStore.dailyRecommendSongs + }; + } + return null; +}); const userPlaylist = ref([]); // 为歌单弹窗添加的状态 @@ -230,22 +237,8 @@ onMounted(async () => { loadNonUserData(); }); -// 提取每日推荐加载逻辑到单独的函数 const loadDayRecommendData = async () => { - try { - const { - data: { data: dayRecommend } - } = await getDayRecommend(); - const dayRecommendSource = dayRecommend as unknown as IDayRecommend; - dayRecommendData.value = { - ...dayRecommendSource, - dailySongs: dayRecommendSource.dailySongs.filter( - (song: any) => !playerStore.dislikeList.includes(song.id) - ) - }; - } catch (error) { - console.error('获取每日推荐失败:', error); - } + await recommendStore.fetchDailyRecommendSongs(); }; // 加载不需要登录的数据 diff --git a/src/renderer/hooks/useSongItem.ts b/src/renderer/hooks/useSongItem.ts index f027b3b..1d31d2b 100644 --- a/src/renderer/hooks/useSongItem.ts +++ b/src/renderer/hooks/useSongItem.ts @@ -1,20 +1,21 @@ -import { useDialog, useMessage } from 'naive-ui'; +import { useMessage } from 'naive-ui'; import { computed, ref } from 'vue'; import { useI18n } from 'vue-i18n'; -import { usePlayerStore } from '@/store'; +import { usePlayerStore, useRecommendStore } from '@/store'; import type { SongResult } from '@/types/music'; import { getImgUrl } from '@/utils'; import { getImageBackground } from '@/utils/linearColor'; +import { dislikeRecommendedSong } from "../api/music"; import { useArtist } from './useArtist'; import { useDownload } from './useDownload'; export function useSongItem(props: { item: SongResult; canRemove?: boolean }) { const { t } = useI18n(); const playerStore = usePlayerStore(); + const recommendStore = useRecommendStore(); const message = useMessage(); - const dialog = useDialog(); const { downloadMusic } = useDownload(); const { navigateToArtist } = useArtist(); @@ -86,23 +87,57 @@ export function useSongItem(props: { item: SongResult; canRemove?: boolean }) { } }; + // 判断当前歌曲是否为每日推荐歌曲 + const isDailyRecommendSong = computed(() => { + return recommendStore.dailyRecommendSongs.some(song => song.id === props.item.id); + }); + // 切换不喜欢状态 - const toggleDislike = async (e: Event) => { + const toggleDislike = async (e: Event) => { e && e.stopPropagation(); + if (isDislike.value) { playerStore.removeFromDislikeList(props.item.id); return; } - dialog.warning({ - title: t('songItem.dialog.dislike.title'), - content: t('songItem.dialog.dislike.content'), - positiveText: t('songItem.dialog.dislike.positiveText'), - negativeText: t('songItem.dialog.dislike.negativeText'), - onPositiveClick: () => { - playerStore.addToDislikeList(props.item.id); + playerStore.addToDislikeList(props.item.id); + + // 只有当前歌曲是每日推荐歌曲时才调用接口 + if (!isDailyRecommendSong.value) { + return; + } + try { + console.log('发送不感兴趣请求,歌曲ID:', props.item.id); + const numericId = typeof props.item.id === 'string' ? parseInt(props.item.id) : props.item.id; + const response = await dislikeRecommendedSong(numericId); + if (response.data.data) { + console.log(response) + const newSongData = response.data.data; + const newSong: SongResult = { + ...newSongData, + name: newSongData.name, + id: newSongData.id, + picUrl: newSongData.al?.picUrl || newSongData.album?.picUrl, + ar: newSongData.ar || newSongData.artists, + al: newSongData.al || newSongData.album, + song: { + ...newSongData.song, + id: newSongData.id, + name: newSongData.name, + artists: newSongData.ar || newSongData.artists, + album: newSongData.al || newSongData.album, + }, + source: 'netease', + count: 0, + }; + recommendStore.replaceSongInDailyRecommend(props.item.id, newSong); + } else { + console.warn('标记不感兴趣API成功,但未返回新歌曲。', response.data); } - }); + } catch (error) { + console.error('发送不感兴趣请求时出错:', error); + } }; // 添加到下一首播放 diff --git a/src/renderer/store/index.ts b/src/renderer/store/index.ts index e0c57fc..fe4d61f 100644 --- a/src/renderer/store/index.ts +++ b/src/renderer/store/index.ts @@ -15,6 +15,7 @@ export * from './modules/lyric'; export * from './modules/menu'; export * from './modules/music'; export * from './modules/player'; +export * from './modules/recommend'; export * from './modules/search'; export * from './modules/settings'; export * from './modules/user'; diff --git a/src/renderer/store/modules/recommend.ts b/src/renderer/store/modules/recommend.ts new file mode 100644 index 0000000..616ba65 --- /dev/null +++ b/src/renderer/store/modules/recommend.ts @@ -0,0 +1,43 @@ +import { defineStore } from 'pinia'; +import { ref } from 'vue'; + +import { getDayRecommend } from '@/api/home'; +import type { IDayRecommend } from '@/types/day_recommend'; +import type { SongResult } from '@/types/music'; + +export const useRecommendStore = defineStore('recommend', () => { + const dailyRecommendSongs = ref([]); + + const fetchDailyRecommendSongs = async () => { + try { + const { data } = await getDayRecommend(); + const recommendData = data.data as unknown as IDayRecommend; + + if (recommendData && Array.isArray(recommendData.dailySongs)) { + dailyRecommendSongs.value = recommendData.dailySongs as any; + console.log(`[Recommend Store] 已加载 ${recommendData.dailySongs.length} 首每日推荐歌曲。`); + } else { + dailyRecommendSongs.value = []; + } + } catch (error) { + console.error('[Recommend Store] 获取每日推荐失败:', error); + dailyRecommendSongs.value = []; + } + }; + + const replaceSongInDailyRecommend = (oldSongId: number | string, newSong: SongResult) => { + const index = dailyRecommendSongs.value.findIndex((song) => song.id === oldSongId); + if (index !== -1) { + dailyRecommendSongs.value.splice(index, 1, newSong as any); + console.log(`[Recommend Store] 已将歌曲 ${oldSongId} 替换为 ${newSong.name}`); + } else { + console.warn(`[Recommend Store] 未在日推列表中找到要替换的歌曲ID: ${oldSongId}`); + } + }; + + return { + dailyRecommendSongs, + fetchDailyRecommendSongs, + replaceSongInDailyRecommend + }; +}); diff --git a/src/renderer/views/music/MusicListPage.vue b/src/renderer/views/music/MusicListPage.vue index e930ce4..5c33efb 100644 --- a/src/renderer/views/music/MusicListPage.vue +++ b/src/renderer/views/music/MusicListPage.vue @@ -227,11 +227,11 @@ import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; import { subscribePlaylist, updatePlaylistTracks } from '@/api/music'; -import { getMusicDetail, getMusicListByType } from '@/api/music'; +import { getMusicDetail } from '@/api/music'; import PlayBottom from '@/components/common/PlayBottom.vue'; import SongItem from '@/components/common/SongItem.vue'; import { useDownload } from '@/hooks/useDownload'; -import { useMusicStore, usePlayerStore } from '@/store'; +import { useMusicStore, usePlayerStore, useRecommendStore } from '@/store'; import { SongResult } from '@/types/music'; import { getImgUrl, isElectron, isMobile, setAnimationClass } from '@/utils'; @@ -239,14 +239,39 @@ const { t } = useI18n(); const route = useRoute(); const playerStore = usePlayerStore(); const musicStore = useMusicStore(); +const recommendStore = useRecommendStore(); const message = useMessage(); // 从路由参数或状态管理获取数据 -const name = ref(''); const loading = ref(false); -const songList = ref([]); -const listInfo = ref(null); -const canRemove = ref(false); +const isDailyRecommend = computed(() => route.query.type === 'dailyRecommend'); +const name = computed(() => { + if (isDailyRecommend.value) { + return t('comp.recommendSinger.songlist'); // 日推的标题 + } + return musicStore.currentMusicListName || ''; // 其他列表的标题 +}); +const songList = computed(() => { + if (isDailyRecommend.value) { + // 如果是日推页面,直接使用 recommendStore 中响应式的数据 + return recommendStore.dailyRecommendSongs; + } + // 否则,使用 musicStore 中的静态数据 + return musicStore.currentMusicList || []; +}); +const listInfo = computed(() => { + if (isDailyRecommend.value) { + return null; + } + return musicStore.currentListInfo || null; +}); +const canRemove = computed(() => { + if (isDailyRecommend.value) { + return false; + } + return musicStore.canRemoveSong || false; +}); + const canCollect = ref(false); const isCollected = ref(false); @@ -303,78 +328,9 @@ const total = computed(() => { // 初始化数据 onMounted(() => { - initData(); checkCollectionStatus(); }); -// 从 pinia 或路由参数获取数据 - -// 从路由参数获取 -const routeId = route.params.id as string; -const routeType = route.query.type as string; - -const initData = () => { - // 优先从 pinia 获取数据 - if (musicStore.currentMusicList) { - name.value = musicStore.currentMusicListName || ''; - songList.value = musicStore.currentMusicList || []; - listInfo.value = musicStore.currentListInfo || null; - canRemove.value = musicStore.canRemoveSong || false; - - // 初始化歌曲列表 - initSongList(songList.value); - return; - } - - if (routeId) { - // 这里根据 type 和 id 加载数据 - // 例如: 获取歌单、专辑等 - loading.value = true; - loadDataByType(routeType, routeId).finally(() => { - loading.value = false; - }); - } -}; - -// 根据类型加载数据 -const loadDataByType = async (type: string, id: string) => { - try { - const result = await getMusicListByType(type, id); - - if (type === 'album') { - const { songs, album } = result.data; - name.value = album.name; - songList.value = songs.map((song: any) => { - song.al.picUrl = song.al.picUrl || album.picUrl; - song.picUrl = song.al.picUrl || album.picUrl || song.picUrl; - return song; - }); - listInfo.value = { - ...album, - creator: { - avatarUrl: album.artist.img1v1Url, - nickname: `${album.artist.name} - ${album.company}` - }, - description: album.description - }; - } else if (type === 'playlist') { - const { playlist } = result.data; - name.value = playlist.name; - listInfo.value = playlist; - - // 初始化歌曲列表 - if (playlist.tracks) { - songList.value = playlist.tracks; - } - } - - // 初始化歌曲列表 - initSongList(songList.value); - } catch (error) { - console.error('加载数据失败:', error); - } -}; - const getCoverImgUrl = computed(() => { const coverImgUrl = listInfo.value?.coverImgUrl; if (coverImgUrl) { @@ -394,22 +350,27 @@ const getCoverImgUrl = computed(() => { return ''; }); -const getDisplaySongs = computed(() => { - if (routeType === 'dailyRecommend') { - return displayedSongs.value.filter((song) => !playerStore.dislikeList.includes(song.id)); - } else { - return displayedSongs.value; - } -}); - // 过滤歌曲列表 const filteredSongs = computed(() => { + // 1. 确定数据源是来自store的完整列表(日推)还是来自本地分页的列表(其他) + const sourceList = isDailyRecommend.value + ? songList.value // 如果是日推,直接使用来自 recommendStore 的完整、响应式列表 + : displayedSongs.value; // 否则,使用用于分页加载的 displayedSongs + + // 2. 过滤不喜欢的歌曲 + const dislikeFilteredList = sourceList.filter( + (song) => !playerStore.dislikeList.includes(song.id) + ); + // ================================================================= + + // 3. 如果没有搜索词,直接返回处理后的列表 if (!searchKeyword.value) { - return getDisplaySongs.value; + return dislikeFilteredList; } + // 4. 如果有搜索词,在处理后的列表上进行搜索 const keyword = searchKeyword.value.toLowerCase().trim(); - return getDisplaySongs.value.filter((song) => { + return dislikeFilteredList.filter((song) => { const songName = song.name?.toLowerCase() || ''; const albumName = song.al?.name?.toLowerCase() || ''; const artists = song.ar || song.artists || []; @@ -439,6 +400,17 @@ const filteredSongs = computed(() => { }); }); +const resetListState = () => { + page.value = 0; + loadedIds.value.clear(); + displayedSongs.value = []; + completePlaylist.value = []; + hasMore.value = true; + loadingList.value = false; + searchKeyword.value = ''; + isFullPlaylistLoaded.value = false; +}; + // 格式化歌曲数据 const formatSong = (item: any) => { if (!item) { @@ -803,6 +775,20 @@ watch(searchKeyword, () => { } }); +watch( + songList, + (newSongs) => { + resetListState(); + initSongList(newSongs); + if (hasMore.value && listInfo.value?.trackIds) { + setTimeout(() => { + loadMoreSongs(); + }, 300); + } + }, + { immediate: true } +); + // 组件卸载时清理状态 onUnmounted(() => { isPlaylistLoading.value = false;