mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-24 08:07:23 +08:00
✨ feat: 添加mv分类
This commit is contained in:
+3
-1
@@ -2,7 +2,9 @@
|
|||||||
<div class="app-container" :class="{ mobile: isMobile, noElectron: !isElectron }">
|
<div class="app-container" :class="{ mobile: isMobile, noElectron: !isElectron }">
|
||||||
<n-config-provider :theme="darkTheme">
|
<n-config-provider :theme="darkTheme">
|
||||||
<n-dialog-provider>
|
<n-dialog-provider>
|
||||||
<router-view></router-view>
|
<n-message-provider>
|
||||||
|
<router-view></router-view>
|
||||||
|
</n-message-provider>
|
||||||
</n-dialog-provider>
|
</n-dialog-provider>
|
||||||
</n-config-provider>
|
</n-config-provider>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+17
-5
@@ -2,15 +2,27 @@ import { IData } from '@/type';
|
|||||||
import { IMvItem, IMvUrlData } from '@/type/mv';
|
import { IMvItem, IMvUrlData } from '@/type/mv';
|
||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
|
|
||||||
|
interface MvParams {
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
area?: string;
|
||||||
|
}
|
||||||
|
|
||||||
// 获取 mv 排行
|
// 获取 mv 排行
|
||||||
export const getTopMv = (limit = 30, offset = 0) => {
|
export const getTopMv = (params: MvParams) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/mv/all',
|
url: '/mv/all',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params,
|
||||||
limit,
|
});
|
||||||
offset,
|
};
|
||||||
},
|
|
||||||
|
// 获取所有mv
|
||||||
|
export const getAllMv = (params: MvParams) => {
|
||||||
|
return request({
|
||||||
|
url: '/mv/all',
|
||||||
|
method: 'get',
|
||||||
|
params,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,12 @@
|
|||||||
<span class="text-sm text-gray-100">微信支付</span>
|
<span class="text-sm text-gray-100">微信支付</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<p class="text-sm text-gray-100 text-center cursor-pointer hover:text-green-500" @click="copyQQ">
|
||||||
|
QQ群:789288579
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n-popover>
|
</n-popover>
|
||||||
</div>
|
</div>
|
||||||
@@ -31,6 +37,12 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { NButton, NImage, NPopover } from 'naive-ui';
|
import { NButton, NImage, NPopover } from 'naive-ui';
|
||||||
|
|
||||||
|
const message = useMessage();
|
||||||
|
const copyQQ = () => {
|
||||||
|
navigator.clipboard.writeText('789288579');
|
||||||
|
message.success('已复制到剪贴板');
|
||||||
|
};
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
alipayQR: {
|
alipayQR: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|||||||
@@ -10,17 +10,15 @@
|
|||||||
<search-bar />
|
<search-bar />
|
||||||
<!-- 主页面路由 -->
|
<!-- 主页面路由 -->
|
||||||
<div class="main-content" :native-scrollbar="false">
|
<div class="main-content" :native-scrollbar="false">
|
||||||
<n-message-provider>
|
<router-view
|
||||||
<router-view
|
v-slot="{ Component }"
|
||||||
v-slot="{ Component }"
|
class="main-page"
|
||||||
class="main-page"
|
:class="route.meta.noScroll && !isMobile ? 'pr-3' : ''"
|
||||||
:class="route.meta.noScroll && !isMobile ? 'pr-3' : ''"
|
>
|
||||||
>
|
<keep-alive :include="keepAliveInclude">
|
||||||
<keep-alive :include="keepAliveInclude">
|
<component :is="Component" />
|
||||||
<component :is="Component" />
|
</keep-alive>
|
||||||
</keep-alive>
|
</router-view>
|
||||||
</router-view>
|
|
||||||
</n-message-provider>
|
|
||||||
</div>
|
</div>
|
||||||
<play-bottom height="5rem" />
|
<play-bottom height="5rem" />
|
||||||
<app-menu v-if="isMobile" class="menu" :menus="menus" />
|
<app-menu v-if="isMobile" class="menu" :menus="menus" />
|
||||||
|
|||||||
+79
-22
@@ -3,6 +3,22 @@
|
|||||||
<div class="mv-list-title">
|
<div class="mv-list-title">
|
||||||
<h2>推荐MV</h2>
|
<h2>推荐MV</h2>
|
||||||
</div>
|
</div>
|
||||||
|
<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">
|
<n-scrollbar :size="100" @scroll="handleScroll">
|
||||||
<div v-loading="initLoading" class="mv-list-content" :class="setAnimationClass('animate__bounceInLeft')">
|
<div v-loading="initLoading" class="mv-list-content" :class="setAnimationClass('animate__bounceInLeft')">
|
||||||
<div
|
<div
|
||||||
@@ -10,7 +26,7 @@
|
|||||||
:key="item.id"
|
:key="item.id"
|
||||||
class="mv-item"
|
class="mv-item"
|
||||||
:class="setAnimationClass('animate__bounceIn')"
|
:class="setAnimationClass('animate__bounceIn')"
|
||||||
:style="getItemAnimationDelay(index)"
|
:style="getAnimationDelay(index)"
|
||||||
>
|
>
|
||||||
<div class="mv-item-img" @click="handleShowMv(item, 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 />
|
<n-image class="mv-item-img-img" :src="getImgUrl(item.cover, '320y180')" lazy preview-disabled />
|
||||||
@@ -38,10 +54,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
import { useStore } from 'vuex';
|
import { useStore } from 'vuex';
|
||||||
|
|
||||||
import { getTopMv } from '@/api/mv';
|
import { getAllMv, getTopMv } from '@/api/mv';
|
||||||
import MvPlayer from '@/components/MvPlayer.vue';
|
import MvPlayer from '@/components/MvPlayer.vue';
|
||||||
import { audioService } from '@/services/audioService';
|
import { audioService } from '@/services/audioService';
|
||||||
import { IMvItem } from '@/type/mv';
|
import { IMvItem } from '@/type/mv';
|
||||||
@@ -62,10 +78,26 @@ const offset = ref(0);
|
|||||||
const limit = ref(42);
|
const limit = ref(42);
|
||||||
const hasMore = ref(true);
|
const hasMore = ref(true);
|
||||||
|
|
||||||
const getItemAnimationDelay = (index: number) => {
|
const categories = [
|
||||||
const currentPageIndex = index % limit.value;
|
{ label: '全部', value: '全部' },
|
||||||
return setAnimationDelay(currentPageIndex, 30);
|
{ 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);
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadMvList();
|
await loadMvList();
|
||||||
@@ -116,26 +148,26 @@ const playNextMv = async (setLoading: (value: boolean) => void) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loadMvList = async () => {
|
const loadMvList = async () => {
|
||||||
if (!hasMore.value || loadingMore.value) return;
|
|
||||||
|
|
||||||
if (offset.value === 0) {
|
|
||||||
initLoading.value = true;
|
|
||||||
} else {
|
|
||||||
loadingMore.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await getTopMv(limit.value, offset.value);
|
if (!hasMore.value || loadingMore.value) return;
|
||||||
if (offset.value === 0) {
|
if (offset.value === 0) {
|
||||||
mvList.value = res.data.data;
|
initLoading.value = true;
|
||||||
} else {
|
} else {
|
||||||
mvList.value.push(...res.data.data);
|
loadingMore.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMore.value = res.data.data.length === limit.value;
|
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;
|
offset.value += limit.value;
|
||||||
} catch (error) {
|
|
||||||
console.error('加载MV失败:', error);
|
|
||||||
} finally {
|
} finally {
|
||||||
initLoading.value = false;
|
initLoading.value = false;
|
||||||
loadingMore.value = false;
|
loadingMore.value = false;
|
||||||
@@ -157,12 +189,37 @@ const isPrevDisabled = computed(() => currentIndex.value === 0);
|
|||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.mv-list {
|
.mv-list {
|
||||||
@apply relative h-full w-full;
|
@apply h-full flex-1 flex flex-col overflow-hidden;
|
||||||
|
|
||||||
&-title {
|
&-title {
|
||||||
@apply text-xl font-bold pb-2;
|
@apply text-xl font-bold pb-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加歌单分类样式
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&-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(180px, 1fr));
|
||||||
|
|||||||
+11
-2
@@ -59,8 +59,12 @@
|
|||||||
isElectron ? '保存并重启' : '保存'
|
isElectron ? '保存并重启' : '保存'
|
||||||
}}</n-button>
|
}}</n-button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-10">
|
||||||
<div class="p-6 bg-black rounded-lg shadow-lg mt-10">
|
<p class="text-sm text-gray-100 text-center cursor-pointer hover:text-green-500" @click="copyQQ">
|
||||||
|
QQ群:789288579
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-6 bg-black rounded-lg shadow-lg">
|
||||||
<div class="text-gray-100 text-base text-center">支持作者</div>
|
<div class="text-gray-100 text-base text-center">支持作者</div>
|
||||||
<div class="flex gap-60">
|
<div class="flex gap-60">
|
||||||
<div class="flex flex-col items-center gap-2 cursor-none hover:scale-[2] transition-all z-10 bg-black">
|
<div class="flex flex-col items-center gap-2 cursor-none hover:scale-[2] transition-all z-10 bg-black">
|
||||||
@@ -90,6 +94,11 @@ import { setAnimationClass } from '@/utils';
|
|||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'Setting',
|
name: 'Setting',
|
||||||
});
|
});
|
||||||
|
const message = useMessage();
|
||||||
|
const copyQQ = () => {
|
||||||
|
navigator.clipboard.writeText('789288579');
|
||||||
|
message.success('已复制到剪贴板');
|
||||||
|
};
|
||||||
|
|
||||||
const isElectron = ref((window as any).electronAPI !== undefined);
|
const isElectron = ref((window as any).electronAPI !== undefined);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|||||||
Reference in New Issue
Block a user