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