diff --git a/src/renderer/views/artist/detail.vue b/src/renderer/views/artist/detail.vue index a41563b..194246b 100644 --- a/src/renderer/views/artist/detail.vue +++ b/src/renderer/views/artist/detail.vue @@ -23,15 +23,103 @@ + +
+
+ + + {{ t('comp.musicList.playAll') }} + + + + + {{ t('comp.musicList.addToPlaylist') }} + +
+ +
+ +
+ + + {{ isCompactLayout ? t('comp.musicList.switchToNormal') : t('comp.musicList.switchToCompact') }} + +
+ + +
+ + +
+
+
+
- +
+ {{ t('comp.musicList.noSearchResults') }} +
+ + + + + +
{{ t('common.loading') }}
@@ -76,6 +164,8 @@ import { useDateFormat } from '@vueuse/core'; import { computed, onMounted, onUnmounted, ref, watch, nextTick, onActivated, onDeactivated } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; +import PinyinMatch from 'pinyin-match'; +import { useMessage } from 'naive-ui'; import { getArtistAlbums, getArtistDetail, getArtistTopSongs } from '@/api/artist'; import { getMusicDetail } from '@/api/music'; @@ -84,7 +174,7 @@ 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'; +import { getImgUrl, isMobile } from '@/utils'; defineOptions({ name: 'ArtistDetail' @@ -93,6 +183,7 @@ defineOptions({ const { t } = useI18n(); const route = useRoute(); const playerStore = usePlayerStore(); +const message = useMessage(); const artistId = computed(() => Number(route.params.id)); const activeTab = ref('songs'); @@ -135,6 +226,11 @@ const artistDataCache = new Map(); // 单个缓存键函数 const getCacheKey = (id: string | number) => `artist_${id}`; +// 搜索和布局相关 +const searchKeyword = ref(''); +const isSearchVisible = ref(false); +const isCompactLayout = ref(isMobile.value ? false : localStorage.getItem('musicListLayout') === 'compact'); + // 加载歌手信息 const loadArtistInfo = async () => { if (!artistId.value) return; @@ -268,13 +364,151 @@ 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 showSearch = () => { + isSearchVisible.value = true; + // 添加一个小延迟后聚焦搜索框 + nextTick(() => { + const inputEl = document.querySelector('.search-container input'); + if (inputEl) { + (inputEl as HTMLInputElement).focus(); + } + }); +}; + +const closeSearch = () => { + isSearchVisible.value = false; + searchKeyword.value = ''; +}; + +const handleSearchBlur = () => { + // 如果搜索框为空,则在失焦时关闭搜索框 + if (!searchKeyword.value) { + setTimeout(() => { + isSearchVisible.value = false; + }, 200); + } +}; + +// 过滤歌曲列表 +const filteredSongs = computed(() => { + if (!searchKeyword.value) { + return songs.value; + } + + const keyword = searchKeyword.value.toLowerCase().trim(); + return songs.value.filter((song) => { + const songName = song.name?.toLowerCase() || ''; + const albumName = song.al?.name?.toLowerCase() || ''; + const artists = song.ar || song.artists || []; + + // 原始文本匹配 + const nameMatch = songName.includes(keyword); + const albumMatch = albumName.includes(keyword); + const artistsMatch = artists.some((artist: any) => { + return artist.name?.toLowerCase().includes(keyword); + }); + + // 拼音匹配 + const namePinyinMatch = song.name && PinyinMatch.match(song.name, keyword); + const albumPinyinMatch = song.al?.name && PinyinMatch.match(song.al.name, keyword); + const artistsPinyinMatch = artists.some((artist: any) => { + return artist.name && PinyinMatch.match(artist.name, keyword); + }); + + return ( + nameMatch || + albumMatch || + artistsMatch || + namePinyinMatch || + albumPinyinMatch || + artistsPinyinMatch + ); + }); +}); + +// 布局切换 +const toggleLayout = () => { + isCompactLayout.value = !isCompactLayout.value; + localStorage.setItem('musicListLayout', isCompactLayout.value ? 'compact' : 'normal'); +}; + +// 播放全部 +const handlePlayAll = () => { + if (filteredSongs.value.length === 0) return; + + playerStore.setPlayList(filteredSongs.value.map(song => ({ + ...song, + picUrl: song.al.picUrl + }))); + + // 开始播放第一首 + playerStore.setPlay(filteredSongs.value[0]); + + message.success(t('comp.musicList.playAll')); +}; + +// 添加到播放列表 +const addToPlaylist = () => { + if (filteredSongs.value.length === 0) return; + + // 获取当前播放列表 + const currentList = playerStore.playList; + + // 添加歌曲到播放列表(避免重复添加) + const newSongs = filteredSongs.value.filter(song => + !currentList.some(item => item.id === song.id) ); + + if (newSongs.length === 0) { + message.info(t('comp.musicList.songsAlreadyInPlaylist')); + return; + } + + // 合并到当前播放列表末尾 + const newList = [ + ...currentList, + ...newSongs.map(song => ({ + ...song, + picUrl: song.al.picUrl + })) + ]; + + playerStore.setPlayList(newList); + + message.success(t('comp.musicList.addToPlaylistSuccess', { count: newSongs.length })); +}; + +const handlePlay = (song?: any) => { + // 如果传入了特定歌曲(点击单曲播放),则将其作为播放列表的第一首 + if (song) { + const songList = [...filteredSongs.value]; + const index = songList.findIndex(item => item.id === song.id); + + if (index !== -1) { + // 将点击的歌曲移到第一位 + const clickedSong = songList.splice(index, 1)[0]; + songList.unshift(clickedSong); + } + + playerStore.setPlayList( + songList.map(item => ({ + ...item, + picUrl: item.al?.picUrl || item.picUrl + })) + ); + + // 设置当前播放歌曲 + playerStore.setPlay(song); + } else { + // 默认行为:播放整个过滤后的列表 + playerStore.setPlayList( + filteredSongs.value.map(item => ({ + ...item, + picUrl: item.al?.picUrl || item.picUrl + })) + ); + } }; // 简化观察器设置 @@ -326,6 +560,13 @@ watch([songsLoadMoreRef, albumsLoadMoreRef], () => { setupObservers(); }); +// 搜索词变化时重新设置观察器 +watch(searchKeyword, () => { + nextTick(() => { + setupObservers(); + }); +}); + onActivated(() => { // 确保当前路由是艺术家详情页 if (route.name === 'artistDetail') { @@ -370,6 +611,37 @@ onUnmounted(() => { albumsObserver = null; } }); + +// 定义在script setup部分 +const songListRef = ref(null); + +// 格式化歌曲(使用在虚拟列表中) +const formatSong = (item: any) => { + if (!item) { + return null; + } + return { + ...item, + picUrl: item.al?.picUrl || item.picUrl + }; +}; + +// 处理虚拟列表滚动 +const handleVirtualScroll = (e: any) => { + if (!e || !e.target) return; + + const { scrollTop, scrollHeight, clientHeight } = e.target; + const threshold = 200; + + if ( + scrollHeight - scrollTop - clientHeight < threshold && + !songLoading.value && + songPage.value.hasMore && + !searchKeyword.value // 搜索状态下不触发加载更多 + ) { + loadSongs(); + } +};