feat: 完善歌单列表组件 实现滚动加载更多

This commit is contained in:
alger
2024-09-11 16:29:43 +08:00
parent b6a5461a1d
commit 800e0b7360
5 changed files with 113 additions and 15 deletions

2
components.d.ts vendored
View File

@@ -17,6 +17,7 @@ declare module 'vue' {
NDropdown: typeof import('naive-ui')['NDropdown']
NEllipsis: typeof import('naive-ui')['NEllipsis']
NImage: typeof import('naive-ui')['NImage']
NInfiniteScroll: typeof import('naive-ui')['NInfiniteScroll']
NInput: typeof import('naive-ui')['NInput']
NLayout: typeof import('naive-ui')['NLayout']
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
@@ -25,6 +26,7 @@ declare module 'vue' {
NSlider: typeof import('naive-ui')['NSlider']
NSwitch: typeof import('naive-ui')['NSwitch']
NTooltip: typeof import('naive-ui')['NTooltip']
NVirtualList: typeof import('naive-ui')['NVirtualList']
PlayBottom: typeof import('./src/components/common/PlayBottom.vue')['default']
PlayListsItem: typeof import('./src/components/common/PlayListsItem.vue')['default']
PlaylistType: typeof import('./src/components/PlaylistType.vue')['default']

View File

@@ -6,23 +6,41 @@
:drawer-style="{ backgroundColor: 'transparent' }"
>
<div class="music-page">
<i class="iconfont icon-icon_error music-close" @click="close"></i>
<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>
<div v-loading="loading || !songList.length" class="music-list-content">
<n-scrollbar @scroll="handleScroll">
<div
v-loading="loading || !songList.length"
class="music-list-content"
:class="{ 'double-list': doubleDisply }"
>
<div
v-for="(item, index) in songList"
v-for="(item, index) in displayedSongs"
:key="item.id"
:class="setAnimationClass('animate__bounceInLeft')"
:style="setAnimationDelay(index, 50)"
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>
</template>
</n-virtual-list>
<play-bottom /> -->
</div>
</div>
</n-drawer>
@@ -31,6 +49,7 @@
<script setup lang="ts">
import { useStore } from 'vuex';
import { getMusicDetail } from '@/api/music';
import SongItem from '@/components/common/SongItem.vue';
import { isMobile, setAnimationClass, setAnimationDelay } from '@/utils';
@@ -38,14 +57,28 @@ import PlayBottom from './common/PlayBottom.vue';
const store = useStore();
const { songList, loading = false } = defineProps<{
const {
songList,
loading = false,
listInfo,
} = defineProps<{
show: boolean;
name: string;
songList: any[];
loading?: boolean;
listInfo?: 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 formatDetail = computed(() => (detail: any) => {
const song = {
artists: detail.ar,
@@ -66,6 +99,42 @@ const handlePlay = () => {
const close = () => {
emit('update:show', false);
};
const loadMoreSongs = async () => {
if (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]));
console.log('displayedSongs.value', displayedSongs.value);
page.value++;
} catch (error) {
console.error('error', error);
} finally {
isLoadingMore.value = false;
}
};
const handleScroll = (e: any) => {
const { scrollTop, scrollHeight, clientHeight } = e.target;
if (scrollTop + clientHeight >= scrollHeight - 50 && !isLoadingMore.value) {
loadMoreSongs();
}
};
watch(
() => songList,
(newSongs) => {
displayedSongs.value = JSON.parse(JSON.stringify(newSongs));
total.value = listInfo ? listInfo.trackIds.length : displayedSongs.value.length;
},
{ deep: true, immediate: true },
);
</script>
<style scoped lang="scss">
@@ -74,16 +143,21 @@ const close = () => {
@apply px-8 w-full h-full bg-black bg-opacity-75 rounded-t-2xl;
backdrop-filter: blur(20px);
}
&-title {
@apply text-lg font-bold text-white p-4;
}
&-close {
@apply absolute top-4 right-8 cursor-pointer text-white text-3xl;
@apply absolute top-4 right-8 cursor-pointer text-white flex gap-2 items-center;
.icon {
@apply text-3xl;
}
}
&-list {
height: calc(100% - 60px);
&-content {
min-height: 400px;
}
@@ -95,4 +169,20 @@ const close = () => {
@apply px-4;
}
}
.loading-more {
@apply text-center text-white py-10;
}
.double-list {
@apply flex flex-wrap gap-5;
.double-item {
width: calc(50% - 10px);
}
.song-item {
background-color: #191919;
}
}
</style>

View File

@@ -28,16 +28,21 @@ export const secondToMinute = (s: number) => {
};
// 格式化数字 千,万, 百万, 千万,亿
const units = [
{ value: 1e8, symbol: '亿' },
{ value: 1e4, symbol: '万' },
];
export const formatNumber = (num: string | number) => {
num = Number(num);
if (num < 10000) {
return num;
for (let i = 0; i < units.length; i++) {
if (num >= units[i].value) {
return `${(num / units[i].value).toFixed(1)}${units[i].symbol}`;
}
}
if (num < 100000000) {
return `${(num / 10000).toFixed(1)}`;
}
return `${(num / 100000000).toFixed(1)}亿`;
return num.toString();
};
const windowData = window as any;
export const getIsMc = () => {
if (!windowData.electron) {

View File

@@ -101,6 +101,7 @@ watch(
v-model:loading="listLoading"
:name="recommendItem?.name || ''"
:song-list="listDetail?.playlist.tracks || []"
:list-info="listDetail?.playlist"
/>
</div>
</template>

View File

@@ -145,7 +145,7 @@ const handlePlay = () => {
</n-scrollbar>
</div>
</div>
<music-list v-model:show="isShowList" :name="list?.name || ''" :song-list="list?.tracks || []" />
<music-list v-model:show="isShowList" :name="list?.name || ''" :song-list="list?.tracks || []" :list-info="list" />
</div>
</template>