feat: 历史记录页面 添加本地和云端两种记录支持,支持歌曲、歌单、专辑

This commit is contained in:
alger
2025-10-22 21:51:16 +08:00
parent a9adb6be36
commit 6d7ba6dbae
16 changed files with 1045 additions and 137 deletions
+445 -84
View File
@@ -3,61 +3,361 @@
<div class="title" :class="setAnimationClass('animate__fadeInRight')">
{{ t('history.title') }}
</div>
<!-- 第一级Tab: 歌曲/歌单/专辑 -->
<div class="category-tabs-wrapper" :class="setAnimationClass('animate__fadeInRight')">
<n-tabs
v-model:value="currentCategory"
type="segment"
animated
size="large"
@update:value="handleCategoryChange"
>
<n-tab name="songs" :tab="t('history.categoryTabs.songs')"></n-tab>
<n-tab name="playlists" :tab="t('history.categoryTabs.playlists')"></n-tab>
<n-tab name="albums" :tab="t('history.categoryTabs.albums')"></n-tab>
</n-tabs>
</div>
<!-- 第二级Tab: 本地/云端 -->
<div class="tabs-wrapper" :class="setAnimationClass('animate__fadeInRight')">
<n-tabs
v-model:value="currentTab"
type="segment"
animated
@update:value="handleTabChange"
size="small"
>
<n-tab name="local" :tab="t('history.tabs.local')"></n-tab>
<n-tab name="cloud" :tab="t('history.tabs.cloud')"></n-tab>
</n-tabs>
</div>
<n-scrollbar ref="scrollbarRef" :size="100" @scroll="handleScroll">
<div class="history-list-content" :class="setAnimationClass('animate__bounceInLeft')">
<div
v-for="(item, index) in displayList"
:key="item.id"
class="history-item"
:class="setAnimationClass('animate__bounceInRight')"
:style="setAnimationDelay(index, 30)"
>
<song-item class="history-item-content" :item="item" @play="handlePlay" />
<div class="history-item-count min-w-[60px]">
{{ t('history.playCount', { count: item.count }) }}
</div>
<div class="history-item-delete">
<i class="iconfont icon-close" @click="handleDelMusic(item)"></i>
<!-- 歌曲列表 -->
<template v-if="currentCategory === 'songs'">
<div
v-for="(item, index) in displayList"
:key="item.id"
class="history-item"
:class="setAnimationClass('animate__bounceInRight')"
:style="setAnimationDelay(index, 30)"
>
<song-item class="history-item-content" :item="item" @play="handlePlay" />
<div class="history-item-count min-w-[60px]" v-show="currentTab === 'local'">
{{ t('history.playCount', { count: item.count }) }}
</div>
<div class="history-item-delete" v-show="currentTab === 'local'">
<i class="iconfont icon-close" @click="handleDelMusic(item)"></i>
</div>
</div>
</template>
<!-- 歌单列表 -->
<template v-if="currentCategory === 'playlists'">
<playlist-item
v-for="(item, index) in displayList"
:key="item.id"
:item="item"
:show-count="currentTab === 'local'"
:show-delete="currentTab === 'local'"
:class="setAnimationClass('animate__bounceInRight')"
:style="setAnimationDelay(index, 30)"
@click="handlePlaylistClick(item)"
@delete="handleDelPlaylist(item)"
/>
</template>
<!-- 专辑列表 -->
<template v-if="currentCategory === 'albums'">
<album-item
v-for="(item, index) in displayList"
:key="item.id"
:item="item"
:show-count="currentTab === 'local'"
:show-delete="currentTab === 'local'"
:class="setAnimationClass('animate__bounceInRight')"
:style="setAnimationDelay(index, 30)"
@click="handleAlbumClick(item)"
@delete="handleDelAlbum(item)"
/>
</template>
<div v-if="displayList.length === 0 && !loading" class="no-data">
{{ t('history.noData') }}
</div>
<div v-if="loading" class="loading-wrapper">
<n-spin size="large" />
</div>
<div v-if="noMore" class="no-more-tip">{{ t('common.noMore') }}</div>
<div v-if="noMore && displayList.length > 0" class="no-more-tip">
{{ t('common.noMore') }}
</div>
</div>
</n-scrollbar>
</div>
</template>
<script setup lang="ts">
import { useMessage } from 'naive-ui';
import { onMounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { processBilibiliVideos } from '@/api/bilibili';
import { getListDetail } from '@/api/list';
import { getAlbumDetail } from '@/api/music';
import { getMusicDetail } from '@/api/music';
import { getRecentAlbums, getRecentPlaylists, getRecentSongs } from '@/api/user';
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 { usePlayerStore } from '@/store/modules/player';
import { useUserStore } from '@/store/modules/user';
import type { SongResult } from '@/types/music';
import { setAnimationClass, setAnimationDelay } from '@/utils';
// 扩展历史记录类型以包含 playTime
interface HistoryRecord extends Partial<SongResult> {
id: string | number;
playTime?: number;
score?: number;
source?: 'netease' | 'bilibili';
count?: number;
recordSource?: 'local' | 'cloud';
sources?: ('local' | 'cloud')[];
bilibiliData?: {
bvid: string;
cid: number;
};
}
const { t } = useI18n();
const message = useMessage();
const router = useRouter();
const { delMusic, musicList } = useMusicHistory();
const { delPlaylist, playlistList } = usePlaylistHistory();
const { delAlbum, albumList } = useAlbumHistory();
const userStore = useUserStore();
const scrollbarRef = ref();
const loading = ref(false);
const noMore = ref(false);
const displayList = ref<SongResult[]>([]);
const displayList = ref<any[]>([]);
const playerStore = usePlayerStore();
const hasLoaded = ref(false);
const currentCategory = ref<'songs' | 'playlists' | 'albums'>('songs');
const currentTab = ref<'local' | 'cloud'>('local');
const cloudRecords = ref<HistoryRecord[]>([]);
const cloudPlaylists = ref<any[]>([]);
const cloudAlbums = ref<any[]>([]);
// 无限滚动相关配置
const pageSize = 100;
const currentPage = ref(1);
// 获取当前页的音乐详情
const getHistorySongs = async () => {
if (musicList.value.length === 0) {
// 获取云端播放记录
const getCloudRecords = async () => {
if (!userStore.user?.userId || userStore.loginType !== 'cookie') {
message.warning(t('history.needLogin'));
return [];
}
try {
const res = await getRecentSongs(1000);
if (res.data?.data?.list) {
return res.data.data.list.map((item: any) => ({
id: item.data?.id,
playTime: item.playTime,
source: 'netease',
count: 1,
data: item.data
}));
}
return [];
} catch (error: any) {
console.error(t('history.getCloudRecordFailed'), error);
if (error?.response?.status !== 301 && error?.response?.data?.code !== -2) {
message.error(t('history.getCloudRecordFailed'));
}
return [];
}
};
// 获取云端歌单播放记录
const getCloudPlaylists = async () => {
if (!userStore.user?.userId || userStore.loginType !== 'cookie') {
message.warning(t('history.needLogin'));
return [];
}
try {
const res = await getRecentPlaylists(100);
if (res.data?.data?.list) {
return res.data.data.list.map((item: any) => ({
id: item.data?.id,
name: item.data?.name,
coverImgUrl: item.data?.coverImgUrl,
picUrl: item.data?.picUrl,
trackCount: item.data?.trackCount,
playCount: item.data?.playCount,
creator: item.data?.creator,
playTime: item.playTime
}));
}
return [];
} catch (error: any) {
console.error(t('history.getCloudRecordFailed'), error);
if (error?.response?.status !== 301 && error?.response?.data?.code !== -2) {
message.error(t('history.getCloudRecordFailed'));
}
return [];
}
};
// 获取云端专辑播放记录
const getCloudAlbums = async () => {
if (!userStore.user?.userId || userStore.loginType !== 'cookie') {
message.warning(t('history.needLogin'));
return [];
}
try {
const res = await getRecentAlbums(100);
if (res.data?.data?.list) {
return res.data.data.list.map((item: any) => ({
id: item.data?.id,
name: item.data?.name,
picUrl: item.data?.picUrl,
size: item.data?.size,
artist: item.data?.artist,
playTime: item.playTime
}));
}
return [];
} catch (error: any) {
console.error(t('history.getCloudRecordFailed'), error);
if (error?.response?.status !== 301 && error?.response?.data?.code !== -2) {
message.error(t('history.getCloudRecordFailed'));
}
return [];
}
};
// 根据当前分类和tab获取要显示的列表
const getCurrentList = (): any[] => {
if (currentCategory.value === 'songs') {
switch (currentTab.value) {
case 'local':
return musicList.value;
case 'cloud':
return cloudRecords.value.filter((item) => item.id);
}
} else if (currentCategory.value === 'playlists') {
switch (currentTab.value) {
case 'local':
return playlistList.value;
case 'cloud':
return cloudPlaylists.value;
}
} else if (currentCategory.value === 'albums') {
switch (currentTab.value) {
case 'local':
return albumList.value;
case 'cloud':
return cloudAlbums.value;
}
}
return [];
};
// 处理分类切换
const handleCategoryChange = async (value: 'songs' | 'playlists' | 'albums') => {
currentCategory.value = value;
currentPage.value = 1;
noMore.value = false;
displayList.value = [];
// 如果切换到云端,且还没有加载对应的云端数据,则加载
if (currentTab.value === 'cloud') {
loading.value = true;
if (value === 'songs' && cloudRecords.value.length === 0) {
cloudRecords.value = await getCloudRecords();
} else if (value === 'playlists' && cloudPlaylists.value.length === 0) {
cloudPlaylists.value = await getCloudPlaylists();
} else if (value === 'albums' && cloudAlbums.value.length === 0) {
cloudAlbums.value = await getCloudAlbums();
}
loading.value = false;
}
await loadHistoryData();
};
// 处理歌单点击
const handlePlaylistClick = async (item: any) => {
try {
const res = await getListDetail(item.id);
if (res.data?.playlist) {
navigateToMusicList(router, {
id: item.id,
type: 'playlist',
name: item.name,
songList: res.data.playlist.tracks || [],
listInfo: res.data.playlist,
canRemove: false
});
}
} catch (error) {
console.error('打开歌单失败:', error);
message.error('打开歌单失败');
}
};
// 处理专辑点击
const handleAlbumClick = async (item: any) => {
try {
const res = await getAlbumDetail(item.id.toString());
if (res.data?.album && res.data?.songs) {
const albumData = res.data.album;
const songs = res.data.songs.map((song: any) => ({
...song,
picUrl: albumData.picUrl
}));
navigateToMusicList(router, {
id: item.id,
type: 'album',
name: albumData.name,
songList: songs,
listInfo: albumData,
canRemove: false
});
}
} catch (error) {
console.error('打开专辑失败:', error);
message.error('打开专辑失败');
}
};
// 删除歌单记录
const handleDelPlaylist = (item: any) => {
delPlaylist(item);
displayList.value = displayList.value.filter((playlist) => playlist.id !== item.id);
};
// 删除专辑记录
const handleDelAlbum = (item: any) => {
delAlbum(item);
displayList.value = displayList.value.filter((album) => album.id !== item.id);
};
// 加载历史数据(根据当前分类)
const loadHistoryData = async () => {
const currentList = getCurrentList();
if (currentList.length === 0) {
displayList.value = [];
return;
}
@@ -66,70 +366,77 @@ const getHistorySongs = async () => {
try {
const startIndex = (currentPage.value - 1) * pageSize;
const endIndex = startIndex + pageSize;
const currentPageItems = musicList.value.slice(startIndex, endIndex);
const currentPageItems = currentList.slice(startIndex, endIndex);
// 分离网易云音乐和B站视频
const neteaseItems = currentPageItems.filter((item) => item.source !== 'bilibili');
const bilibiliItems = currentPageItems.filter((item) => item.source === 'bilibili');
// 根据分类处理不同的数据
if (currentCategory.value === 'songs') {
// 处理歌曲数据
const neteaseItems = currentPageItems.filter((item) => item.source !== 'bilibili');
const bilibiliItems = currentPageItems.filter((item) => item.source === 'bilibili');
// 处理网易云音乐
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'
};
});
}
}
// 处理B站视频 - 使用公用方法
const bilibiliIds = bilibiliItems
.map((item) => `${item.bilibiliData?.bvid}--1--${item.bilibiliData?.cid}`)
.filter((id) => id && !id.includes('undefined'));
const bilibiliSongs = await processBilibiliVideos(bilibiliIds);
// 添加count信息
bilibiliSongs.forEach((song) => {
const historyItem = bilibiliItems.find(
(item) =>
item.bilibiliData?.bvid === song.bilibiliData?.bvid &&
item.bilibiliData?.cid === song.bilibiliData?.cid
);
if (historyItem) {
song.count = historyItem.count || 0;
}
});
// 合并两种来源的数据,并保持原有顺序
const newSongs = currentPageItems
.map((item) => {
if (item.source === 'bilibili') {
return bilibiliSongs.find(
(song) =>
song.bilibiliData?.bvid === item.bilibiliData?.bvid &&
song.bilibiliData?.cid === item.bilibiliData?.cid
);
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'
};
});
}
return neteaseSongs.find((song) => song.id === item.id);
})
.filter((song): song is SongResult => !!song);
}
if (currentPage.value === 1) {
displayList.value = newSongs;
const bilibiliIds = bilibiliItems
.map((item) => `${item.bilibiliData?.bvid}--1--${item.bilibiliData?.cid}`)
.filter((id) => id && !id.includes('undefined'));
const bilibiliSongs = await processBilibiliVideos(bilibiliIds);
bilibiliSongs.forEach((song) => {
const historyItem = bilibiliItems.find(
(item) =>
item.bilibiliData?.bvid === song.bilibiliData?.bvid &&
item.bilibiliData?.cid === song.bilibiliData?.cid
);
if (historyItem) {
song.count = historyItem.count || 0;
}
});
const newSongs = currentPageItems
.map((item) => {
if (item.source === 'bilibili') {
return bilibiliSongs.find(
(song) =>
song.bilibiliData?.bvid === item.bilibiliData?.bvid &&
song.bilibiliData?.cid === item.bilibiliData?.cid
);
}
return neteaseSongs.find((song) => song.id === item.id);
})
.filter((song): song is SongResult => !!song);
if (currentPage.value === 1) {
displayList.value = newSongs;
} else {
displayList.value = [...displayList.value, ...newSongs];
}
} else {
displayList.value = [...displayList.value, ...newSongs];
// 处理歌单和专辑数据(直接显示,不需要额外请求)
if (currentPage.value === 1) {
displayList.value = currentPageItems;
} else {
displayList.value = [...displayList.value, ...currentPageItems];
}
}
noMore.value = displayList.value.length >= musicList.value.length;
const totalLength = getCurrentList().length;
noMore.value = displayList.value.length >= totalLength;
} catch (error) {
console.error(t('history.getHistoryFailed'), error);
} finally {
@@ -140,11 +447,11 @@ const getHistorySongs = async () => {
// 处理滚动事件
const handleScroll = (e: any) => {
const { scrollTop, scrollHeight, offsetHeight } = e.target;
const threshold = 100; // 距离底部多少像素时加载更多
const threshold = 100;
if (!loading.value && !noMore.value && scrollHeight - (scrollTop + offsetHeight) < threshold) {
currentPage.value++;
getHistorySongs();
loadHistoryData();
}
};
@@ -153,22 +460,45 @@ const handlePlay = () => {
playerStore.setPlayList(displayList.value);
};
// 处理 tab 切换
const handleTabChange = async (value: 'local' | 'cloud') => {
currentTab.value = value;
currentPage.value = 1;
noMore.value = false;
displayList.value = [];
// 如果切换到云端,且还没有加载对应的云端数据,则加载
if (value === 'cloud') {
loading.value = true;
if (currentCategory.value === 'songs' && cloudRecords.value.length === 0) {
cloudRecords.value = await getCloudRecords();
} else if (currentCategory.value === 'playlists' && cloudPlaylists.value.length === 0) {
cloudPlaylists.value = await getCloudPlaylists();
} else if (currentCategory.value === 'albums' && cloudAlbums.value.length === 0) {
cloudAlbums.value = await getCloudAlbums();
}
loading.value = false;
}
await loadHistoryData();
};
onMounted(async () => {
if (!hasLoaded.value) {
await getHistorySongs();
await loadHistoryData();
hasLoaded.value = true;
}
});
// 监听历史列表变化,变化时重置并重新加载
watch(
musicList,
[musicList, playlistList, albumList],
async () => {
hasLoaded.value = false;
currentPage.value = 1;
noMore.value = false;
await getHistorySongs();
hasLoaded.value = true;
if (hasLoaded.value) {
currentPage.value = 1;
noMore.value = false;
await loadHistoryData();
}
},
{ deep: true }
);
@@ -191,6 +521,14 @@ const handleDelMusic = async (item: SongResult) => {
@apply text-gray-900 dark:text-white;
}
.category-tabs-wrapper {
@apply px-4 mb-2;
}
.tabs-wrapper {
@apply px-4;
}
.history-list-content {
@apply mt-2 pb-28 px-4;
.history-item {
@@ -220,4 +558,27 @@ const handleDelMusic = async (item: SongResult) => {
@apply text-center py-4 text-sm;
@apply text-gray-500 dark:text-gray-400;
}
.no-data {
@apply text-center py-8 text-gray-500 dark:text-gray-400;
}
:deep(.n-tabs-rail) {
@apply rounded-xl overflow-hidden !important;
.n-tabs-capsule {
@apply rounded-xl !important;
}
}
.category-tabs-wrapper {
:deep(.n-tabs-rail) {
@apply rounded-xl overflow-hidden bg-white dark:bg-dark-300 !important;
.n-tabs-capsule {
@apply rounded-xl bg-green-500 dark:bg-green-600 !important;
}
.n-tabs-tab--active {
@apply text-white !important;
}
}
}
</style>
+48 -3
View File
@@ -239,6 +239,8 @@ 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 { SongResult } from '@/types/music';
@@ -252,6 +254,8 @@ const musicStore = useMusicStore();
const recommendStore = useRecommendStore();
const userStore = useUserStore();
const message = useMessage();
const { addPlaylist } = usePlaylistHistory();
const { addAlbum } = useAlbumHistory();
// 从路由参数或状态管理获取数据
const loading = ref(false);
@@ -631,7 +635,7 @@ const handlePlay = async () => {
playerStore.setPlayList(filteredSongs.value.map(formatSong));
return;
}
saveHistory();
// 如果完整播放列表已加载完成
if (isFullPlaylistLoaded.value && completePlaylist.value.length > 0) {
playerStore.setPlayList(completePlaylist.value.map(formatSong));
@@ -874,11 +878,27 @@ const toggleCollect = async () => {
// 更新收藏状态
if (type === 'album') {
// 专辑:更新 store 中的收藏状态
// 专辑:更新 store 中的收藏状态和专辑列表
if (isCollected.value) {
// 添加到收藏ID集合
userStore.addCollectedAlbum(listInfo.value.id);
// 添加到专辑列表
const albumData = {
id: listInfo.value.id,
name: listInfo.value.name,
picUrl: listInfo.value.picUrl || listInfo.value.coverImgUrl,
size: listInfo.value.size,
artist: listInfo.value.artist || listInfo.value.artists?.[0]
};
userStore.albumList.unshift(albumData);
} else {
// 从收藏ID集合中移除
userStore.removeCollectedAlbum(listInfo.value.id);
// 从专辑列表中移除
const index = userStore.albumList.findIndex((album) => album.id === listInfo.value.id);
if (index !== -1) {
userStore.albumList.splice(index, 1);
}
}
(listInfo.value as any).isSub = isCollected.value;
} else {
@@ -899,7 +919,7 @@ const toggleCollect = async () => {
// 播放全部
const handlePlayAll = () => {
if (displayedSongs.value.length === 0) return;
saveHistory();
// 如果有搜索关键词,只播放过滤后的歌曲
if (searchKeyword.value) {
playerStore.setPlayList(filteredSongs.value.map(formatSong));
@@ -914,6 +934,31 @@ const handlePlayAll = () => {
playerStore.setPlay(formatSong(displayedSongs.value[0]));
};
const saveHistory = () => {
if (listInfo.value?.id) {
if (isAlbum.value) {
// 保存专辑播放记录
addAlbum({
id: listInfo.value.id,
name: listInfo.value.name || '',
picUrl: listInfo.value.picUrl || listInfo.value.coverImgUrl || '',
size: listInfo.value.size || displayedSongs.value.length,
artist: listInfo.value.artist || listInfo.value.artists?.[0]
});
} else if (route.query.type === 'playlist') {
// 保存歌单播放记录
addPlaylist({
id: listInfo.value.id,
name: listInfo.value.name || '',
coverImgUrl: listInfo.value.coverImgUrl || listInfo.value.picUrl || '',
trackCount: listInfo.value.trackCount || displayedSongs.value.length,
playCount: listInfo.value.playCount,
creator: listInfo.value.creator
});
}
}
};
// 添加到播放列表末尾
const addToPlaylist = () => {
if (displayedSongs.value.length === 0) return;
+33 -39
View File
@@ -144,7 +144,6 @@ const userStore = useUserStore();
const playerStore = usePlayerStore();
const router = useRouter();
const userDetail = ref<IUserDetail>();
const playList = ref<any[]>([]);
const recordList = ref();
const infoLoading = ref(false);
const mounted = ref(true);
@@ -159,34 +158,27 @@ const tabs = [
{ key: 'album', label: 'user.tabs.album' }
];
const currentTab = ref('created');
const albumList = ref<any[]>([]);
const user = computed(() => userStore.user);
// 创建的歌单(当前用户创建的)
const createdPlaylists = computed(() => {
if (!user.value) return [];
return playList.value.filter((item) => item.creator?.userId === user.value!.userId);
return userStore.playList.filter((item) => item.creator?.userId === user.value!.userId);
});
// 收藏的歌单(当前用户创建的)
// 收藏的歌单(当前用户收藏的)
const favoritePlaylists = computed(() => {
if (!user.value) return [];
return playList.value.filter((item) => item.creator?.userId !== user.value!.userId);
return userStore.playList.filter((item) => item.creator?.userId !== user.value!.userId);
});
// 当前显示的列表(根据 tab 切换)
const currentList = computed(() => {
switch (currentTab.value) {
case 'created':
return createdPlaylists.value;
case 'favorite':
return favoritePlaylists.value;
case 'album':
return albumList.value;
default:
return [];
if (currentTab.value === 'album') {
return userStore.albumList;
}
return currentTab.value === 'created' ? createdPlaylists.value : favoritePlaylists.value;
});
// 获取封面图片 URL
@@ -226,28 +218,18 @@ onBeforeUnmount(() => {
// 检查登录状态
const checkLoginStatus = () => {
// 优先使用 userStore 的状态
// userStore 的状态已经在 App.vue 中全局初始化,这里只需要检查
if (userStore.user && userStore.loginType) {
return true;
}
// 如果 store 中没有数据,尝试从 localStorage 恢复
// 如果还是没有登录信息,跳转到登录页
const loginInfo = checkAuthStatus();
if (!loginInfo.isLoggedIn) {
!isMobile.value && router.push('/login');
return false;
}
// 恢复用户数据和登录类型到 store
if (!userStore.user && loginInfo.user) {
userStore.setUser(loginInfo.user);
}
if (!userStore.loginType && loginInfo.loginType) {
userStore.setLoginType(loginInfo.loginType);
}
return true;
};
@@ -270,22 +252,28 @@ const loadData = async () => {
return;
}
// 使用 Promise.all 并行请求提高效率
const [userDetailRes, playlistRes, recordRes] = await Promise.all([
getUserDetail(user.value.userId),
getUserPlaylist(user.value.userId),
getUserRecord(user.value.userId)
]);
// 如果 store 中还没有数据,则加载
const promises = [getUserDetail(user.value.userId), getUserRecord(user.value.userId)];
if (userStore.playList.length === 0) {
promises.push(getUserPlaylist(user.value.userId));
}
const results = await Promise.all(promises);
if (!mounted.value) return;
userDetail.value = userDetailRes.data;
playList.value = playlistRes.data.playlist;
recordList.value = recordRes.data.allData.map((item: any) => ({
userDetail.value = results[0].data;
recordList.value = results[1].data.allData.map((item: any) => ({
...item,
...item.song,
picUrl: item.song.al.picUrl
}));
// 如果加载了歌单,更新 store
if (results.length > 2 && results[2].data?.playlist) {
userStore.playList = results[2].data.playlist;
}
} catch (error: any) {
console.error('加载用户页面失败:', error);
if (error.response?.status === 401) {
@@ -304,11 +292,17 @@ const loadData = async () => {
// 加载专辑列表
const loadAlbumList = async () => {
// 如果 store 中已经有数据,直接返回
if (userStore.albumList.length > 0) {
return;
}
try {
infoLoading.value = true;
const res = await getUserAlbumSublist({ limit: 100, offset: 0 });
if (!mounted.value) return;
albumList.value = res.data.data || [];
// 更新 store 中的专辑列表
userStore.albumList = res.data.data || [];
} catch (error: any) {
console.error('加载专辑列表失败:', error);
message.error('加载专辑列表失败');
@@ -348,8 +342,8 @@ watch(currentTab, async (newTab) => {
if (newTab === 'album') {
// 刷新收藏专辑列表到 store
await userStore.initializeCollectedAlbums();
// 如果本地列表为空,则加载
if (albumList.value.length === 0) {
// 如果 store 中列表为空,则加载
if (userStore.albumList.length === 0) {
loadAlbumList();
}
}
@@ -487,7 +481,7 @@ const currentLoginType = computed(() => userStore.loginType);
.record-list {
@apply rounded-2xl;
@apply bg-light dark:bg-black;
height: calc(100% - 100px);
height: calc(100% - 60px);
.record-item {
@apply flex items-center px-2 mb-2 rounded-2xl bg-light-100 dark:bg-dark-100;