Merge pull request #71 from algerkong/fix/music-list-play

Fix/music list play
This commit is contained in:
Alger
2025-03-02 22:49:00 +08:00
committed by GitHub
7 changed files with 213 additions and 66 deletions

View File

@@ -10,6 +10,7 @@ export default {
volumeDown: 'Volume Down',
mute: 'Mute',
unmute: 'Unmute',
songNum: 'Song Number: {num}',
playMode: {
sequence: 'Sequence',
loop: 'Loop',

View File

@@ -10,6 +10,7 @@ export default {
volumeDown: '音量减少',
mute: '静音',
unmute: '取消静音',
songNum: '歌曲总数:{num}',
playMode: {
sequence: '顺序播放',
loop: '循环播放',

View File

@@ -88,11 +88,15 @@ onMounted(() => {
homeRouter.filter((item) => item.meta.isMobile)
);
}
window.electron.ipcRenderer.on('set-language', handleSetLanguage);
if (isElectron) {
window.electron.ipcRenderer.on('set-language', handleSetLanguage);
}
});
onUnmounted(() => {
window.electron.ipcRenderer.removeListener('set-language', handleSetLanguage);
if (isElectron) {
window.electron.ipcRenderer.removeListener('set-language', handleSetLanguage);
}
});
</script>

View File

@@ -37,12 +37,12 @@
<n-avatar round :size="24" :src="getImgUrl(listInfo.creator.avatarUrl, '50y50')" />
<span class="creator-name">{{ listInfo.creator.nickname }}</span>
</div>
<div v-if="total" class="music-total">{{ t('player.songNum', { num: total }) }}</div>
<n-scrollbar style="max-height: 200">
<n-scrollbar style="max-height: 200px">
<div v-if="listInfo?.description" class="music-desc">
{{ listInfo.description }}
</div>
<play-bottom />
</n-scrollbar>
</div>
@@ -60,7 +60,7 @@
:style="getItemAnimationDelay(index)"
>
<song-item
:item="formatDetail(item)"
:item="formatSong(item)"
:can-remove="canRemove"
@play="handlePlay"
@remove-song="(id) => emit('remove-song', id)"
@@ -69,6 +69,9 @@
<div v-if="isLoadingMore" class="loading-more">
{{ t('common.loadingMore') }}
</div>
<div v-if="!hasMore" class="loading-more">
{{ t('common.noMore') }}
</div>
<play-bottom />
</div>
</n-spin>
@@ -82,6 +85,7 @@
</template>
<script setup lang="ts">
import { computed, onUnmounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useStore } from 'vuex';
@@ -119,10 +123,14 @@ const props = withDefaults(
const emit = defineEmits(['update:show', 'update:loading', 'remove-song']);
const page = ref(0);
const pageSize = 20;
const pageSize = 40;
const isLoadingMore = ref(false);
const displayedSongs = ref<any[]>([]);
const loadingList = ref(false);
const loadedIds = ref(new Set<number>()); // 用于追踪已加载的歌曲ID
const isPlaylistLoading = ref(false); // 标记是否正在加载播放列表
const completePlaylist = ref<any[]>([]); // 存储完整的播放列表
const hasMore = ref(true); // 标记是否还有更多数据可加载
// 计算总数
const total = computed(() => {
@@ -132,108 +140,229 @@ const total = computed(() => {
return props.songList.length;
});
const formatDetail = computed(() => (detail: any) => {
const song = {
artists: detail.ar,
name: detail.al.name,
id: detail.al.id
// 格式化歌曲数据
const formatSong = (item: any) => {
return {
...item,
picUrl: item.al?.picUrl || item.picUrl,
song: {
artists: item.ar || item.artists,
name: item.al?.name || item.name,
id: item.al?.id || item.id
}
};
};
detail.song = song;
detail.picUrl = detail.al.picUrl;
return detail;
});
/**
* 加载歌曲数据的核心函数
* @param ids 要加载的歌曲ID数组
* @param appendToList 是否将加载的歌曲追加到现有列表
* @param updateComplete 是否更新完整播放列表
*/
const loadSongs = async (ids: number[], appendToList = true, updateComplete = false) => {
if (ids.length === 0) return [];
const handlePlay = () => {
const tracks = props.songList || [];
store.commit(
'setPlayList',
tracks.map((item) => ({
...item,
picUrl: item.al.picUrl,
song: {
artists: item.ar
try {
const { data } = await getMusicDetail(ids);
if (data?.songs) {
// 更新已加载ID集合
const newSongs = data.songs.filter((song: any) => !loadedIds.value.has(song.id));
newSongs.forEach((song: any) => {
loadedIds.value.add(song.id);
});
if (appendToList) {
displayedSongs.value.push(...newSongs);
}
}))
);
if (updateComplete) {
completePlaylist.value.push(...newSongs);
}
return newSongs;
}
} catch (error) {
console.error('加载歌曲失败:', error);
}
return [];
};
// 加载完整播放列表
const loadFullPlaylist = async () => {
if (isPlaylistLoading.value) return;
isPlaylistLoading.value = true;
completePlaylist.value = [...displayedSongs.value]; // 先用当前已加载的歌曲初始化
try {
// 如果没有trackIds直接使用当前歌曲列表
if (!props.listInfo?.trackIds) {
return;
}
// 获取所有未加载的歌曲ID
const allIds = props.listInfo.trackIds.map((item) => item.id);
const unloadedIds = allIds.filter((id) => !loadedIds.value.has(id));
// 如果所有歌曲都已加载,直接返回
if (unloadedIds.length === 0) {
return;
}
// 分批加载未加载的歌曲
const batchSize = 500; // 每批加载的歌曲数量
for (let i = 0; i < unloadedIds.length; i += batchSize) {
const batchIds = unloadedIds.slice(i, i + batchSize);
if (batchIds.length === 0) continue;
await loadSongs(batchIds, false, true);
// 添加小延迟避免请求过于密集
if (i + batchSize < unloadedIds.length) {
// 使用 setTimeout 直接延迟,避免 Promise 相关的 linter 错误
await new Promise<void>((resolve) => {
setTimeout(() => resolve(), 300);
});
}
}
} catch (error) {
console.error('加载完整播放列表失败:', error);
} finally {
isPlaylistLoading.value = false;
}
};
// 处理播放
const handlePlay = async () => {
// 先使用当前已加载的歌曲开始播放
store.commit('setPlayList', displayedSongs.value.map(formatSong));
// 在后台加载完整播放列表
loadFullPlaylist().then(() => {
// 加载完成后,更新播放列表为完整列表
if (completePlaylist.value.length > 0) {
store.commit('setPlayList', completePlaylist.value.map(formatSong));
}
});
};
const close = () => {
emit('update:show', false);
};
// 优化加载更多歌曲的函数
// 加载更多歌曲
const loadMoreSongs = async () => {
if (isLoadingMore.value || displayedSongs.value.length >= total.value) return;
// 检查是否正在加载或已经加载完成
if (isLoadingMore.value || displayedSongs.value.length >= total.value) {
hasMore.value = false;
return;
}
isLoadingMore.value = true;
try {
if (props.listInfo?.trackIds) {
// 如果有 trackIds需要分批请求歌曲详情
const start = page.value * pageSize;
const end = Math.min((page.value + 1) * pageSize, total.value);
const trackIds = props.listInfo.trackIds.slice(start, end).map((item) => item.id);
const start = displayedSongs.value.length;
const end = Math.min(start + pageSize, total.value);
if (trackIds.length > 0) {
const { data } = await getMusicDetail(trackIds);
displayedSongs.value = [...displayedSongs.value, ...data.songs];
page.value++;
if (props.listInfo?.trackIds) {
// 获取这一批次需要加载的所有ID
const trackIdsToLoad = props.listInfo.trackIds
.slice(start, end)
.map((item) => item.id)
.filter((id) => !loadedIds.value.has(id));
if (trackIdsToLoad.length > 0) {
await loadSongs(trackIdsToLoad, true, false);
}
} else {
// 如果没有 trackIds直接使用 songList 分页
const start = page.value * pageSize;
const end = Math.min((page.value + 1) * pageSize, props.songList.length);
} else if (start < props.songList.length) {
// 直接使用 songList 分页
const newSongs = props.songList.slice(start, end);
displayedSongs.value = [...displayedSongs.value, ...newSongs];
page.value++;
newSongs.forEach((song) => {
if (!loadedIds.value.has(song.id)) {
loadedIds.value.add(song.id);
displayedSongs.value.push(song);
}
});
}
// 更新是否还有更多数据的状态
hasMore.value = displayedSongs.value.length < total.value;
} catch (error) {
console.error('加载歌曲失败:', error);
console.error('加载更多歌曲失败:', error);
} finally {
isLoadingMore.value = false;
loadingList.value = false;
}
};
const getItemAnimationDelay = (index: number) => {
const currentPageIndex = index % pageSize;
return setAnimationDelay(currentPageIndex, 20);
};
// 修改滚动处理函数
const handleScroll = (e: Event) => {
const target = e.target as HTMLElement;
if (!target) return;
const { scrollTop, scrollHeight, clientHeight } = target;
if (scrollHeight - scrollTop - clientHeight < 100 && !isLoadingMore.value) {
const threshold = 200;
if (
scrollHeight - scrollTop - clientHeight < threshold &&
!isLoadingMore.value &&
hasMore.value
) {
loadMoreSongs();
}
};
watch(
() => props.show,
(newVal) => {
loadingList.value = newVal;
if (!props.cover) {
loadingList.value = false;
}
}
);
const getItemAnimationDelay = (index: number) => {
const currentPageIndex = index % pageSize;
return setAnimationDelay(currentPageIndex, 20);
};
// 监听 songList 变化,重置分页状态
// 重置列表状态
const resetListState = () => {
page.value = 0;
loadedIds.value.clear();
displayedSongs.value = [];
completePlaylist.value = [];
hasMore.value = true;
loadingList.value = false;
};
// 初始化歌曲列表
const initSongList = (songs: any[]) => {
if (songs.length > 0) {
displayedSongs.value = [...songs];
songs.forEach((song) => loadedIds.value.add(song.id));
page.value = Math.ceil(songs.length / pageSize);
}
// 检查是否还有更多数据可加载
hasMore.value = displayedSongs.value.length < total.value;
};
// 修改 songList 监听器
watch(
() => props.songList,
(newSongs) => {
page.value = 0;
displayedSongs.value = newSongs.slice(0, pageSize);
if (newSongs.length > pageSize) {
page.value = 1;
// 重置所有状态
resetListState();
// 初始化歌曲列表
initSongList(newSongs);
// 如果还有更多歌曲需要加载,且差距较小,立即加载
if (hasMore.value && props.listInfo?.trackIds) {
setTimeout(() => {
loadMoreSongs();
}, 300);
}
loadingList.value = false;
},
{ immediate: true }
);
// 组件卸载时清理状态
onUnmounted(() => {
isPlaylistLoading.value = false;
});
</script>
<style scoped lang="scss">
@@ -242,6 +371,10 @@ watch(
@apply text-xl font-bold text-gray-900 dark:text-white;
}
&-total {
@apply text-sm font-normal text-gray-500 dark:text-gray-400;
}
&-page {
@apply px-8 w-full h-full bg-light dark:bg-black bg-opacity-75 dark:bg-opacity-75 rounded-t-2xl;
backdrop-filter: blur(20px);

View File

@@ -329,6 +329,7 @@ const actions = {
commit('setCurrentArtistId', id);
},
async initializeSystemFonts({ commit, state }) {
if (!isElectron) return;
// 如果已经有字体列表(不只是默认字体),则不重复获取
if (state.systemFonts.length > 1) return;

View File

@@ -59,6 +59,13 @@ export const formatNumber = (num: string | number) => {
};
export const getImgUrl = (url: string | undefined, size: string = '') => {
if (!url) return '';
if (url.includes('thumbnail')) {
// 只替换最后一个 thumbnail 参数的尺寸
return url.replace(/thumbnail=\d+y\d+(?!.*thumbnail)/, `thumbnail=${size}`);
}
const imgUrl = `${url}?param=${size}`;
return imgUrl;
};

View File

@@ -36,7 +36,7 @@
<div class="recommend-item-img">
<n-image
class="recommend-item-img-img"
:src="getImgUrl(item.picUrl || item.coverImgUrl, '200y200')"
:src="getImgUrl(item.picUrl || item.coverImgUrl, '300y300')"
width="200"
height="200"
lazy