2023-12-27 18:21:01 +08:00
|
|
|
<template>
|
|
|
|
|
<div class="mv-list">
|
|
|
|
|
<div class="mv-list-title">
|
|
|
|
|
<h2>推荐MV</h2>
|
|
|
|
|
</div>
|
2024-12-25 19:55:24 +08:00
|
|
|
<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>
|
2024-11-23 22:42:23 +08:00
|
|
|
<n-scrollbar :size="100" @scroll="handleScroll">
|
|
|
|
|
<div v-loading="initLoading" class="mv-list-content" :class="setAnimationClass('animate__bounceInLeft')">
|
2024-05-16 18:54:30 +08:00
|
|
|
<div
|
|
|
|
|
v-for="(item, index) in mvList"
|
|
|
|
|
:key="item.id"
|
|
|
|
|
class="mv-item"
|
|
|
|
|
:class="setAnimationClass('animate__bounceIn')"
|
2024-12-25 19:55:24 +08:00
|
|
|
:style="getAnimationDelay(index)"
|
2024-05-16 18:54:30 +08:00
|
|
|
>
|
2024-11-23 22:42:23 +08:00
|
|
|
<div class="mv-item-img" @click="handleShowMv(item, index)">
|
2024-12-08 21:50:58 +08:00
|
|
|
<n-image class="mv-item-img-img" :src="getImgUrl(item.cover, '320y180')" lazy preview-disabled />
|
2023-12-27 18:21:01 +08:00
|
|
|
<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>
|
2024-11-23 22:42:23 +08:00
|
|
|
|
|
|
|
|
<div v-if="loadingMore" class="loading-more">加载中...</div>
|
|
|
|
|
<div v-if="!hasMore && !initLoading" class="no-more">没有更多了</div>
|
2023-12-27 18:21:01 +08:00
|
|
|
</div>
|
|
|
|
|
</n-scrollbar>
|
|
|
|
|
|
2024-11-23 22:42:23 +08:00
|
|
|
<mv-player
|
|
|
|
|
v-model:show="showMv"
|
|
|
|
|
:current-mv="playMvItem"
|
|
|
|
|
:is-prev-disabled="isPrevDisabled"
|
|
|
|
|
@next="playNextMv"
|
|
|
|
|
@prev="playPrevMv"
|
|
|
|
|
/>
|
2023-12-27 18:21:01 +08:00
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2024-12-25 19:55:24 +08:00
|
|
|
import { computed, onMounted, ref, watch } from 'vue';
|
2023-12-27 18:21:01 +08:00
|
|
|
import { useStore } from 'vuex';
|
|
|
|
|
|
2024-12-25 19:55:24 +08:00
|
|
|
import { getAllMv, getTopMv } from '@/api/mv';
|
2024-11-23 22:42:23 +08:00
|
|
|
import MvPlayer from '@/components/MvPlayer.vue';
|
2024-12-12 22:36:07 +08:00
|
|
|
import { audioService } from '@/services/audioService';
|
2024-05-16 18:54:30 +08:00
|
|
|
import { IMvItem } from '@/type/mv';
|
|
|
|
|
import { formatNumber, getImgUrl, setAnimationClass, setAnimationDelay } from '@/utils';
|
|
|
|
|
|
2024-05-22 12:07:48 +08:00
|
|
|
defineOptions({
|
|
|
|
|
name: 'Mv',
|
|
|
|
|
});
|
|
|
|
|
|
2024-05-16 18:54:30 +08:00
|
|
|
const showMv = ref(false);
|
|
|
|
|
const mvList = ref<Array<IMvItem>>([]);
|
|
|
|
|
const playMvItem = ref<IMvItem>();
|
|
|
|
|
const store = useStore();
|
2024-11-23 22:42:23 +08:00
|
|
|
const initLoading = ref(false);
|
|
|
|
|
const loadingMore = ref(false);
|
|
|
|
|
const currentIndex = ref(0);
|
|
|
|
|
const offset = ref(0);
|
2024-12-09 22:39:33 +08:00
|
|
|
const limit = ref(42);
|
2024-11-23 22:42:23 +08:00
|
|
|
const hasMore = ref(true);
|
2023-12-27 18:21:01 +08:00
|
|
|
|
2024-12-25 19:55:24 +08:00
|
|
|
const categories = [
|
|
|
|
|
{ label: '全部', value: '全部' },
|
|
|
|
|
{ label: '内地', value: '内地' },
|
|
|
|
|
{ label: '港台', value: '港台' },
|
|
|
|
|
{ label: '欧美', value: '欧美' },
|
|
|
|
|
{ label: '日本', value: '日本' },
|
|
|
|
|
{ label: '韩国', value: '韩国' },
|
|
|
|
|
];
|
|
|
|
|
const selectedCategory = ref('全部');
|
|
|
|
|
|
|
|
|
|
watch(selectedCategory, async () => {
|
|
|
|
|
offset.value = 0;
|
|
|
|
|
mvList.value = [];
|
|
|
|
|
hasMore.value = true;
|
|
|
|
|
await loadMvList();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const getAnimationDelay = computed(() => {
|
|
|
|
|
return (index: number) => setAnimationDelay(index, 30);
|
|
|
|
|
});
|
2024-11-28 23:33:38 +08:00
|
|
|
|
2023-12-27 18:21:01 +08:00
|
|
|
onMounted(async () => {
|
2024-11-23 22:42:23 +08:00
|
|
|
await loadMvList();
|
2024-05-16 18:54:30 +08:00
|
|
|
});
|
2023-12-27 18:21:01 +08:00
|
|
|
|
2024-11-23 22:42:23 +08:00
|
|
|
const handleShowMv = async (item: IMvItem, index: number) => {
|
2024-05-16 18:54:30 +08:00
|
|
|
store.commit('setIsPlay', false);
|
|
|
|
|
store.commit('setPlayMusic', false);
|
2024-12-12 22:36:07 +08:00
|
|
|
audioService.getCurrentSound()?.pause();
|
2024-05-16 18:54:30 +08:00
|
|
|
showMv.value = true;
|
2024-11-23 22:42:23 +08:00
|
|
|
currentIndex.value = index;
|
2023-12-27 18:21:01 +08:00
|
|
|
playMvItem.value = item;
|
2024-05-16 18:54:30 +08:00
|
|
|
};
|
2023-12-27 18:21:01 +08:00
|
|
|
|
2024-11-23 22:42:23 +08:00
|
|
|
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);
|
2023-12-27 18:21:01 +08:00
|
|
|
}
|
2024-05-16 18:54:30 +08:00
|
|
|
};
|
2024-11-23 22:42:23 +08:00
|
|
|
|
|
|
|
|
const loadMvList = async () => {
|
|
|
|
|
try {
|
2024-12-25 19:55:24 +08:00
|
|
|
if (!hasMore.value || loadingMore.value) return;
|
2024-11-23 22:42:23 +08:00
|
|
|
if (offset.value === 0) {
|
2024-12-25 19:55:24 +08:00
|
|
|
initLoading.value = true;
|
2024-11-23 22:42:23 +08:00
|
|
|
} else {
|
2024-12-25 19:55:24 +08:00
|
|
|
loadingMore.value = true;
|
2024-11-23 22:42:23 +08:00
|
|
|
}
|
|
|
|
|
|
2024-12-25 19:55:24 +08:00
|
|
|
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;
|
2024-11-23 22:42:23 +08:00
|
|
|
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);
|
2023-12-27 18:21:01 +08:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
.mv-list {
|
2024-12-25 19:55:24 +08:00
|
|
|
@apply h-full flex-1 flex flex-col overflow-hidden;
|
2023-12-29 16:04:44 +08:00
|
|
|
|
|
|
|
|
&-title {
|
2024-12-09 20:39:32 +08:00
|
|
|
@apply text-xl font-bold pb-2;
|
2023-12-29 16:04:44 +08:00
|
|
|
}
|
2023-12-27 18:21:01 +08:00
|
|
|
|
2024-12-25 19:55:24 +08:00
|
|
|
// 添加歌单分类样式
|
|
|
|
|
.play-list-type {
|
|
|
|
|
.title {
|
|
|
|
|
@apply text-lg font-bold mb-4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.categories-wrapper {
|
|
|
|
|
@apply flex items-center py-2 pb-4;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&-item {
|
|
|
|
|
@apply py-2 px-3 mr-3 inline-block border border-gray-700 rounded-xl cursor-pointer transition-all duration-300;
|
|
|
|
|
background-color: #1a1a1a;
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
@apply bg-green-600/50;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
|
@apply bg-green-600 border-green-500;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-27 18:21:01 +08:00
|
|
|
&-content {
|
2024-12-08 21:50:58 +08:00
|
|
|
@apply grid gap-4 pb-28 mt-2 pr-4;
|
2024-12-09 22:58:57 +08:00
|
|
|
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
2023-12-27 18:21:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.mv-item {
|
2023-12-29 16:04:44 +08:00
|
|
|
@apply p-2 rounded-lg;
|
2024-01-03 22:27:58 +08:00
|
|
|
background-color: #1f1f1f;
|
2023-12-27 18:21:01 +08:00
|
|
|
&-img {
|
2023-12-29 16:04:44 +08:00
|
|
|
@apply rounded-lg overflow-hidden relative;
|
2024-12-08 21:50:58 +08:00
|
|
|
aspect-ratio: 16/9;
|
2024-01-03 22:27:58 +08:00
|
|
|
line-height: 0;
|
2023-12-27 18:21:01 +08:00
|
|
|
|
|
|
|
|
&:hover img {
|
2023-12-29 16:04:44 +08:00
|
|
|
@apply hover:scale-110 transition-all duration-300 ease-in-out object-top;
|
2023-12-27 18:21:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&-img {
|
2024-12-08 21:50:58 +08:00
|
|
|
@apply w-full h-full object-cover rounded-lg overflow-hidden;
|
2023-12-27 18:21:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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;
|
2023-12-29 16:04:44 +08:00
|
|
|
background-color: #0000009b;
|
2023-12-27 18:21:01 +08:00
|
|
|
opacity: 0;
|
|
|
|
|
|
|
|
|
|
i {
|
2023-12-29 16:04:44 +08:00
|
|
|
font-size: 40px;
|
2023-12-27 18:21:01 +08:00
|
|
|
transition: all 0.5s ease-in-out;
|
|
|
|
|
opacity: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
@apply opacity-100;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&:hover i {
|
2023-12-29 16:04:44 +08:00
|
|
|
@apply transform scale-150 opacity-80;
|
2023-12-27 18:21:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.play-count {
|
|
|
|
|
position: absolute;
|
2024-01-03 22:27:58 +08:00
|
|
|
top: 20px;
|
2023-12-27 18:21:01 +08:00
|
|
|
left: 10px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&-title {
|
|
|
|
|
@apply p-2 text-sm text-white truncate;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-23 17:12:35 +08:00
|
|
|
.mobile {
|
2024-12-09 20:39:32 +08:00
|
|
|
.mv-list-title {
|
|
|
|
|
@apply text-xl font-bold px-4;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-23 17:12:35 +08:00
|
|
|
.mv-list-content {
|
2024-12-08 21:50:58 +08:00
|
|
|
@apply px-4;
|
2024-12-09 22:39:33 +08:00
|
|
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
2024-05-23 17:12:35 +08:00
|
|
|
}
|
|
|
|
|
}
|
2024-11-23 22:42:23 +08:00
|
|
|
|
|
|
|
|
.loading-more,
|
|
|
|
|
.no-more {
|
|
|
|
|
@apply col-span-full text-center py-4 text-gray-400;
|
|
|
|
|
}
|
2024-05-16 18:54:30 +08:00
|
|
|
</style>
|