mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-05 07:20:50 +08:00
✨ feat: 添加热门mv页面
This commit is contained in:
30
src/api/mv.ts
Normal file
30
src/api/mv.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { IData } from '@/type'
|
||||
import { IMvItem, IMvUrlData } from '@/type/mv'
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 获取 mv 排行
|
||||
export const getTopMv = (limit: number) => {
|
||||
return request.get<IData<Array<IMvItem>>>('/top/mv', {
|
||||
params: {
|
||||
limit,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 获取 mv 数据
|
||||
export const getMvDetail = (mvid: string) => {
|
||||
return request.get('/mv/detail', {
|
||||
params: {
|
||||
mvid,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 获取 mv 地址
|
||||
export const getMvUrl = (id: Number) => {
|
||||
return request.get<IData<IMvUrlData>>('/mv/url', {
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -29,6 +29,16 @@ const layoutRouter = [
|
||||
},
|
||||
component: () => import('@/views/list/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/mv',
|
||||
name: 'mv',
|
||||
mate: {
|
||||
title: 'MV',
|
||||
keepAlive: true,
|
||||
icon: 'icon-recordfill',
|
||||
},
|
||||
component: () => import('@/views/mv/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/user',
|
||||
name: 'user',
|
||||
|
||||
4
src/type/index.ts
Normal file
4
src/type/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface IData<T> {
|
||||
code: number
|
||||
data: T
|
||||
}
|
||||
112
src/type/mv.ts
Normal file
112
src/type/mv.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
export interface IMvItem {
|
||||
id: number
|
||||
cover: string
|
||||
name: string
|
||||
playCount: number
|
||||
briefDesc?: any
|
||||
desc?: any
|
||||
artistName: string
|
||||
artistId: number
|
||||
duration: number
|
||||
mark: number
|
||||
mv: IMvData
|
||||
lastRank: number
|
||||
score: number
|
||||
subed: boolean
|
||||
artists: Artist[]
|
||||
transNames?: string[]
|
||||
alias?: string[]
|
||||
}
|
||||
|
||||
export interface IMvData {
|
||||
authId: number
|
||||
status: number
|
||||
id: number
|
||||
title: string
|
||||
subTitle: string
|
||||
appTitle: string
|
||||
aliaName: string
|
||||
transName: string
|
||||
pic4v3: number
|
||||
pic16v9: number
|
||||
caption: number
|
||||
captionLanguage: string
|
||||
style?: any
|
||||
mottos: string
|
||||
oneword?: any
|
||||
appword: string
|
||||
stars?: any
|
||||
desc: string
|
||||
area: string
|
||||
type: string
|
||||
subType: string
|
||||
neteaseonly: number
|
||||
upban: number
|
||||
topWeeks: string
|
||||
publishTime: string
|
||||
online: number
|
||||
score: number
|
||||
plays: number
|
||||
monthplays: number
|
||||
weekplays: number
|
||||
dayplays: number
|
||||
fee: number
|
||||
artists: Artist[]
|
||||
videos: Video[]
|
||||
}
|
||||
|
||||
interface Video {
|
||||
tagSign: TagSign
|
||||
tag: string
|
||||
url: string
|
||||
duration: number
|
||||
size: number
|
||||
width: number
|
||||
height: number
|
||||
container: string
|
||||
md5: string
|
||||
check: boolean
|
||||
}
|
||||
|
||||
interface TagSign {
|
||||
br: number
|
||||
type: string
|
||||
tagSign: string
|
||||
resolution: number
|
||||
mvtype: string
|
||||
}
|
||||
|
||||
interface Artist {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
// {
|
||||
// "id": 14686812,
|
||||
// "url": "http://vodkgeyttp8.vod.126.net/cloudmusic/e18b/core/aa57/6f56150a35613ef77fc70b253bea4977.mp4?wsSecret=84a301277e05143de1dd912d2a4dbb0d&wsTime=1703668700",
|
||||
// "r": 1080,
|
||||
// "size": 215391070,
|
||||
// "md5": "",
|
||||
// "code": 200,
|
||||
// "expi": 3600,
|
||||
// "fee": 0,
|
||||
// "mvFee": 0,
|
||||
// "st": 0,
|
||||
// "promotionVo": null,
|
||||
// "msg": ""
|
||||
// }
|
||||
|
||||
export interface IMvUrlData {
|
||||
id: number
|
||||
url: string
|
||||
r: number
|
||||
size: number
|
||||
md5: string
|
||||
code: number
|
||||
expi: number
|
||||
fee: number
|
||||
mvFee: number
|
||||
st: number
|
||||
promotionVo: null | any
|
||||
msg: string
|
||||
}
|
||||
@@ -25,6 +25,18 @@ export const secondToMinute = (s: number) => {
|
||||
return minuteStr + ':' + secondStr
|
||||
}
|
||||
|
||||
// 格式化数字 千,万, 百万, 千万,亿
|
||||
export const formatNumber = (num: any) => {
|
||||
num = num * 1
|
||||
if (num < 10000) {
|
||||
return num
|
||||
}
|
||||
if (num < 100000000) {
|
||||
return (num / 10000).toFixed(1) + '万'
|
||||
}
|
||||
return (num / 100000000).toFixed(1) + '亿'
|
||||
}
|
||||
|
||||
export const getIsMc = () => {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { getRecommendList, getListDetail, getListByCat } from '@/api/list'
|
||||
import type { IRecommendItem } from "@/type/list";
|
||||
import type { IListDetail } from "@/type/listDetail";
|
||||
import { setAnimationClass, setAnimationDelay, getImgUrl } from "@/utils";
|
||||
import { setAnimationClass, setAnimationDelay, getImgUrl, formatNumber } from "@/utils";
|
||||
import { useRoute } from 'vue-router';
|
||||
import MusicList from "@/components/MusicList.vue";
|
||||
import PlayBottom from '@/components/common/PlayBottom.vue';
|
||||
@@ -52,17 +52,8 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
// 格式化数字 千,万, 百万, 千万,亿
|
||||
const formatNumber = (num: any) => {
|
||||
num = num * 1
|
||||
if (num < 10000) {
|
||||
return num
|
||||
}
|
||||
if (num < 100000000) {
|
||||
return (num / 10000).toFixed(1) + '万'
|
||||
}
|
||||
return (num / 100000000).toFixed(1) + '亿'
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -115,7 +106,7 @@ const formatNumber = (num: any) => {
|
||||
|
||||
&-list {
|
||||
@apply grid gap-6 pb-28 pr-3;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
}
|
||||
&-item {
|
||||
&-img {
|
||||
|
||||
146
src/views/mv/index.vue
Normal file
146
src/views/mv/index.vue
Normal file
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<div class="mv-list">
|
||||
<div class="mv-list-title">
|
||||
<h2>推荐MV</h2>
|
||||
</div>
|
||||
<n-scrollbar :size="100">
|
||||
<div class="mv-list-content" :class="setAnimationClass('animate__bounceInLeft')">
|
||||
<div class="mv-item" v-for="(item, index) in mvList" :key="item.id"
|
||||
:class="setAnimationClass('animate__bounceIn')" :style="setAnimationDelay(index, 30)">
|
||||
<div class="mv-item-img" @click="handleShowMv(item)">
|
||||
<n-image class="mv-item-img-img" :src="getImgUrl((item.cover), '200y200')" 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>
|
||||
</n-scrollbar>
|
||||
|
||||
<n-drawer :show="showMv" height="100vh" placement="bottom">
|
||||
<div class="mv-detail">
|
||||
<div class="mv-detail-title">
|
||||
<div class="title">{{ playMvItem?.name }}</div>
|
||||
<button @click="close">
|
||||
<i class="iconfont icon-xiasanjiaoxing"></i>
|
||||
</button>
|
||||
</div>
|
||||
<video :src="playMvUrl" controls autoplay></video>
|
||||
</div>
|
||||
</n-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { getTopMv, getMvUrl } from '@/api/mv';
|
||||
import { IMvItem } from '@/type/mv';
|
||||
import { setAnimationClass, setAnimationDelay, getImgUrl, formatNumber } from "@/utils";
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
const showMv = ref(false)
|
||||
const mvList = ref<Array<IMvItem>>([])
|
||||
const playMvItem = ref<IMvItem>()
|
||||
const playMvUrl = ref<string>()
|
||||
const store = useStore()
|
||||
|
||||
onMounted(async () => {
|
||||
const res = await getTopMv(30)
|
||||
mvList.value = res.data.data
|
||||
console.log('mvList.value', mvList.value)
|
||||
})
|
||||
|
||||
const handleShowMv = async (item: IMvItem) => {
|
||||
store.commit('setIsPlay', false)
|
||||
showMv.value = true
|
||||
const res = await getMvUrl(item.id)
|
||||
playMvItem.value = item;
|
||||
playMvUrl.value = res.data.data.url
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
showMv.value = false
|
||||
if (store.state.playMusicUrl) {
|
||||
store.commit('setIsPlay', true)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mv-list {
|
||||
@apply relative h-full w-full pt-4;
|
||||
|
||||
&-content {
|
||||
@apply grid gap-6 pb-28 pr-3;
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
}
|
||||
|
||||
.mv-item {
|
||||
&-img {
|
||||
@apply rounded-xl overflow-hidden relative;
|
||||
|
||||
&:hover img {
|
||||
@apply hover:scale-110 transition-all duration-300 ease-in-out;
|
||||
}
|
||||
|
||||
&-img {
|
||||
@apply h-full w-full rounded-xl 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;
|
||||
background-color: #00000088;
|
||||
opacity: 0;
|
||||
|
||||
i {
|
||||
font-size: 50px;
|
||||
transition: all 0.5s ease-in-out;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@apply opacity-100;
|
||||
}
|
||||
|
||||
&:hover i {
|
||||
@apply transform scale-150 opacity-100;
|
||||
}
|
||||
|
||||
.play-count {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-title {
|
||||
@apply p-2 text-sm text-white truncate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mv-detail {
|
||||
@apply w-full h-full bg-black relative;
|
||||
|
||||
&-title {
|
||||
@apply absolute w-full top-12 left-0 flex justify-between z-50 h-10 px-4 py-2 text-lg items-center;
|
||||
background: linear-gradient(0, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.8) 100%);
|
||||
|
||||
button .icon-xiasanjiaoxing {
|
||||
@apply text-2xl;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
@apply text-green-400;
|
||||
}
|
||||
}
|
||||
|
||||
video {
|
||||
@apply w-full h-full;
|
||||
}
|
||||
}</style>
|
||||
Reference in New Issue
Block a user