mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-03 14:20:50 +08:00
feat: 日推不感兴趣调用官方接口
This commit is contained in:
@@ -208,6 +208,12 @@ export const likeSong = (id: number, like: boolean = true) => {
|
||||
return request.get('/like', { params: { id, like } });
|
||||
};
|
||||
|
||||
// 将每日推荐中的歌曲标记为不感兴趣,并获取一首新歌
|
||||
export const dislikeRecommendedSong = (id: number | string) => {
|
||||
return request.get('/recommend/songs/dislike', {
|
||||
params: { id }
|
||||
});
|
||||
};
|
||||
// 获取用户喜欢的音乐列表
|
||||
export const getLikedList = (uid: number) => {
|
||||
return request.get('/likelist', {
|
||||
|
||||
@@ -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' }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, useUserStore, useRecommendStore } 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<IHotSinger>();
|
||||
const dayRecommendData = ref<IDayRecommend>();
|
||||
const dayRecommendData = computed(() => {
|
||||
if (recommendStore.dailyRecommendSongs.length > 0) {
|
||||
return {
|
||||
dailySongs: recommendStore.dailyRecommendSongs,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
const userPlaylist = ref<Playlist[]>([]);
|
||||
|
||||
// 为歌单弹窗添加的状态
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
// 加载不需要登录的数据
|
||||
|
||||
@@ -2,17 +2,19 @@ import { useDialog, 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 { useArtist } from './useArtist';
|
||||
import { useDownload } from './useDownload';
|
||||
import { dislikeRecommendedSong } from "../api/music";
|
||||
|
||||
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();
|
||||
@@ -89,6 +91,7 @@ export function useSongItem(props: { item: SongResult; canRemove?: boolean }) {
|
||||
// 切换不喜欢状态
|
||||
const toggleDislike = async (e: Event) => {
|
||||
e && e.stopPropagation();
|
||||
|
||||
if (isDislike.value) {
|
||||
playerStore.removeFromDislikeList(props.item.id);
|
||||
return;
|
||||
@@ -99,8 +102,39 @@ export function useSongItem(props: { item: SongResult; canRemove?: boolean }) {
|
||||
content: t('songItem.dialog.dislike.content'),
|
||||
positiveText: t('songItem.dialog.dislike.positiveText'),
|
||||
negativeText: t('songItem.dialog.dislike.negativeText'),
|
||||
onPositiveClick: () => {
|
||||
onPositiveClick: async () => {
|
||||
playerStore.addToDislikeList(props.item.id);
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -18,5 +18,6 @@ export * from './modules/player';
|
||||
export * from './modules/search';
|
||||
export * from './modules/settings';
|
||||
export * from './modules/user';
|
||||
export * from './modules/recommend';
|
||||
|
||||
export default pinia;
|
||||
|
||||
42
src/renderer/store/modules/recommend.ts
Normal file
42
src/renderer/store/modules/recommend.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { getDayRecommend } from '@/api/home';
|
||||
import type { SongResult } from '@/types/music';
|
||||
import type { IDayRecommend } from '@/types/day_recommend';
|
||||
|
||||
export const useRecommendStore = defineStore('recommend', () => {
|
||||
const dailyRecommendSongs = ref<SongResult[]>([]);
|
||||
|
||||
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,
|
||||
};
|
||||
});
|
||||
@@ -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<any[]>([]);
|
||||
const listInfo = ref<any>(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,25 @@ 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 || [];
|
||||
@@ -429,16 +388,27 @@ const filteredSongs = computed(() => {
|
||||
});
|
||||
|
||||
return (
|
||||
nameMatch ||
|
||||
albumMatch ||
|
||||
artistsMatch ||
|
||||
namePinyinMatch ||
|
||||
albumPinyinMatch ||
|
||||
artistsPinyinMatch
|
||||
nameMatch ||
|
||||
albumMatch ||
|
||||
artistsMatch ||
|
||||
namePinyinMatch ||
|
||||
albumPinyinMatch ||
|
||||
artistsPinyinMatch
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
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 +773,20 @@ watch(searchKeyword, () => {
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
songList,
|
||||
(newSongs) => {
|
||||
resetListState();
|
||||
initSongList(newSongs);
|
||||
if (hasMore.value && listInfo.value?.trackIds) {
|
||||
setTimeout(() => {
|
||||
loadMoreSongs();
|
||||
}, 300);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 组件卸载时清理状态
|
||||
onUnmounted(() => {
|
||||
isPlaylistLoading.value = false;
|
||||
@@ -945,6 +929,7 @@ const handleBatchDownload = async () => {
|
||||
await batchDownloadMusic(selectedSongsList);
|
||||
cancelSelect();
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
Reference in New Issue
Block a user