Files
AlgerMusicPlayer/src/renderer/views/mv/index.vue

305 lines
7.7 KiB
Vue

<template>
<div class="mv-list">
<div class="play-list-type">
<n-scrollbar x-scrollable>
<div class="categories-wrapper">
<span
v-for="(category, index) in categories"
:key="category.value"
class="play-list-type-item"
:class="[
setAnimationClass('animate__bounceIn'),
{ active: selectedCategory === category.value }
]"
:style="getAnimationDelay(index)"
@click="selectedCategory = category.value"
>
{{ category.label }}
</span>
</div>
</n-scrollbar>
</div>
<n-scrollbar :size="100" @scroll="handleScroll">
<div
v-loading="initLoading"
class="mv-list-content"
:class="setAnimationClass('animate__bounceInLeft')"
>
<div
v-for="(item, index) in mvList"
:key="item.id"
class="mv-item"
:class="setAnimationClass('animate__bounceIn')"
:style="getAnimationDelay(index)"
>
<div class="mv-item-img" @click="handleShowMv(item, index)">
<n-image
class="mv-item-img-img"
:src="getImgUrl(item.cover, '320y180')"
lazy
preview-disabled
/>
<div class="top">
<div class="play-count">{{ formatNumber(item.playCount) }}</div>
<i class="iconfont icon-videofill"></i>
</div>
</div>
<div class="mv-item-title">{{ item.name }}</div>
</div>
<div v-if="loadingMore" class="loading-more">加载中...</div>
<div v-if="!hasMore && !initLoading" class="no-more">没有更多了</div>
</div>
</n-scrollbar>
<mv-player
v-model:show="showMv"
:current-mv="playMvItem"
:is-prev-disabled="isPrevDisabled"
@next="playNextMv"
@prev="playPrevMv"
/>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue';
import { getAllMv, getTopMv } from '@/api/mv';
import MvPlayer from '@/components/MvPlayer.vue';
import { audioService } from '@/services/audioService';
import { usePlayerStore } from '@/store/modules/player';
import { IMvItem } from '@/type/mv';
import { formatNumber, getImgUrl, setAnimationClass, setAnimationDelay } from '@/utils';
defineOptions({
name: 'Mv'
});
const showMv = ref(false);
const mvList = ref<Array<IMvItem>>([]);
const playMvItem = ref<IMvItem>();
const initLoading = ref(false);
const loadingMore = ref(false);
const currentIndex = ref(0);
const offset = ref(0);
const limit = ref(42);
const hasMore = ref(true);
const categories = [
{ label: '全部', value: '全部' },
{ label: '内地', value: '内地' },
{ label: '港台', value: '港台' },
{ label: '欧美', value: '欧美' },
{ label: '日本', value: '日本' },
{ label: '韩国', value: '韩国' }
];
const selectedCategory = ref('全部');
const playerStore = usePlayerStore();
watch(selectedCategory, async () => {
offset.value = 0;
mvList.value = [];
hasMore.value = true;
await loadMvList();
});
const getAnimationDelay = (index: number) => {
const currentPageIndex = index % limit.value;
return setAnimationDelay(currentPageIndex, 30);
};
onMounted(async () => {
await loadMvList();
});
const handleShowMv = async (item: IMvItem, index: number) => {
playerStore.setIsPlay(false);
audioService.pause();
showMv.value = true;
currentIndex.value = index;
playMvItem.value = item;
};
const playPrevMv = async (setLoading: (value: boolean) => void) => {
try {
if (currentIndex.value > 0) {
const prevItem = mvList.value[currentIndex.value - 1];
await handleShowMv(prevItem, currentIndex.value - 1);
}
} finally {
setLoading(false);
}
};
const playNextMv = async (setLoading: (value: boolean) => void) => {
try {
if (currentIndex.value < mvList.value.length - 1) {
const nextItem = mvList.value[currentIndex.value + 1];
await handleShowMv(nextItem, currentIndex.value + 1);
} else if (hasMore.value) {
await loadMvList();
if (mvList.value.length > currentIndex.value + 1) {
const nextItem = mvList.value[currentIndex.value + 1];
await handleShowMv(nextItem, currentIndex.value + 1);
} else {
showMv.value = false;
}
} else {
showMv.value = false;
}
} catch (error) {
console.error('加载更多MV失败:', error);
showMv.value = false;
} finally {
setLoading(false);
}
};
const loadMvList = async () => {
try {
if (!hasMore.value || loadingMore.value) return;
if (offset.value === 0) {
initLoading.value = true;
} else {
loadingMore.value = true;
}
const params = {
limit: limit.value,
offset: offset.value,
area: selectedCategory.value === '全部' ? '' : selectedCategory.value
};
const res = selectedCategory.value === '全部' ? await getTopMv(params) : await getAllMv(params);
const { data } = res.data;
mvList.value.push(...data);
hasMore.value = data.length === limit.value;
offset.value += limit.value;
} finally {
initLoading.value = false;
loadingMore.value = false;
}
};
const handleScroll = (e: Event) => {
const target = e.target as Element;
const { scrollTop, clientHeight, scrollHeight } = target;
const threshold = 100;
if (scrollHeight - (scrollTop + clientHeight) < threshold) {
loadMvList();
}
};
const isPrevDisabled = computed(() => currentIndex.value === 0);
</script>
<style scoped lang="scss">
.mv-list {
@apply h-full flex-1 flex flex-col overflow-hidden;
&-title {
@apply text-xl font-bold pb-2;
@apply text-gray-900 dark:text-white;
}
// 添加歌单分类样式
.play-list-type {
.title {
@apply text-lg font-bold mb-2;
@apply text-gray-900 dark:text-white;
}
.categories-wrapper {
@apply flex items-center py-2;
white-space: nowrap;
}
&-item {
@apply py-2 px-3 mr-3 inline-block rounded-xl cursor-pointer transition-all duration-300;
@apply bg-light dark:bg-black text-gray-900 dark:text-white;
@apply border border-gray-200 dark:border-gray-700;
&:hover {
@apply bg-green-50 dark:bg-green-900;
}
&.active {
@apply bg-green-500 border-green-500 text-white;
}
}
}
&-content {
@apply grid gap-4 pb-28 mt-2 pr-4;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
}
.mv-item {
@apply p-2 rounded-lg;
@apply bg-light dark:bg-black;
@apply border border-gray-200 dark:border-gray-700;
&-img {
@apply rounded-lg overflow-hidden relative;
aspect-ratio: 16/9;
line-height: 0;
&:hover img {
@apply hover:scale-110 transition-all duration-300 ease-in-out object-top;
}
&-img {
@apply w-full h-full object-cover rounded-lg overflow-hidden;
}
.top {
@apply absolute w-full h-full top-0 left-0 flex justify-center items-center transition-all duration-300 ease-in-out cursor-pointer;
@apply bg-black bg-opacity-60;
opacity: 0;
i {
@apply text-4xl text-white;
}
.play-count {
@apply absolute top-2 right-2 text-sm;
@apply text-white text-opacity-90;
}
&:hover {
opacity: 1;
}
}
}
&-title {
@apply mt-2 text-sm line-clamp-1;
@apply text-gray-900 dark:text-white;
}
}
}
.loading-more {
@apply text-center py-4 col-span-full;
@apply text-gray-500 dark:text-gray-400;
}
.no-more {
@apply text-center py-4 col-span-full;
@apply text-gray-500 dark:text-gray-400;
}
.mobile {
.mv-list-content {
@apply pl-4 pr-4;
}
.categories-wrapper {
@apply pl-4;
}
}
</style>