feat: 用户页面添加收藏专辑展示

This commit is contained in:
alger
2025-10-22 21:50:20 +08:00
parent bee5445b6e
commit a9adb6be36
10 changed files with 328 additions and 58 deletions
+173 -39
View File
@@ -32,41 +32,50 @@
</div>
<div class="uesr-signature">{{ userDetail.profile.signature }}</div>
<div class="play-list" :class="setAnimationClass('animate__fadeInLeft')">
<div class="title">
<div>{{ t('user.playlist.created') }}</div>
<div class="import-btn" @click="goToImportPlaylist" v-if="isElectron">
{{ t('comp.playlist.import.button') }}
</div>
<div class="tab-container">
<n-tabs v-model:value="currentTab" type="segment" animated>
<n-tab v-for="tab in tabs" :key="tab.key" :name="tab.key" :tab="t(tab.label)">
</n-tab>
</n-tabs>
</div>
<n-scrollbar>
<div
v-for="(item, index) in playList"
:key="index"
class="play-list-item"
@click="openPlaylist(item)"
>
<n-image
:src="getImgUrl(item.coverImgUrl, '50y50')"
class="play-list-item-img"
lazy
preview-disabled
/>
<div class="play-list-item-info">
<div class="play-list-item-name">
<n-ellipsis :line-clamp="1">{{ item.name }}</n-ellipsis>
<div v-if="item.creator.userId === user.userId" class="playlist-creator-tag">
{{ t('user.playlist.mine') }}
<div class="mt-4">
<button
class="play-list-item"
@click="goToImportPlaylist"
v-if="isElectron && currentTab === 'created'"
>
<div class="play-list-item-img"><i class="icon iconfont ri-add-line"></i></div>
<div class="play-list-item-info">
<div class="play-list-item-name">
{{ t('comp.playlist.import.button') }}
</div>
</div>
<div class="play-list-item-count">
{{ t('user.playlist.trackCount', { count: item.trackCount }) }}{{
t('user.playlist.playCount', { count: item.playCount })
}}
</button>
<div
v-for="(item, index) in currentList"
:key="index"
class="play-list-item"
@click="handleItemClick(item)"
>
<n-image
:src="getImgUrl(getCoverUrl(item), '50y50')"
class="play-list-item-img"
lazy
preview-disabled
/>
<div class="play-list-item-info">
<div class="play-list-item-name">
<n-ellipsis :line-clamp="1">{{ item.name }}</n-ellipsis>
</div>
<div class="play-list-item-count">
{{ getItemDescription(item) }}
</div>
</div>
</div>
<div class="pb-20"></div>
<play-bottom />
</div>
<div class="pb-20"></div>
<play-bottom />
</n-scrollbar>
</div>
</div>
@@ -114,7 +123,7 @@ import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { getListDetail } from '@/api/list';
import { getUserDetail, getUserPlaylist, getUserRecord } from '@/api/user';
import { getUserAlbumSublist, getUserDetail, getUserPlaylist, getUserRecord } from '@/api/user';
import { navigateToMusicList } from '@/components/common/MusicListNavigator';
import PlayBottom from '@/components/common/PlayBottom.vue';
import SongItem from '@/components/common/SongItem.vue';
@@ -143,8 +152,70 @@ const list = ref<Playlist>();
const listLoading = ref(false);
const message = useMessage();
// Tab 相关
const tabs = [
{ key: 'created', label: 'user.tabs.created' },
{ key: 'favorite', label: 'user.tabs.favorite' },
{ 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);
});
// 收藏的歌单(非当前用户创建的)
const favoritePlaylists = computed(() => {
if (!user.value) return [];
return playList.value.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 [];
}
});
// 获取封面图片 URL
const getCoverUrl = (item: any) => {
return item.coverImgUrl || item.picUrl || '';
};
// 获取列表项描述
const getItemDescription = (item: any) => {
if (currentTab.value === 'album') {
// 专辑:显示艺术家和歌曲数量
const artist = item.artist?.name || '';
const size = item.size ? ` · ${item.size}` : '';
return `${artist}${size}`;
} else {
// 歌单:显示曲目数和播放量
return `${t('user.playlist.trackCount', { count: item.trackCount })}${t('user.playlist.playCount', { count: item.playCount })}`;
}
};
// 统一处理列表项点击
const handleItemClick = (item: any) => {
if (currentTab.value === 'album') {
openAlbum(item);
} else {
openPlaylist(item);
}
};
const goToImportPlaylist = () => {
router.push('/playlist/import');
};
@@ -231,6 +302,23 @@ const loadData = async () => {
}
};
// 加载专辑列表
const loadAlbumList = async () => {
try {
infoLoading.value = true;
const res = await getUserAlbumSublist({ limit: 100, offset: 0 });
if (!mounted.value) return;
albumList.value = res.data.data || [];
} catch (error: any) {
console.error('加载专辑列表失败:', error);
message.error('加载专辑列表失败');
} finally {
if (mounted.value) {
infoLoading.value = false;
}
}
};
// 监听路由变化
watch(
() => router.currentRoute.value.path,
@@ -255,6 +343,18 @@ watch(
}
);
// 监听 tab 切换
watch(currentTab, async (newTab) => {
if (newTab === 'album') {
// 刷新收藏专辑列表到 store
await userStore.initializeCollectedAlbums();
// 如果本地列表为空,则加载
if (albumList.value.length === 0) {
loadAlbumList();
}
}
});
// 页面挂载时检查登录状态
onMounted(() => {
checkLoginStatus() && loadData();
@@ -279,6 +379,38 @@ const openPlaylist = (item: any) => {
});
};
// 打开专辑
const openAlbum = async (item: any) => {
// 使用专辑 API 获取专辑详情
try {
listLoading.value = true;
const { getAlbumDetail } = await import('@/api/music');
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((item) => ({
...item,
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('加载专辑失败');
} finally {
listLoading.value = false;
}
};
const handlePlay = () => {
const tracks = recordList.value || [];
playerStore.setPlayList(tracks);
@@ -321,13 +453,7 @@ const currentLoginType = computed(() => userStore.loginType);
.title {
@apply text-lg font-bold flex items-center justify-between;
@apply text-gray-900 dark:text-white;
.import-btn {
@apply bg-light-100 font-normal rounded-lg px-2 py-1 text-opacity-70 text-sm hover:bg-light-200 hover:text-green-500 dark:bg-dark-200 dark:hover:bg-dark-300 dark:hover:text-green-400;
@apply cursor-pointer;
@apply transition-all duration-200;
}
}
.user-name {
@apply text-xl font-bold mb-4 flex justify-between;
@apply text-white text-opacity-70;
@@ -393,14 +519,15 @@ const currentLoginType = computed(() => userStore.loginType);
}
&-item {
@apply flex items-center px-2 py-1 rounded-xl cursor-pointer;
@apply flex items-center px-2 py-2 rounded-xl cursor-pointer w-full;
@apply transition-all duration-200;
@apply hover:bg-light-200 dark:hover:bg-dark-200;
&-img {
width: 60px;
height: 60px;
@apply rounded-xl;
@apply flex items-center justify-center rounded-xl text-[40px] w-[60px] h-[60px] bg-light-300 dark:bg-dark-300;
.iconfont {
@apply text-[40px];
}
}
&-info {
@@ -442,4 +569,11 @@ const currentLoginType = computed(() => userStore.loginType);
@apply flex justify-center items-center h-full w-full;
}
}
:deep(.n-tabs-rail) {
@apply rounded-xl overflow-hidden !important;
.n-tabs-capsule {
@apply rounded-xl !important;
}
}
</style>