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-close">
<i class="icon ri-layout-column-line" @click="doubleDisply = !doubleDisply"></i>
<i class="icon iconfont icon-icon_error" @click="close"></i>
</div>
<div class="music-title text-el">{{ name }}</div>
<!-- 歌单歌曲列表 -->
<div class="music-list">
<n-scrollbar @scroll="handleScroll">
<div
v-loading="loading || !songList.length"
class="music-list-content"
:class="{ 'double-list': doubleDisply }"
>
<div
v-for="(item, index) in displayedSongs"
:key="item.id"
class="double-item"
:class="setAnimationClass('animate__bounceInUp')"
:style="setAnimationDelay(index, 5)"
>
<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>
<div v-loading="loading" class="music-list">
<n-virtual-list
v-if="displayedSongs.length"
ref="virtualListRef"
:items="displayedSongs"
:item-size="60"
:keep-alive="true"
:min-size="5"
:style="{ height: listHeight }"
@scroll="handleScroll"
>
<template #default="{ item }">
<song-item :item="formatDetail(item)" @play="handlePlay" />
</template>
</n-virtual-list>
<play-bottom /> -->
<div v-else-if="loading" class="loading-more">加载中...</div>
<play-bottom />
</div>
</div>
</n-drawer>
</template>
<script setup lang="ts">
// 导入 NVirtualListInst 类型
import type { VirtualListInst } from 'naive-ui';
import { useStore } from 'vuex';
import { getMusicDetail } from '@/api/music';
import SongItem from '@/components/common/SongItem.vue';
import { isMobile, setAnimationClass, setAnimationDelay } from '@/utils';
import { isMobile } from '@/utils';
import PlayBottom from './common/PlayBottom.vue';
const store = useStore();
const {
songList,
loading = false,
listInfo,
} = defineProps<{
const props = defineProps<{
show: boolean;
name: string;
songList: any[];
loading?: boolean;
listInfo?: any;
listInfo?: {
trackIds: { id: number }[];
[key: string]: any;
};
}>();
const emit = defineEmits(['update:show', 'update:loading']);
const page = ref(0);
const pageSize = 20;
const total = ref(0);
const isLoadingMore = ref(false);
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 song = {
@@ -95,7 +87,7 @@ const formatDetail = computed(() => (detail: any) => {
});
const handlePlay = () => {
const tracks = songList || [];
const tracks = props.songList || [];
store.commit(
'setPlayList',
tracks.map((item) => ({
@@ -112,40 +104,70 @@ const close = () => {
emit('update:show', false);
};
// 优化加载更多歌曲的函数
const loadMoreSongs = async () => {
if (displayedSongs.value.length >= total.value) return;
if (isLoadingMore.value || displayedSongs.value.length >= total.value) return;
isLoadingMore.value = true;
try {
const trackIds = listInfo.trackIds
.slice(page.value * pageSize, (page.value + 1) * pageSize)
.map((item: any) => item.id);
const reslist = await getMusicDetail(trackIds);
// displayedSongs.value = displayedSongs.value.concat(reslist.data.songs);
displayedSongs.value = JSON.parse(JSON.stringify([...displayedSongs.value, ...reslist.data.songs]));
page.value++;
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);
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) {
console.error('error', error);
console.error('加载歌曲失败:', error);
} finally {
isLoadingMore.value = false;
}
};
const handleScroll = (e: any) => {
const { scrollTop, scrollHeight, clientHeight } = e.target;
if (scrollTop + clientHeight >= scrollHeight - 50 && !isLoadingMore.value) {
// 添加虚拟列表的引用
const virtualListRef = ref<VirtualListInst | null>(null);
// 修改滚动处理函数
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();
}
};
// 监听 songList 变化,重置分页状态
watch(
() => songList,
() => props.songList,
(newSongs) => {
displayedSongs.value = JSON.parse(JSON.stringify(newSongs));
total.value = listInfo ? listInfo.trackIds.length : displayedSongs.value.length;
page.value = 0;
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>
<style scoped lang="scss">
@@ -168,9 +190,13 @@ watch(
&-list {
height: calc(100% - 60px);
position: relative; // 添加相对定位
&-content {
min-height: 400px;
:deep(.n-virtual-list__scroll) {
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
}
}
@@ -186,14 +212,20 @@ watch(
}
.double-list {
@apply flex flex-wrap gap-5;
.double-item {
width: calc(50% - 10px);
width: 100%;
}
.song-item {
background-color: #191919;
}
}
// 确保 PlayBottom 不会影响滚动区域
:deep(.bottom) {
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
</style>
+1 -1
View File
@@ -15,7 +15,7 @@ import directives from './directive';
const app = createApp(App);
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(store);
+1 -1
View File
@@ -96,7 +96,7 @@ const handleScroll = (e: any) => {
// 监听窗口大小变化,调整每行显示数量
const updateItemsPerRow = () => {
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 > 768) ITEMS_PER_ROW.value = 6;
else ITEMS_PER_ROW.value = 5;
+21 -4
View File
@@ -57,12 +57,18 @@ onActivated(() => {
const isShowList = ref(false);
const list = ref<Playlist>();
const listLoading = ref(false);
// 展示歌单
const showPlaylist = async (id: number) => {
const showPlaylist = async (id: number, name: string) => {
isShowList.value = true;
list.value = {};
listLoading.value = true;
list.value = {
name,
} as Playlist;
const { data } = await getListDetail(id);
list.value = data.playlist;
listLoading.value = false;
};
const handlePlay = () => {
@@ -103,7 +109,12 @@ const handlePlay = () => {
<div class="play-list" :class="setAnimationClass('animate__fadeInLeft')">
<div class="title">创建的歌单</div>
<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 />
<div class="play-list-item-info">
<div class="play-list-item-name">{{ item.name }}</div>
@@ -133,7 +144,13 @@ const handlePlay = () => {
</n-scrollbar>
</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>
</template>