mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-14 14:50:50 +08:00
✨ feat: 优化歌单列表页面
This commit is contained in:
1
components.d.ts
vendored
1
components.d.ts
vendored
@@ -35,6 +35,7 @@ declare module 'vue' {
|
||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||
NTooltip: typeof import('naive-ui')['NTooltip']
|
||||
NVirtualList: typeof import('naive-ui')['NVirtualList']
|
||||
NVirtualListualList: typeof import('naive-ui')['NVirtualListualList']
|
||||
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']
|
||||
|
||||
@@ -9,41 +9,72 @@
|
||||
@mask-click="close"
|
||||
>
|
||||
<div class="music-page">
|
||||
<div class="music-close">
|
||||
<i class="icon iconfont icon-icon_error" @click="close"></i>
|
||||
<div class="music-header h-12 flex items-center justify-between">
|
||||
<n-ellipsis :line-clamp="1">
|
||||
<div class="music-title">
|
||||
{{ name }}
|
||||
</div>
|
||||
</n-ellipsis>
|
||||
<div class="music-close">
|
||||
<i class="icon iconfont icon-icon_error" @click="close"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="music-title text-el">{{ name }}</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>
|
||||
<div v-else-if="loading" class="loading-more">加载中...</div>
|
||||
<play-bottom />
|
||||
<div class="music-content">
|
||||
<!-- 左侧歌单信息 -->
|
||||
<div class="music-info">
|
||||
<div class="music-cover">
|
||||
<n-image
|
||||
:src="getImgUrl(listInfo?.coverImgUrl, '300y300')"
|
||||
class="cover-img"
|
||||
preview-disabled
|
||||
:class="setAnimationClass('animate__fadeIn')"
|
||||
/>
|
||||
</div>
|
||||
<div class="music-detail">
|
||||
<div v-if="listInfo?.creator" class="creator-info">
|
||||
<n-avatar round :size="24" :src="getImgUrl(listInfo.creator.avatarUrl, '50y50')" />
|
||||
<span class="creator-name">{{ listInfo.creator.nickname }}</span>
|
||||
</div>
|
||||
<div v-if="listInfo?.description" class="music-desc">
|
||||
<n-ellipsis :line-clamp="isMobile ? 3 : 10">
|
||||
{{ listInfo.description }}
|
||||
</n-ellipsis>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧歌曲列表 -->
|
||||
<div class="music-list-container">
|
||||
<div v-loading="loading" class="music-list">
|
||||
<n-scrollbar @scroll="handleScroll">
|
||||
<div v-loading="loading || !songList.length" class="music-list-content">
|
||||
<div
|
||||
v-for="(item, index) in displayedSongs"
|
||||
:key="item.id"
|
||||
class="double-item"
|
||||
:class="setAnimationClass('animate__bounceInDown')"
|
||||
:style="getItemAnimationDelay(index)"
|
||||
>
|
||||
<song-item :item="formatDetail(item)" @play="handlePlay" />
|
||||
</div>
|
||||
<div v-if="isLoadingMore" class="loading-more">加载更多...</div>
|
||||
<play-bottom />
|
||||
</div>
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
<play-bottom />
|
||||
</div>
|
||||
</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 } from '@/utils';
|
||||
import { getImgUrl, isMobile, setAnimationClass, setAnimationDelay } from '@/utils';
|
||||
|
||||
import PlayBottom from './common/PlayBottom.vue';
|
||||
|
||||
@@ -136,8 +167,10 @@ const loadMoreSongs = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 添加虚拟列表的引用
|
||||
const virtualListRef = ref<VirtualListInst | null>(null);
|
||||
const getItemAnimationDelay = (index: number) => {
|
||||
const currentPageIndex = index % pageSize;
|
||||
return setAnimationDelay(currentPageIndex, 20);
|
||||
};
|
||||
|
||||
// 修改滚动处理函数
|
||||
const handleScroll = (e: Event) => {
|
||||
@@ -162,35 +195,62 @@ watch(
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
// 添加计算属性来处理列表高度
|
||||
const listHeight = computed(() => {
|
||||
const baseHeight = '100%'; // 减去标题高度
|
||||
return store.state.isPlay ? `calc(100% - 90px)` : baseHeight; // 112px 是 PlayBottom 的高度
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.music {
|
||||
&-title {
|
||||
@apply text-xl font-bold text-white;
|
||||
}
|
||||
|
||||
&-page {
|
||||
@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 flex gap-2 items-center;
|
||||
@apply cursor-pointer text-white flex gap-2 items-center;
|
||||
.icon {
|
||||
@apply text-3xl;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
@apply flex h-[calc(100%-60px)];
|
||||
}
|
||||
|
||||
&-info {
|
||||
@apply w-[400px] flex-shrink-0 pr-8 flex flex-col;
|
||||
|
||||
.music-cover {
|
||||
@apply w-full aspect-square rounded-lg overflow-hidden mb-4;
|
||||
.cover-img {
|
||||
@apply w-full h-full object-cover;
|
||||
}
|
||||
}
|
||||
|
||||
.music-detail {
|
||||
@apply flex flex-col flex-grow;
|
||||
|
||||
.creator-info {
|
||||
@apply flex items-center mb-4;
|
||||
.creator-name {
|
||||
@apply ml-2 text-sm text-gray-300;
|
||||
}
|
||||
}
|
||||
|
||||
.music-desc {
|
||||
@apply text-sm text-gray-400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-list-container {
|
||||
@apply flex-grow min-h-0 flex flex-col relative;
|
||||
}
|
||||
|
||||
&-list {
|
||||
height: calc(100% - 60px);
|
||||
position: relative; // 添加相对定位
|
||||
@apply flex-grow min-h-0;
|
||||
|
||||
:deep(.n-virtual-list__scroll) {
|
||||
scrollbar-width: none;
|
||||
@@ -205,6 +265,21 @@ const listHeight = computed(() => {
|
||||
.music-page {
|
||||
@apply px-4;
|
||||
}
|
||||
|
||||
.music-content {
|
||||
@apply flex-col;
|
||||
}
|
||||
|
||||
.music-info {
|
||||
@apply w-full pr-0 mb-2 flex flex-row;
|
||||
|
||||
.music-cover {
|
||||
@apply w-[100px] h-[100px] rounded-lg overflow-hidden mb-4;
|
||||
}
|
||||
.music-detail {
|
||||
@apply flex-1 ml-4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-more {
|
||||
@@ -220,12 +295,4 @@ const listHeight = computed(() => {
|
||||
background-color: #191919;
|
||||
}
|
||||
}
|
||||
|
||||
// 确保 PlayBottom 不会影响滚动区域
|
||||
:deep(.bottom) {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -21,7 +21,8 @@ const isLoadingMore = ref(false);
|
||||
|
||||
// 计算每个项目的动画延迟
|
||||
const getItemAnimationDelay = (index: number) => {
|
||||
return setAnimationDelay(index, 30);
|
||||
const currentPageIndex = index % TOTAL_ITEMS;
|
||||
return setAnimationDelay(currentPageIndex, 30);
|
||||
};
|
||||
|
||||
const recommendItem = ref<IRecommendItem | null>();
|
||||
@@ -167,7 +168,7 @@ watch(
|
||||
|
||||
&-list {
|
||||
@apply grid gap-x-8 gap-y-6 pb-28 pr-4;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
}
|
||||
&-item {
|
||||
@apply flex flex-col;
|
||||
@@ -224,6 +225,7 @@ watch(
|
||||
|
||||
.recommend-list {
|
||||
@apply px-4 gap-4;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -58,7 +58,7 @@ const initLoading = ref(false);
|
||||
const loadingMore = ref(false);
|
||||
const currentIndex = ref(0);
|
||||
const offset = ref(0);
|
||||
const limit = ref(28);
|
||||
const limit = ref(42);
|
||||
const hasMore = ref(true);
|
||||
|
||||
const getItemAnimationDelay = (index: number) => {
|
||||
@@ -163,7 +163,7 @@ const isPrevDisabled = computed(() => currentIndex.value === 0);
|
||||
|
||||
&-content {
|
||||
@apply grid gap-4 pb-28 mt-2 pr-4;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
}
|
||||
|
||||
.mv-item {
|
||||
@@ -223,6 +223,7 @@ const isPrevDisabled = computed(() => currentIndex.value === 0);
|
||||
|
||||
.mv-list-content {
|
||||
@apply px-4;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user