mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-21 13:47:24 +08:00
✨ feat: 添加新的歌手详情页面
This commit is contained in:
Vendored
+1
@@ -8,6 +8,7 @@ export {}
|
|||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
|
NAlert: typeof import('naive-ui')['NAlert']
|
||||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||||
NBadge: typeof import('naive-ui')['NBadge']
|
NBadge: typeof import('naive-ui')['NBadge']
|
||||||
NButton: typeof import('naive-ui')['NButton']
|
NButton: typeof import('naive-ui')['NButton']
|
||||||
|
|||||||
@@ -92,8 +92,9 @@ import { computed, h, inject, ref, useTemplateRef } from 'vue';
|
|||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import { getSongUrl } from '@/hooks/MusicListHook';
|
import { getSongUrl } from '@/hooks/MusicListHook';
|
||||||
|
import { useArtist } from '@/hooks/useArtist';
|
||||||
import { audioService } from '@/services/audioService';
|
import { audioService } from '@/services/audioService';
|
||||||
import { usePlayerStore, useSettingsStore } from '@/store';
|
import { usePlayerStore } from '@/store';
|
||||||
import type { SongResult } from '@/type/music';
|
import type { SongResult } from '@/type/music';
|
||||||
import { getImgUrl, isElectron } from '@/utils';
|
import { getImgUrl, isElectron } from '@/utils';
|
||||||
import { getImageBackground } from '@/utils/linearColor';
|
import { getImageBackground } from '@/utils/linearColor';
|
||||||
@@ -121,7 +122,6 @@ const props = withDefaults(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const playerStore = usePlayerStore();
|
const playerStore = usePlayerStore();
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
|
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
|
|
||||||
@@ -142,6 +142,8 @@ const isDownloading = ref(false);
|
|||||||
|
|
||||||
const openPlaylistDrawer = inject<(songId: number) => void>('openPlaylistDrawer');
|
const openPlaylistDrawer = inject<(songId: number) => void>('openPlaylistDrawer');
|
||||||
|
|
||||||
|
const { navigateToArtist } = useArtist();
|
||||||
|
|
||||||
const renderSongPreview = () => {
|
const renderSongPreview = () => {
|
||||||
return h(
|
return h(
|
||||||
'div',
|
'div',
|
||||||
@@ -392,8 +394,7 @@ const toggleSelect = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleArtistClick = (id: number) => {
|
const handleArtistClick = (id: number) => {
|
||||||
settingsStore.setCurrentArtistId(id);
|
navigateToArtist(id);
|
||||||
settingsStore.setShowArtistDrawer(true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取歌手列表(最多显示5个)
|
// 获取歌手列表(最多显示5个)
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
class="recommend-singer-item relative"
|
class="recommend-singer-item relative"
|
||||||
:class="setAnimationClass('animate__backInRight')"
|
:class="setAnimationClass('animate__backInRight')"
|
||||||
:style="setAnimationDelay(index + 2, 100)"
|
:style="setAnimationDelay(index + 2, 100)"
|
||||||
@click="handleOpenSinger(item.id)"
|
@click="handleArtistClick(item.id)"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:style="setBackgroundImg(getImgUrl(item.picUrl, '500y500'))"
|
:style="setBackgroundImg(getImgUrl(item.picUrl, '500y500'))"
|
||||||
@@ -103,7 +103,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 播放按钮(hover时显示) -->
|
<!-- 播放按钮(hover时显示) -->
|
||||||
<div class="recommend-singer-item-play-overlay" @click.stop="handleOpenSinger(item.id)">
|
<div
|
||||||
|
class="recommend-singer-item-play-overlay"
|
||||||
|
@click.stop="handleArtistClick(item.id)"
|
||||||
|
>
|
||||||
<div class="recommend-singer-item-play-btn">
|
<div class="recommend-singer-item-play-btn">
|
||||||
<i class="iconfont icon-playfill text-4xl"></i>
|
<i class="iconfont icon-playfill text-4xl"></i>
|
||||||
</div>
|
</div>
|
||||||
@@ -140,7 +143,8 @@ import { getDayRecommend, getHotSinger } from '@/api/home';
|
|||||||
import { getListDetail } from '@/api/list';
|
import { getListDetail } from '@/api/list';
|
||||||
import { getUserPlaylist } from '@/api/user';
|
import { getUserPlaylist } from '@/api/user';
|
||||||
import MusicList from '@/components/MusicList.vue';
|
import MusicList from '@/components/MusicList.vue';
|
||||||
import { useSettingsStore, useUserStore } from '@/store';
|
import { useArtist } from '@/hooks/useArtist';
|
||||||
|
import { useUserStore } from '@/store';
|
||||||
import { IDayRecommend } from '@/type/day_recommend';
|
import { IDayRecommend } from '@/type/day_recommend';
|
||||||
import { Playlist } from '@/type/list';
|
import { Playlist } from '@/type/list';
|
||||||
import type { IListDetail } from '@/type/listDetail';
|
import type { IListDetail } from '@/type/listDetail';
|
||||||
@@ -169,6 +173,8 @@ const playlistLoading = ref(false);
|
|||||||
const playlistItem = ref<Playlist | null>(null);
|
const playlistItem = ref<Playlist | null>(null);
|
||||||
const playlistDetail = ref<IListDetail | null>(null);
|
const playlistDetail = ref<IListDetail | null>(null);
|
||||||
|
|
||||||
|
const { navigateToArtist } = useArtist();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取轮播项的样式
|
* 获取轮播项的样式
|
||||||
* @param index 项目索引(用于动画延迟)
|
* @param index 项目索引(用于动画延迟)
|
||||||
@@ -259,11 +265,8 @@ const loadData = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
const handleArtistClick = (id: number) => {
|
||||||
|
navigateToArtist(id);
|
||||||
const handleOpenSinger = (id: number) => {
|
|
||||||
settingsStore.setCurrentArtistId(id);
|
|
||||||
settingsStore.setShowArtistDrawer(true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const toPlaylist = async (id: number) => {
|
const toPlaylist = async (id: number) => {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
export const useArtist = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳转到歌手详情页
|
||||||
|
* @param id 歌手ID
|
||||||
|
*/
|
||||||
|
const navigateToArtist = (id: number) => {
|
||||||
|
router.push(`/artist/detail/${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
navigateToArtist
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -47,13 +47,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<install-app-modal v-if="!isElectron"></install-app-modal>
|
<install-app-modal v-if="!isElectron"></install-app-modal>
|
||||||
<update-modal v-if="isElectron" />
|
<update-modal v-if="isElectron" />
|
||||||
<artist-drawer ref="artistDrawerRef" :show="artistDrawerShow" />
|
|
||||||
<playlist-drawer v-model="showPlaylistDrawer" :song-id="currentSongId" />
|
<playlist-drawer v-model="showPlaylistDrawer" :song-id="currentSongId" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, defineAsyncComponent, nextTick, onMounted, provide, ref, watch } from 'vue';
|
import { computed, defineAsyncComponent, onMounted, provide, ref } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
import DownloadDrawer from '@/components/common/DownloadDrawer.vue';
|
import DownloadDrawer from '@/components/common/DownloadDrawer.vue';
|
||||||
@@ -82,7 +81,6 @@ const MobilePlayBar = defineAsyncComponent(() => import('./components/MobilePlay
|
|||||||
const SearchBar = defineAsyncComponent(() => import('./components/SearchBar.vue'));
|
const SearchBar = defineAsyncComponent(() => import('./components/SearchBar.vue'));
|
||||||
const TitleBar = defineAsyncComponent(() => import('./components/TitleBar.vue'));
|
const TitleBar = defineAsyncComponent(() => import('./components/TitleBar.vue'));
|
||||||
|
|
||||||
const ArtistDrawer = defineAsyncComponent(() => import('@/components/common/ArtistDrawer.vue'));
|
|
||||||
const PlaylistDrawer = defineAsyncComponent(() => import('@/components/common/PlaylistDrawer.vue'));
|
const PlaylistDrawer = defineAsyncComponent(() => import('@/components/common/PlaylistDrawer.vue'));
|
||||||
|
|
||||||
const playerStore = usePlayerStore();
|
const playerStore = usePlayerStore();
|
||||||
@@ -98,26 +96,6 @@ onMounted(() => {
|
|||||||
settingsStore.initializeTheme();
|
settingsStore.initializeTheme();
|
||||||
});
|
});
|
||||||
|
|
||||||
const artistDrawerRef = ref<InstanceType<typeof ArtistDrawer>>();
|
|
||||||
const artistDrawerShow = computed({
|
|
||||||
get: () => settingsStore.showArtistDrawer,
|
|
||||||
set: (val) => settingsStore.setShowArtistDrawer(val)
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听歌手ID变化
|
|
||||||
watch(
|
|
||||||
() => settingsStore.currentArtistId,
|
|
||||||
(newId) => {
|
|
||||||
if (newId) {
|
|
||||||
console.log('newId', newId);
|
|
||||||
artistDrawerShow.value = true;
|
|
||||||
nextTick(() => {
|
|
||||||
artistDrawerRef.value?.loadArtistInfo(newId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const showPlaylistDrawer = ref(false);
|
const showPlaylistDrawer = ref(false);
|
||||||
const currentSongId = ref<number | undefined>();
|
const currentSongId = ref<number | undefined>();
|
||||||
|
|
||||||
|
|||||||
@@ -142,7 +142,8 @@ import {
|
|||||||
textColors,
|
textColors,
|
||||||
useLyricProgress
|
useLyricProgress
|
||||||
} from '@/hooks/MusicHook';
|
} from '@/hooks/MusicHook';
|
||||||
import { usePlayerStore } from '@/store';
|
import { useArtist } from '@/hooks/useArtist';
|
||||||
|
import { usePlayerStore } from '@/store/modules/player';
|
||||||
import { useSettingsStore } from '@/store/modules/settings';
|
import { useSettingsStore } from '@/store/modules/settings';
|
||||||
import { getImgUrl, isMobile } from '@/utils';
|
import { getImgUrl, isMobile } from '@/utils';
|
||||||
import { animateGradient, getHoverBackgroundColor, getTextColors } from '@/utils/linearColor';
|
import { animateGradient, getHoverBackgroundColor, getTextColors } from '@/utils/linearColor';
|
||||||
@@ -375,9 +376,11 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
|
const { navigateToArtist } = useArtist();
|
||||||
|
|
||||||
const handleArtistClick = (id: number) => {
|
const handleArtistClick = (id: number) => {
|
||||||
isVisible.value = false;
|
isVisible.value = false;
|
||||||
settingsStore.currentArtistId = id;
|
navigateToArtist(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setData = computed(() => settingsStore.setData);
|
const setData = computed(() => settingsStore.setData);
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ import {
|
|||||||
sound,
|
sound,
|
||||||
textColors
|
textColors
|
||||||
} from '@/hooks/MusicHook';
|
} from '@/hooks/MusicHook';
|
||||||
|
import { useArtist } from '@/hooks/useArtist';
|
||||||
import { audioService } from '@/services/audioService';
|
import { audioService } from '@/services/audioService';
|
||||||
import { usePlayerStore } from '@/store/modules/player';
|
import { usePlayerStore } from '@/store/modules/player';
|
||||||
import { useSettingsStore } from '@/store/modules/settings';
|
import { useSettingsStore } from '@/store/modules/settings';
|
||||||
@@ -381,9 +382,11 @@ const openLyricWindow = () => {
|
|||||||
openLyric();
|
openLyric();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { navigateToArtist } = useArtist();
|
||||||
|
|
||||||
const handleArtistClick = (id: number) => {
|
const handleArtistClick = (id: number) => {
|
||||||
musicFullVisible.value = false;
|
musicFullVisible.value = false;
|
||||||
settingsStore.currentArtistId = id;
|
navigateToArtist(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加全局快捷键处理
|
// 添加全局快捷键处理
|
||||||
|
|||||||
@@ -31,6 +31,17 @@ const otherRouter = [
|
|||||||
back: true
|
back: true
|
||||||
},
|
},
|
||||||
component: () => import('@/views/user/detail.vue')
|
component: () => import('@/views/user/detail.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/artist/detail/:id',
|
||||||
|
name: 'artistDetail',
|
||||||
|
meta: {
|
||||||
|
title: '歌手详情',
|
||||||
|
keepAlive: true,
|
||||||
|
showInMenu: false,
|
||||||
|
back: true
|
||||||
|
},
|
||||||
|
component: () => import('@/views/artist/detail.vue')
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
export default otherRouter;
|
export default otherRouter;
|
||||||
|
|||||||
@@ -0,0 +1,334 @@
|
|||||||
|
<template>
|
||||||
|
<n-scrollbar v-loading="loading" class="artist-page">
|
||||||
|
<!-- 歌手信息头部 -->
|
||||||
|
<div class="artist-header">
|
||||||
|
<div class="artist-cover">
|
||||||
|
<n-image
|
||||||
|
:src="getImgUrl(artistInfo?.avatar, '300y300')"
|
||||||
|
class="artist-avatar"
|
||||||
|
preview-disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="artist-info">
|
||||||
|
<h1 class="artist-name">{{ artistInfo?.name }}</h1>
|
||||||
|
<div v-if="artistInfo?.alias?.length" class="artist-alias">
|
||||||
|
{{ artistInfo.alias.join(' / ') }}
|
||||||
|
</div>
|
||||||
|
<div v-if="artistInfo?.briefDesc" class="artist-desc">
|
||||||
|
{{ artistInfo.briefDesc }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 标签页切换 -->
|
||||||
|
<n-tabs v-model:value="activeTab" class="content-tabs" type="line" animated>
|
||||||
|
<n-tab-pane name="songs" :tab="t('artist.hotSongs')">
|
||||||
|
<div class="songs-list">
|
||||||
|
<div class="song-list-content">
|
||||||
|
<song-item
|
||||||
|
v-for="song in songs"
|
||||||
|
:key="song.id"
|
||||||
|
:item="song"
|
||||||
|
:list="true"
|
||||||
|
@play="handlePlay"
|
||||||
|
/>
|
||||||
|
<div v-if="songLoading" class="loading-more">{{ t('common.loading') }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-tab-pane>
|
||||||
|
|
||||||
|
<n-tab-pane name="albums" :tab="t('artist.albums')">
|
||||||
|
<div class="albums-list">
|
||||||
|
<div class="albums-grid">
|
||||||
|
<search-item
|
||||||
|
v-for="album in albums"
|
||||||
|
:key="album.id"
|
||||||
|
shape="square"
|
||||||
|
:item="{
|
||||||
|
id: album.id,
|
||||||
|
picUrl: album.picUrl,
|
||||||
|
name: album.name,
|
||||||
|
desc: formatPublishTime(album.publishTime),
|
||||||
|
size: album.size,
|
||||||
|
type: '专辑'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
<div v-if="albumLoading" class="loading-more">{{ t('common.loading') }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-tab-pane>
|
||||||
|
|
||||||
|
<n-tab-pane name="about" :tab="t('artist.description')">
|
||||||
|
<div class="artist-description">
|
||||||
|
<div class="description-content" v-html="artistInfo?.briefDesc"></div>
|
||||||
|
</div>
|
||||||
|
</n-tab-pane>
|
||||||
|
</n-tabs>
|
||||||
|
|
||||||
|
<play-bottom />
|
||||||
|
</n-scrollbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useDateFormat, useThrottleFn } from '@vueuse/core';
|
||||||
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
import { getArtistAlbums, getArtistDetail, getArtistTopSongs } from '@/api/artist';
|
||||||
|
import { getMusicDetail } from '@/api/music';
|
||||||
|
import PlayBottom from '@/components/common/PlayBottom.vue';
|
||||||
|
import SearchItem from '@/components/common/SearchItem.vue';
|
||||||
|
import SongItem from '@/components/common/SongItem.vue';
|
||||||
|
import { usePlayerStore } from '@/store';
|
||||||
|
import { IArtist } from '@/type/artist';
|
||||||
|
import { getImgUrl } from '@/utils';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const route = useRoute();
|
||||||
|
const playerStore = usePlayerStore();
|
||||||
|
|
||||||
|
const artistId = computed(() => Number(route.params.id));
|
||||||
|
const activeTab = ref('songs');
|
||||||
|
|
||||||
|
// 歌手信息
|
||||||
|
const artistInfo = ref<IArtist>();
|
||||||
|
const songs = ref<any[]>([]);
|
||||||
|
const albums = ref<any[]>([]);
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
const loading = ref(false);
|
||||||
|
const songLoading = ref(false);
|
||||||
|
const albumLoading = ref(false);
|
||||||
|
|
||||||
|
// 分页参数
|
||||||
|
const songPage = ref({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 30,
|
||||||
|
hasMore: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const albumPage = ref({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 30,
|
||||||
|
hasMore: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载歌手信息
|
||||||
|
const loadArtistInfo = async () => {
|
||||||
|
if (!artistId.value) return;
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const info = await getArtistDetail(artistId.value);
|
||||||
|
if (info.data?.data?.artist) {
|
||||||
|
artistInfo.value = info.data.data.artist;
|
||||||
|
}
|
||||||
|
// 重置分页并加载初始数据
|
||||||
|
resetPagination();
|
||||||
|
await Promise.all([loadSongs(), loadAlbums()]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载歌手信息失败:', error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置分页
|
||||||
|
const resetPagination = () => {
|
||||||
|
songPage.value = {
|
||||||
|
page: 1,
|
||||||
|
pageSize: 30,
|
||||||
|
hasMore: true
|
||||||
|
};
|
||||||
|
albumPage.value = {
|
||||||
|
page: 1,
|
||||||
|
pageSize: 30,
|
||||||
|
hasMore: true
|
||||||
|
};
|
||||||
|
songs.value = [];
|
||||||
|
albums.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载歌曲
|
||||||
|
const loadSongs = async () => {
|
||||||
|
if (!artistId.value || !songPage.value.hasMore || songLoading.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
songLoading.value = true;
|
||||||
|
const { page, pageSize } = songPage.value;
|
||||||
|
const res = await getArtistTopSongs({
|
||||||
|
id: artistId.value,
|
||||||
|
limit: pageSize,
|
||||||
|
offset: (page - 1) * pageSize
|
||||||
|
});
|
||||||
|
|
||||||
|
const ids = res.data.songs.map((item) => item.id);
|
||||||
|
const songsDetail = await getMusicDetail(ids);
|
||||||
|
|
||||||
|
if (songsDetail.data?.songs) {
|
||||||
|
const newSongs = songsDetail.data.songs.map((item) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
picUrl: item.al.picUrl,
|
||||||
|
song: {
|
||||||
|
artists: item.ar,
|
||||||
|
name: item.name,
|
||||||
|
id: item.id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
songs.value = page === 1 ? newSongs : [...songs.value, ...newSongs];
|
||||||
|
songPage.value.hasMore = newSongs.length === pageSize;
|
||||||
|
songPage.value.page++;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载歌曲失败:', error);
|
||||||
|
} finally {
|
||||||
|
songLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载专辑
|
||||||
|
const loadAlbums = async () => {
|
||||||
|
if (!artistId.value || !albumPage.value.hasMore || albumLoading.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
albumLoading.value = true;
|
||||||
|
const { page, pageSize } = albumPage.value;
|
||||||
|
const res = await getArtistAlbums({
|
||||||
|
id: artistId.value,
|
||||||
|
limit: pageSize,
|
||||||
|
offset: (page - 1) * pageSize
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.data?.hotAlbums) {
|
||||||
|
const newAlbums = res.data.hotAlbums;
|
||||||
|
albums.value = page === 1 ? newAlbums : [...albums.value, ...newAlbums];
|
||||||
|
albumPage.value.hasMore = newAlbums.length === pageSize;
|
||||||
|
albumPage.value.page++;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载专辑失败:', error);
|
||||||
|
} finally {
|
||||||
|
albumLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化发布时间
|
||||||
|
const formatPublishTime = (time: number) => {
|
||||||
|
return useDateFormat(time, 'YYYY-MM-DD').value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePlay = () => {
|
||||||
|
playerStore.setPlayList(
|
||||||
|
songs.value.map((item) => ({
|
||||||
|
...item,
|
||||||
|
picUrl: item.al.picUrl
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加滚动处理函数
|
||||||
|
const handleScroll = useThrottleFn(() => {
|
||||||
|
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||||||
|
const windowHeight = window.innerHeight;
|
||||||
|
const documentHeight = document.documentElement.scrollHeight;
|
||||||
|
|
||||||
|
if (documentHeight - (scrollTop + windowHeight) < 100) {
|
||||||
|
if (activeTab.value === 'songs') {
|
||||||
|
loadSongs();
|
||||||
|
} else if (activeTab.value === 'albums') {
|
||||||
|
loadAlbums();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
// 监听页面滚动
|
||||||
|
onMounted(() => {
|
||||||
|
loadArtistInfo();
|
||||||
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('scroll', handleScroll);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听路由参数变化
|
||||||
|
watch(
|
||||||
|
() => route.params.id,
|
||||||
|
(newId) => {
|
||||||
|
if (newId) {
|
||||||
|
loadArtistInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.artist-page {
|
||||||
|
@apply min-h-screen w-full bg-light dark:bg-dark pb-24;
|
||||||
|
|
||||||
|
.nav-header {
|
||||||
|
@apply flex items-center px-4 py-3 sticky top-0 bg-light dark:bg-dark z-10;
|
||||||
|
|
||||||
|
i {
|
||||||
|
@apply text-xl mr-4 cursor-pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
@apply text-base font-medium truncate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.artist-header {
|
||||||
|
@apply flex flex-col md:flex-row gap-4 md:gap-6 px-4 pb-4;
|
||||||
|
|
||||||
|
.artist-cover {
|
||||||
|
@apply flex justify-center md:justify-start;
|
||||||
|
|
||||||
|
.artist-avatar {
|
||||||
|
@apply w-40 h-40 md:w-48 md:h-48 rounded-2xl object-cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.artist-info {
|
||||||
|
@apply flex-1;
|
||||||
|
|
||||||
|
.artist-name {
|
||||||
|
@apply text-2xl md:text-4xl font-bold mb-2 text-center md:text-left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.artist-alias {
|
||||||
|
@apply text-gray-500 dark:text-gray-400 mb-2 text-center md:text-left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.artist-desc {
|
||||||
|
@apply text-sm text-gray-600 dark:text-gray-300 line-clamp-3 text-center md:text-left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-tabs {
|
||||||
|
@apply px-4;
|
||||||
|
|
||||||
|
:deep(.n-tabs-nav) {
|
||||||
|
@apply sticky top-0 bg-light dark:bg-dark z-10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.albums-grid {
|
||||||
|
@apply grid gap-6 grid-cols-2 sm:grid-cols-3 md:grid-cols-5 lg:grid-cols-6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-more {
|
||||||
|
@apply text-center py-4 text-gray-500 dark:text-gray-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.artist-description {
|
||||||
|
.description-content {
|
||||||
|
@apply text-sm leading-relaxed whitespace-pre-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user