mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-28 02:47:22 +08:00
✨ feat: 完善歌单列表组件 实现滚动加载更多
This commit is contained in:
Vendored
+2
@@ -17,6 +17,7 @@ declare module 'vue' {
|
|||||||
NDropdown: typeof import('naive-ui')['NDropdown']
|
NDropdown: typeof import('naive-ui')['NDropdown']
|
||||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||||
NImage: typeof import('naive-ui')['NImage']
|
NImage: typeof import('naive-ui')['NImage']
|
||||||
|
NInfiniteScroll: typeof import('naive-ui')['NInfiniteScroll']
|
||||||
NInput: typeof import('naive-ui')['NInput']
|
NInput: typeof import('naive-ui')['NInput']
|
||||||
NLayout: typeof import('naive-ui')['NLayout']
|
NLayout: typeof import('naive-ui')['NLayout']
|
||||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||||
@@ -25,6 +26,7 @@ declare module 'vue' {
|
|||||||
NSlider: typeof import('naive-ui')['NSlider']
|
NSlider: typeof import('naive-ui')['NSlider']
|
||||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||||
NTooltip: typeof import('naive-ui')['NTooltip']
|
NTooltip: typeof import('naive-ui')['NTooltip']
|
||||||
|
NVirtualList: typeof import('naive-ui')['NVirtualList']
|
||||||
PlayBottom: typeof import('./src/components/common/PlayBottom.vue')['default']
|
PlayBottom: typeof import('./src/components/common/PlayBottom.vue')['default']
|
||||||
PlayListsItem: typeof import('./src/components/common/PlayListsItem.vue')['default']
|
PlayListsItem: typeof import('./src/components/common/PlayListsItem.vue')['default']
|
||||||
PlaylistType: typeof import('./src/components/PlaylistType.vue')['default']
|
PlaylistType: typeof import('./src/components/PlaylistType.vue')['default']
|
||||||
|
|||||||
@@ -6,23 +6,41 @@
|
|||||||
:drawer-style="{ backgroundColor: 'transparent' }"
|
:drawer-style="{ backgroundColor: 'transparent' }"
|
||||||
>
|
>
|
||||||
<div class="music-page">
|
<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-title text-el">{{ name }}</div>
|
||||||
<!-- 歌单歌曲列表 -->
|
<!-- 歌单歌曲列表 -->
|
||||||
<div class="music-list">
|
<div class="music-list">
|
||||||
<n-scrollbar>
|
<n-scrollbar @scroll="handleScroll">
|
||||||
<div v-loading="loading || !songList.length" class="music-list-content">
|
<div
|
||||||
|
v-loading="loading || !songList.length"
|
||||||
|
class="music-list-content"
|
||||||
|
:class="{ 'double-list': doubleDisply }"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(item, index) in songList"
|
v-for="(item, index) in displayedSongs"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:class="setAnimationClass('animate__bounceInLeft')"
|
class="double-item"
|
||||||
:style="setAnimationDelay(index, 50)"
|
:class="setAnimationClass('animate__bounceInUp')"
|
||||||
|
:style="setAnimationDelay(index, 5)"
|
||||||
>
|
>
|
||||||
<song-item :item="formatDetail(item)" @play="handlePlay" />
|
<song-item :item="formatDetail(item)" @play="handlePlay" />
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="isLoadingMore" class="loading-more">加载更多...</div>
|
||||||
</div>
|
</div>
|
||||||
<play-bottom />
|
<play-bottom />
|
||||||
</n-scrollbar>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</n-drawer>
|
</n-drawer>
|
||||||
@@ -31,6 +49,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useStore } from 'vuex';
|
import { useStore } from 'vuex';
|
||||||
|
|
||||||
|
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, setAnimationClass, setAnimationDelay } from '@/utils';
|
||||||
|
|
||||||
@@ -38,14 +57,28 @@ import PlayBottom from './common/PlayBottom.vue';
|
|||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
|
||||||
const { songList, loading = false } = defineProps<{
|
const {
|
||||||
|
songList,
|
||||||
|
loading = false,
|
||||||
|
listInfo,
|
||||||
|
} = defineProps<{
|
||||||
show: boolean;
|
show: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
songList: any[];
|
songList: any[];
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
listInfo?: any;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits(['update:show', 'update:loading']);
|
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 formatDetail = computed(() => (detail: any) => {
|
||||||
const song = {
|
const song = {
|
||||||
artists: detail.ar,
|
artists: detail.ar,
|
||||||
@@ -66,6 +99,42 @@ const handlePlay = () => {
|
|||||||
const close = () => {
|
const close = () => {
|
||||||
emit('update:show', false);
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<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;
|
@apply px-8 w-full h-full bg-black bg-opacity-75 rounded-t-2xl;
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-title {
|
&-title {
|
||||||
@apply text-lg font-bold text-white p-4;
|
@apply text-lg font-bold text-white p-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-close {
|
&-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 {
|
&-list {
|
||||||
height: calc(100% - 60px);
|
height: calc(100% - 60px);
|
||||||
|
|
||||||
&-content {
|
&-content {
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
}
|
}
|
||||||
@@ -95,4 +169,20 @@ const close = () => {
|
|||||||
@apply px-4;
|
@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>
|
</style>
|
||||||
|
|||||||
+11
-6
@@ -28,16 +28,21 @@ export const secondToMinute = (s: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 格式化数字 千,万, 百万, 千万,亿
|
// 格式化数字 千,万, 百万, 千万,亿
|
||||||
|
const units = [
|
||||||
|
{ value: 1e8, symbol: '亿' },
|
||||||
|
{ value: 1e4, symbol: '万' },
|
||||||
|
];
|
||||||
|
|
||||||
export const formatNumber = (num: string | number) => {
|
export const formatNumber = (num: string | number) => {
|
||||||
num = Number(num);
|
num = Number(num);
|
||||||
if (num < 10000) {
|
for (let i = 0; i < units.length; i++) {
|
||||||
return num;
|
if (num >= units[i].value) {
|
||||||
|
return `${(num / units[i].value).toFixed(1)}${units[i].symbol}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (num < 100000000) {
|
return num.toString();
|
||||||
return `${(num / 10000).toFixed(1)}万`;
|
|
||||||
}
|
|
||||||
return `${(num / 100000000).toFixed(1)}亿`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const windowData = window as any;
|
const windowData = window as any;
|
||||||
export const getIsMc = () => {
|
export const getIsMc = () => {
|
||||||
if (!windowData.electron) {
|
if (!windowData.electron) {
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ watch(
|
|||||||
v-model:loading="listLoading"
|
v-model:loading="listLoading"
|
||||||
:name="recommendItem?.name || ''"
|
:name="recommendItem?.name || ''"
|
||||||
:song-list="listDetail?.playlist.tracks || []"
|
:song-list="listDetail?.playlist.tracks || []"
|
||||||
|
:list-info="listDetail?.playlist"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ const handlePlay = () => {
|
|||||||
</n-scrollbar>
|
</n-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user