feat: 优化列表渲染

This commit is contained in:
alger
2024-12-04 20:38:26 +08:00
parent f6923b4c47
commit c5e7c87658
4 changed files with 115 additions and 66 deletions
+92 -60
View File
@@ -10,77 +10,69 @@
> >
<div class="music-page"> <div class="music-page">
<div class="music-close"> <div class="music-close">
<i class="icon ri-layout-column-line" @click="doubleDisply = !doubleDisply"></i>
<i class="icon iconfont icon-icon_error" @click="close"></i> <i class="icon iconfont icon-icon_error" @click="close"></i>
</div> </div>
<div class="music-title text-el">{{ name }}</div> <div class="music-title text-el">{{ name }}</div>
<!-- 歌单歌曲列表 --> <!-- 歌单歌曲列表 -->
<div class="music-list"> <div v-loading="loading" class="music-list">
<n-scrollbar @scroll="handleScroll"> <n-virtual-list
<div v-if="displayedSongs.length"
v-loading="loading || !songList.length" ref="virtualListRef"
class="music-list-content" :items="displayedSongs"
:class="{ 'double-list': doubleDisply }" :item-size="60"
> :keep-alive="true"
<div :min-size="5"
v-for="(item, index) in displayedSongs" :style="{ height: listHeight }"
:key="item.id" @scroll="handleScroll"
class="double-item" >
:class="setAnimationClass('animate__bounceInUp')" <template #default="{ item }">
:style="setAnimationDelay(index, 5)" <song-item :item="formatDetail(item)" @play="handlePlay" />
>
<song-item :item="formatDetail(item)" @play="handlePlay" />
</div>
<div v-if="isLoadingMore" class="loading-more">加载更多...</div>
</div>
<play-bottom />
</n-scrollbar>
<!-- <n-virtual-list :item-size="42" :items="displayedSongs" item-resizable @scroll="handleScroll">
<template #default="{ item, index }">
<div :key="item.id" class="double-item">
<song-item :item="formatDetail(item)" @play="handlePlay" />
</div>
</template> </template>
</n-virtual-list> </n-virtual-list>
<play-bottom /> --> <div v-else-if="loading" class="loading-more">加载中...</div>
<play-bottom />
</div> </div>
</div> </div>
</n-drawer> </n-drawer>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// 导入 NVirtualListInst 类型
import type { VirtualListInst } from 'naive-ui';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
import { getMusicDetail } from '@/api/music'; import { getMusicDetail } from '@/api/music';
import SongItem from '@/components/common/SongItem.vue'; import SongItem from '@/components/common/SongItem.vue';
import { isMobile, setAnimationClass, setAnimationDelay } from '@/utils'; import { isMobile } from '@/utils';
import PlayBottom from './common/PlayBottom.vue'; import PlayBottom from './common/PlayBottom.vue';
const store = useStore(); const store = useStore();
const { const props = defineProps<{
songList,
loading = false,
listInfo,
} = defineProps<{
show: boolean; show: boolean;
name: string; name: string;
songList: any[]; songList: any[];
loading?: boolean; loading?: boolean;
listInfo?: any; listInfo?: {
trackIds: { id: number }[];
[key: string]: any;
};
}>(); }>();
const emit = defineEmits(['update:show', 'update:loading']); const emit = defineEmits(['update:show', 'update:loading']);
const page = ref(0); const page = ref(0);
const pageSize = 20; const pageSize = 20;
const total = ref(0);
const isLoadingMore = ref(false); const isLoadingMore = ref(false);
const displayedSongs = ref<any[]>([]); const displayedSongs = ref<any[]>([]);
// 双排显示开关 // 计算总数
const doubleDisply = ref(false); const total = computed(() => {
if (props.listInfo?.trackIds) {
return props.listInfo.trackIds.length;
}
return props.songList.length;
});
const formatDetail = computed(() => (detail: any) => { const formatDetail = computed(() => (detail: any) => {
const song = { const song = {
@@ -95,7 +87,7 @@ const formatDetail = computed(() => (detail: any) => {
}); });
const handlePlay = () => { const handlePlay = () => {
const tracks = songList || []; const tracks = props.songList || [];
store.commit( store.commit(
'setPlayList', 'setPlayList',
tracks.map((item) => ({ tracks.map((item) => ({
@@ -112,40 +104,70 @@ const close = () => {
emit('update:show', false); emit('update:show', false);
}; };
// 优化加载更多歌曲的函数
const loadMoreSongs = async () => { const loadMoreSongs = async () => {
if (displayedSongs.value.length >= total.value) return; if (isLoadingMore.value || displayedSongs.value.length >= total.value) return;
isLoadingMore.value = true; isLoadingMore.value = true;
try { try {
const trackIds = listInfo.trackIds if (props.listInfo?.trackIds) {
.slice(page.value * pageSize, (page.value + 1) * pageSize) // 如果有 trackIds,需要分批请求歌曲详情
.map((item: any) => item.id); const start = page.value * pageSize;
const reslist = await getMusicDetail(trackIds); const end = Math.min((page.value + 1) * pageSize, total.value);
// displayedSongs.value = displayedSongs.value.concat(reslist.data.songs); const trackIds = props.listInfo.trackIds.slice(start, end).map((item) => item.id);
displayedSongs.value = JSON.parse(JSON.stringify([...displayedSongs.value, ...reslist.data.songs]));
page.value++; if (trackIds.length > 0) {
const { data } = await getMusicDetail(trackIds);
displayedSongs.value = [...displayedSongs.value, ...data.songs];
page.value++;
}
} else {
// 如果没有 trackIds,直接使用 songList 分页
const start = page.value * pageSize;
const end = Math.min((page.value + 1) * pageSize, props.songList.length);
const newSongs = props.songList.slice(start, end);
displayedSongs.value = [...displayedSongs.value, ...newSongs];
page.value++;
}
} catch (error) { } catch (error) {
console.error('error', error); console.error('加载歌曲失败:', error);
} finally { } finally {
isLoadingMore.value = false; isLoadingMore.value = false;
} }
}; };
const handleScroll = (e: any) => { // 添加虚拟列表的引用
const { scrollTop, scrollHeight, clientHeight } = e.target; const virtualListRef = ref<VirtualListInst | null>(null);
if (scrollTop + clientHeight >= scrollHeight - 50 && !isLoadingMore.value) {
// 修改滚动处理函数
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) {
loadMoreSongs(); loadMoreSongs();
} }
}; };
// 监听 songList 变化,重置分页状态
watch( watch(
() => songList, () => props.songList,
(newSongs) => { (newSongs) => {
displayedSongs.value = JSON.parse(JSON.stringify(newSongs)); page.value = 0;
total.value = listInfo ? listInfo.trackIds.length : displayedSongs.value.length; displayedSongs.value = newSongs.slice(0, pageSize);
if (newSongs.length > pageSize) {
page.value = 1;
}
}, },
{ deep: true, immediate: true }, { immediate: true },
); );
// 添加计算属性来处理列表高度
const listHeight = computed(() => {
const baseHeight = '100%'; // 减去标题高度
return store.state.isPlay ? `calc(100% - 90px)` : baseHeight; // 112px 是 PlayBottom 的高度
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -168,9 +190,13 @@ watch(
&-list { &-list {
height: calc(100% - 60px); height: calc(100% - 60px);
position: relative; // 添加相对定位
&-content { :deep(.n-virtual-list__scroll) {
min-height: 400px; scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
} }
} }
} }
@@ -186,14 +212,20 @@ watch(
} }
.double-list { .double-list {
@apply flex flex-wrap gap-5;
.double-item { .double-item {
width: calc(50% - 10px); width: 100%;
} }
.song-item { .song-item {
background-color: #191919; background-color: #191919;
} }
} }
// 确保 PlayBottom 不会影响滚动区域
:deep(.bottom) {
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
</style> </style>
+1 -1
View File
@@ -15,7 +15,7 @@ import directives from './directive';
const app = createApp(App); const app = createApp(App);
Object.keys(directives).forEach((key: string) => { Object.keys(directives).forEach((key: string) => {
app.directive(key, directives[key]); app.directive(key, directives[key as keyof typeof directives]);
}); });
app.use(router); app.use(router);
app.use(store); app.use(store);
+1 -1
View File
@@ -96,7 +96,7 @@ const handleScroll = (e: any) => {
// 监听窗口大小变化,调整每行显示数量 // 监听窗口大小变化,调整每行显示数量
const updateItemsPerRow = () => { const updateItemsPerRow = () => {
const width = window.innerWidth; const width = window.innerWidth;
if (width > 1800) ITEMS_PER_ROW.value = 10; if (width > 1800) ITEMS_PER_ROW.value = 8;
else if (width > 1200) ITEMS_PER_ROW.value = 8; else if (width > 1200) ITEMS_PER_ROW.value = 8;
else if (width > 768) ITEMS_PER_ROW.value = 6; else if (width > 768) ITEMS_PER_ROW.value = 6;
else ITEMS_PER_ROW.value = 5; else ITEMS_PER_ROW.value = 5;
+21 -4
View File
@@ -57,12 +57,18 @@ onActivated(() => {
const isShowList = ref(false); const isShowList = ref(false);
const list = ref<Playlist>(); const list = ref<Playlist>();
const listLoading = ref(false);
// 展示歌单 // 展示歌单
const showPlaylist = async (id: number) => { const showPlaylist = async (id: number, name: string) => {
isShowList.value = true; isShowList.value = true;
list.value = {}; listLoading.value = true;
list.value = {
name,
} as Playlist;
const { data } = await getListDetail(id); const { data } = await getListDetail(id);
list.value = data.playlist; list.value = data.playlist;
listLoading.value = false;
}; };
const handlePlay = () => { const handlePlay = () => {
@@ -103,7 +109,12 @@ const handlePlay = () => {
<div class="play-list" :class="setAnimationClass('animate__fadeInLeft')"> <div class="play-list" :class="setAnimationClass('animate__fadeInLeft')">
<div class="title">创建的歌单</div> <div class="title">创建的歌单</div>
<n-scrollbar> <n-scrollbar>
<div v-for="(item, index) in playList" :key="index" class="play-list-item" @click="showPlaylist(item.id)"> <div
v-for="(item, index) in playList"
:key="index"
class="play-list-item"
@click="showPlaylist(item.id, item.name)"
>
<n-image :src="getImgUrl(item.coverImgUrl, '50y50')" class="play-list-item-img" lazy preview-disabled /> <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-info">
<div class="play-list-item-name">{{ item.name }}</div> <div class="play-list-item-name">{{ item.name }}</div>
@@ -133,7 +144,13 @@ const handlePlay = () => {
</n-scrollbar> </n-scrollbar>
</div> </div>
</div> </div>
<music-list v-model:show="isShowList" :name="list?.name || ''" :song-list="list?.tracks || []" :list-info="list" /> <music-list
v-model:show="isShowList"
:name="list?.name || ''"
:song-list="list?.tracks || []"
:list-info="list"
:loading="listLoading"
/>
</div> </div>
</template> </template>