mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-30 03:47:22 +08:00
feat: 用户页面添加收藏专辑展示
This commit is contained in:
@@ -10,6 +10,11 @@ export default {
|
|||||||
trackCount: '{count} tracks',
|
trackCount: '{count} tracks',
|
||||||
playCount: 'Played {count} times'
|
playCount: 'Played {count} times'
|
||||||
},
|
},
|
||||||
|
tabs: {
|
||||||
|
created: 'Created',
|
||||||
|
favorite: 'Favorite',
|
||||||
|
album: 'Album'
|
||||||
|
},
|
||||||
ranking: {
|
ranking: {
|
||||||
title: 'Listening History',
|
title: 'Listening History',
|
||||||
playCount: '{count} times'
|
playCount: '{count} times'
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ export default {
|
|||||||
trackCount: '{count}曲',
|
trackCount: '{count}曲',
|
||||||
playCount: '{count}回再生'
|
playCount: '{count}回再生'
|
||||||
},
|
},
|
||||||
|
tabs: {
|
||||||
|
created: '作成',
|
||||||
|
favorite: 'お気に入り',
|
||||||
|
album: 'アルバム'
|
||||||
|
},
|
||||||
ranking: {
|
ranking: {
|
||||||
title: '聴取ランキング',
|
title: '聴取ランキング',
|
||||||
playCount: '{count}回'
|
playCount: '{count}回'
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ export default {
|
|||||||
trackCount: '{count}곡',
|
trackCount: '{count}곡',
|
||||||
playCount: '{count}회 재생'
|
playCount: '{count}회 재생'
|
||||||
},
|
},
|
||||||
|
tabs: {
|
||||||
|
created: '생성',
|
||||||
|
favorite: '즐겨찾기',
|
||||||
|
album: '앨범'
|
||||||
|
},
|
||||||
ranking: {
|
ranking: {
|
||||||
title: '음악 청취 순위',
|
title: '음악 청취 순위',
|
||||||
playCount: '{count}회'
|
playCount: '{count}회'
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ export default {
|
|||||||
trackCount: '{count}首',
|
trackCount: '{count}首',
|
||||||
playCount: '播放{count}次'
|
playCount: '播放{count}次'
|
||||||
},
|
},
|
||||||
|
tabs: {
|
||||||
|
created: '创建',
|
||||||
|
favorite: '收藏',
|
||||||
|
album: '专辑'
|
||||||
|
},
|
||||||
ranking: {
|
ranking: {
|
||||||
title: '听歌排行',
|
title: '听歌排行',
|
||||||
playCount: '{count}次'
|
playCount: '{count}次'
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ export default {
|
|||||||
trackCount: '{count}首',
|
trackCount: '{count}首',
|
||||||
playCount: '播放{count}次'
|
playCount: '播放{count}次'
|
||||||
},
|
},
|
||||||
|
tabs: {
|
||||||
|
created: '建立',
|
||||||
|
favorite: '收藏',
|
||||||
|
album: '專輯'
|
||||||
|
},
|
||||||
ranking: {
|
ranking: {
|
||||||
title: '聽歌排行',
|
title: '聽歌排行',
|
||||||
playCount: '{count}次'
|
playCount: '{count}次'
|
||||||
|
|||||||
@@ -176,3 +176,15 @@ export function subscribePlaylist(params: { t: number; id: number }) {
|
|||||||
params
|
params
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收藏/取消收藏专辑
|
||||||
|
* @param params t: 1 收藏, 2 取消收藏; id: 专辑id
|
||||||
|
*/
|
||||||
|
export function subscribeAlbum(params: { t: number; id: number }) {
|
||||||
|
return request({
|
||||||
|
url: '/album/sub',
|
||||||
|
method: 'post',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -72,3 +72,15 @@ export const getUserPlaylists = (params: { uid: string | number }) => {
|
|||||||
params
|
params
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取已收藏专辑列表
|
||||||
|
export const getUserAlbumSublist = (params?: { limit?: number; offset?: number }) => {
|
||||||
|
return request({
|
||||||
|
url: '/album/sublist',
|
||||||
|
method: 'get',
|
||||||
|
params: {
|
||||||
|
limit: params?.limit || 25,
|
||||||
|
offset: params?.offset || 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ref } from 'vue';
|
|||||||
|
|
||||||
import { logout } from '@/api/login';
|
import { logout } from '@/api/login';
|
||||||
import { getLikedList } from '@/api/music';
|
import { getLikedList } from '@/api/music';
|
||||||
|
import { getUserAlbumSublist } from '@/api/user';
|
||||||
import { clearLoginStatus } from '@/utils/auth';
|
import { clearLoginStatus } from '@/utils/auth';
|
||||||
|
|
||||||
interface UserData {
|
interface UserData {
|
||||||
@@ -27,6 +28,8 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
);
|
);
|
||||||
const searchValue = ref('');
|
const searchValue = ref('');
|
||||||
const searchType = ref(1);
|
const searchType = ref(1);
|
||||||
|
// 收藏的专辑 ID 列表
|
||||||
|
const collectedAlbumIds = ref<Set<number>>(new Set());
|
||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
const setUser = (userData: UserData) => {
|
const setUser = (userData: UserData) => {
|
||||||
@@ -69,6 +72,39 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
searchType.value = type;
|
searchType.value = type;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 初始化收藏的专辑列表
|
||||||
|
const initializeCollectedAlbums = async () => {
|
||||||
|
if (!user.value || !localStorage.getItem('token')) {
|
||||||
|
collectedAlbumIds.value.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await getUserAlbumSublist({ limit: 1000, offset: 0 });
|
||||||
|
const albumIds = (data?.data || []).map((album: any) => album.id);
|
||||||
|
collectedAlbumIds.value = new Set(albumIds);
|
||||||
|
console.log(`已加载 ${albumIds.length} 个收藏专辑`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取收藏专辑列表失败:', error);
|
||||||
|
collectedAlbumIds.value.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加收藏专辑
|
||||||
|
const addCollectedAlbum = (albumId: number) => {
|
||||||
|
collectedAlbumIds.value.add(albumId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 移除收藏专辑
|
||||||
|
const removeCollectedAlbum = (albumId: number) => {
|
||||||
|
collectedAlbumIds.value.delete(albumId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查专辑是否已收藏
|
||||||
|
const isAlbumCollected = (albumId: number) => {
|
||||||
|
return collectedAlbumIds.value.has(albumId);
|
||||||
|
};
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
const initializeUser = async () => {
|
const initializeUser = async () => {
|
||||||
const savedUser = getLocalStorageItem<UserData | null>('user', null);
|
const savedUser = getLocalStorageItem<UserData | null>('user', null);
|
||||||
@@ -77,6 +113,9 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
// 如果用户已登录,获取收藏列表
|
// 如果用户已登录,获取收藏列表
|
||||||
if (localStorage.getItem('token')) {
|
if (localStorage.getItem('token')) {
|
||||||
try {
|
try {
|
||||||
|
// 同时初始化收藏专辑列表
|
||||||
|
await initializeCollectedAlbums();
|
||||||
|
|
||||||
const { data } = await getLikedList(savedUser.userId);
|
const { data } = await getLikedList(savedUser.userId);
|
||||||
return data?.ids || [];
|
return data?.ids || [];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -94,6 +133,7 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
loginType,
|
loginType,
|
||||||
searchValue,
|
searchValue,
|
||||||
searchType,
|
searchType,
|
||||||
|
collectedAlbumIds,
|
||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
setUser,
|
setUser,
|
||||||
@@ -101,6 +141,10 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
handleLogout,
|
handleLogout,
|
||||||
setSearchValue,
|
setSearchValue,
|
||||||
setSearchType,
|
setSearchType,
|
||||||
initializeUser
|
initializeUser,
|
||||||
|
initializeCollectedAlbums,
|
||||||
|
addCollectedAlbum,
|
||||||
|
removeCollectedAlbum,
|
||||||
|
isAlbumCollected
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -153,7 +153,12 @@
|
|||||||
object-fit="cover"
|
object-fit="cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="listInfo?.creator" class="creator-info">
|
<!-- 歌单显示创建者,专辑显示艺术家 -->
|
||||||
|
<div v-if="isAlbum && listInfo?.artist" class="creator-info">
|
||||||
|
<n-avatar round :size="24" :src="getImgUrl(listInfo.artist.picUrl, '50y50')" />
|
||||||
|
<span class="creator-name">{{ listInfo.artist.name }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="!isAlbum && listInfo?.creator" class="creator-info">
|
||||||
<n-avatar round :size="24" :src="getImgUrl(listInfo.creator.avatarUrl, '50y50')" />
|
<n-avatar round :size="24" :src="getImgUrl(listInfo.creator.avatarUrl, '50y50')" />
|
||||||
<span class="creator-name">{{ listInfo.creator.nickname }}</span>
|
<span class="creator-name">{{ listInfo.creator.nickname }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -226,12 +231,16 @@ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
|||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
import { subscribePlaylist, updatePlaylistTracks } from '@/api/music';
|
import {
|
||||||
import { getMusicDetail } from '@/api/music';
|
getMusicDetail,
|
||||||
|
subscribeAlbum,
|
||||||
|
subscribePlaylist,
|
||||||
|
updatePlaylistTracks
|
||||||
|
} 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 { useDownload } from '@/hooks/useDownload';
|
import { useDownload } from '@/hooks/useDownload';
|
||||||
import { useMusicStore, usePlayerStore, useRecommendStore } from '@/store';
|
import { useMusicStore, usePlayerStore, useRecommendStore, useUserStore } from '@/store';
|
||||||
import { SongResult } from '@/types/music';
|
import { SongResult } from '@/types/music';
|
||||||
import { getImgUrl, isElectron, isMobile, setAnimationClass } from '@/utils';
|
import { getImgUrl, isElectron, isMobile, setAnimationClass } from '@/utils';
|
||||||
import { getLoginErrorMessage, hasPermission } from '@/utils/auth';
|
import { getLoginErrorMessage, hasPermission } from '@/utils/auth';
|
||||||
@@ -241,11 +250,13 @@ const route = useRoute();
|
|||||||
const playerStore = usePlayerStore();
|
const playerStore = usePlayerStore();
|
||||||
const musicStore = useMusicStore();
|
const musicStore = useMusicStore();
|
||||||
const recommendStore = useRecommendStore();
|
const recommendStore = useRecommendStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
|
|
||||||
// 从路由参数或状态管理获取数据
|
// 从路由参数或状态管理获取数据
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const isDailyRecommend = computed(() => route.query.type === 'dailyRecommend');
|
const isDailyRecommend = computed(() => route.query.type === 'dailyRecommend');
|
||||||
|
const isAlbum = computed(() => route.query.type === 'album');
|
||||||
const name = computed(() => {
|
const name = computed(() => {
|
||||||
if (isDailyRecommend.value) {
|
if (isDailyRecommend.value) {
|
||||||
return t('comp.recommendSinger.songlist'); // 日推的标题
|
return t('comp.recommendSinger.songlist'); // 日推的标题
|
||||||
@@ -333,7 +344,7 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const getCoverImgUrl = computed(() => {
|
const getCoverImgUrl = computed(() => {
|
||||||
const coverImgUrl = listInfo.value?.coverImgUrl;
|
const coverImgUrl = listInfo.value?.coverImgUrl || listInfo.value?.picUrl;
|
||||||
if (coverImgUrl) {
|
if (coverImgUrl) {
|
||||||
return coverImgUrl;
|
return coverImgUrl;
|
||||||
}
|
}
|
||||||
@@ -801,19 +812,29 @@ const toggleLayout = () => {
|
|||||||
localStorage.setItem('musicListLayout', isCompactLayout.value ? 'compact' : 'normal');
|
localStorage.setItem('musicListLayout', isCompactLayout.value ? 'compact' : 'normal');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化歌单收藏状态
|
// 初始化收藏状态(支持歌单和专辑)
|
||||||
const checkCollectionStatus = () => {
|
const checkCollectionStatus = () => {
|
||||||
// 只有歌单类型才能收藏
|
const type = route.query.type as string;
|
||||||
if (route.query.type === 'playlist' && listInfo.value?.id) {
|
|
||||||
|
// 歌单类型的收藏检查
|
||||||
|
if (type === 'playlist' && listInfo.value?.id) {
|
||||||
canCollect.value = true;
|
canCollect.value = true;
|
||||||
// 检查是否已收藏
|
|
||||||
isCollected.value = listInfo.value.subscribed || false;
|
isCollected.value = listInfo.value.subscribed || false;
|
||||||
} else {
|
}
|
||||||
|
// 专辑类型的收藏检查 - 使用 store 判断
|
||||||
|
else if (type === 'album' && listInfo.value?.id) {
|
||||||
|
canCollect.value = true;
|
||||||
|
// 从 userStore 中判断是否已收藏
|
||||||
|
isCollected.value = userStore.isAlbumCollected(listInfo.value.id);
|
||||||
|
}
|
||||||
|
// 其他类型不支持收藏
|
||||||
|
else {
|
||||||
canCollect.value = false;
|
canCollect.value = false;
|
||||||
|
isCollected.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 切换收藏状态
|
// 切换收藏状态(支持歌单和专辑)
|
||||||
const toggleCollect = async () => {
|
const toggleCollect = async () => {
|
||||||
if (!listInfo.value?.id) return;
|
if (!listInfo.value?.id) return;
|
||||||
|
|
||||||
@@ -823,13 +844,23 @@ const toggleCollect = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const type = route.query.type as string;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
loadingList.value = true;
|
loadingList.value = true;
|
||||||
const tVal = isCollected.value ? 2 : 1; // 1:收藏, 2:取消收藏
|
const tVal = isCollected.value ? 2 : 1; // 1:收藏, 2:取消收藏
|
||||||
const response = await subscribePlaylist({
|
|
||||||
t: tVal,
|
// 根据类型调用不同的API
|
||||||
id: listInfo.value.id
|
const response =
|
||||||
});
|
type === 'album'
|
||||||
|
? await subscribeAlbum({
|
||||||
|
t: tVal,
|
||||||
|
id: listInfo.value.id
|
||||||
|
})
|
||||||
|
: await subscribePlaylist({
|
||||||
|
t: tVal,
|
||||||
|
id: listInfo.value.id
|
||||||
|
});
|
||||||
|
|
||||||
// 假设API返回格式是 { data: { code: number, msg?: string } }
|
// 假设API返回格式是 { data: { code: number, msg?: string } }
|
||||||
const res = response.data;
|
const res = response.data;
|
||||||
@@ -840,13 +871,25 @@ const toggleCollect = async () => {
|
|||||||
? 'comp.musicList.collectSuccess'
|
? 'comp.musicList.collectSuccess'
|
||||||
: 'comp.musicList.cancelCollectSuccess';
|
: 'comp.musicList.cancelCollectSuccess';
|
||||||
message.success(t(msgKey));
|
message.success(t(msgKey));
|
||||||
// 更新歌单信息
|
|
||||||
listInfo.value.subscribed = isCollected.value;
|
// 更新收藏状态
|
||||||
|
if (type === 'album') {
|
||||||
|
// 专辑:更新 store 中的收藏状态
|
||||||
|
if (isCollected.value) {
|
||||||
|
userStore.addCollectedAlbum(listInfo.value.id);
|
||||||
|
} else {
|
||||||
|
userStore.removeCollectedAlbum(listInfo.value.id);
|
||||||
|
}
|
||||||
|
(listInfo.value as any).isSub = isCollected.value;
|
||||||
|
} else {
|
||||||
|
// 歌单:更新 listInfo 的状态
|
||||||
|
listInfo.value.subscribed = isCollected.value;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res.msg || t('comp.musicList.operationFailed'));
|
throw new Error(res.msg || t('comp.musicList.operationFailed'));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('收藏歌单失败:', error);
|
console.error(`收藏${type === 'album' ? '专辑' : '歌单'}失败:`, error);
|
||||||
message.error(t('comp.musicList.operationFailed'));
|
message.error(t('comp.musicList.operationFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
loadingList.value = false;
|
loadingList.value = false;
|
||||||
|
|||||||
@@ -32,41 +32,50 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="uesr-signature">{{ userDetail.profile.signature }}</div>
|
<div class="uesr-signature">{{ userDetail.profile.signature }}</div>
|
||||||
<div class="play-list" :class="setAnimationClass('animate__fadeInLeft')">
|
<div class="play-list" :class="setAnimationClass('animate__fadeInLeft')">
|
||||||
<div class="title">
|
<div class="tab-container">
|
||||||
<div>{{ t('user.playlist.created') }}</div>
|
<n-tabs v-model:value="currentTab" type="segment" animated>
|
||||||
<div class="import-btn" @click="goToImportPlaylist" v-if="isElectron">
|
<n-tab v-for="tab in tabs" :key="tab.key" :name="tab.key" :tab="t(tab.label)">
|
||||||
{{ t('comp.playlist.import.button') }}
|
</n-tab>
|
||||||
</div>
|
</n-tabs>
|
||||||
</div>
|
</div>
|
||||||
<n-scrollbar>
|
<n-scrollbar>
|
||||||
<div
|
<div class="mt-4">
|
||||||
v-for="(item, index) in playList"
|
<button
|
||||||
:key="index"
|
class="play-list-item"
|
||||||
class="play-list-item"
|
@click="goToImportPlaylist"
|
||||||
@click="openPlaylist(item)"
|
v-if="isElectron && currentTab === 'created'"
|
||||||
>
|
>
|
||||||
<n-image
|
<div class="play-list-item-img"><i class="icon iconfont ri-add-line"></i></div>
|
||||||
:src="getImgUrl(item.coverImgUrl, '50y50')"
|
<div class="play-list-item-info">
|
||||||
class="play-list-item-img"
|
<div class="play-list-item-name">
|
||||||
lazy
|
{{ t('comp.playlist.import.button') }}
|
||||||
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="play-list-item-count">
|
</button>
|
||||||
{{ t('user.playlist.trackCount', { count: item.trackCount }) }},{{
|
<div
|
||||||
t('user.playlist.playCount', { count: item.playCount })
|
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>
|
</div>
|
||||||
|
<div class="pb-20"></div>
|
||||||
|
<play-bottom />
|
||||||
</div>
|
</div>
|
||||||
<div class="pb-20"></div>
|
|
||||||
<play-bottom />
|
|
||||||
</n-scrollbar>
|
</n-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -114,7 +123,7 @@ import { useI18n } from 'vue-i18n';
|
|||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { getListDetail } from '@/api/list';
|
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 { navigateToMusicList } from '@/components/common/MusicListNavigator';
|
||||||
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';
|
||||||
@@ -143,8 +152,70 @@ const list = ref<Playlist>();
|
|||||||
const listLoading = ref(false);
|
const listLoading = ref(false);
|
||||||
const message = useMessage();
|
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 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 = () => {
|
const goToImportPlaylist = () => {
|
||||||
router.push('/playlist/import');
|
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(
|
watch(
|
||||||
() => router.currentRoute.value.path,
|
() => 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(() => {
|
onMounted(() => {
|
||||||
checkLoginStatus() && loadData();
|
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 handlePlay = () => {
|
||||||
const tracks = recordList.value || [];
|
const tracks = recordList.value || [];
|
||||||
playerStore.setPlayList(tracks);
|
playerStore.setPlayList(tracks);
|
||||||
@@ -321,13 +453,7 @@ const currentLoginType = computed(() => userStore.loginType);
|
|||||||
.title {
|
.title {
|
||||||
@apply text-lg font-bold flex items-center justify-between;
|
@apply text-lg font-bold flex items-center justify-between;
|
||||||
@apply text-gray-900 dark:text-white;
|
@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 {
|
.user-name {
|
||||||
@apply text-xl font-bold mb-4 flex justify-between;
|
@apply text-xl font-bold mb-4 flex justify-between;
|
||||||
@apply text-white text-opacity-70;
|
@apply text-white text-opacity-70;
|
||||||
@@ -393,14 +519,15 @@ const currentLoginType = computed(() => userStore.loginType);
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-item {
|
&-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 transition-all duration-200;
|
||||||
@apply hover:bg-light-200 dark:hover:bg-dark-200;
|
@apply hover:bg-light-200 dark:hover:bg-dark-200;
|
||||||
|
|
||||||
&-img {
|
&-img {
|
||||||
width: 60px;
|
@apply flex items-center justify-center rounded-xl text-[40px] w-[60px] h-[60px] bg-light-300 dark:bg-dark-300;
|
||||||
height: 60px;
|
.iconfont {
|
||||||
@apply rounded-xl;
|
@apply text-[40px];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-info {
|
&-info {
|
||||||
@@ -442,4 +569,11 @@ const currentLoginType = computed(() => userStore.loginType);
|
|||||||
@apply flex justify-center items-center h-full w-full;
|
@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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user