可以播放了

This commit is contained in:
algerkong
2021-07-20 22:46:18 +08:00
parent bb1bea4002
commit b55af4babd
7 changed files with 248 additions and 85 deletions

1
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1 @@
{}

7
src/api/music.ts Normal file
View File

@@ -0,0 +1,7 @@
import { IPlayMusicUrl } from "@/type/music";
import request from "@/utils/request";
// 根据音乐Id获取音乐播放URl
export const getMusicUrl = (id: number) => {
return request.get<IPlayMusicUrl>("/song/url", { params: { id: id } });
};

View File

@@ -1,39 +1,50 @@
<template>
<div class="layout-page">
<div class="layout-main">
<app-menu class="menu" :menus="menus" />
<div class="main">
<div class="search-box flex">
<div class="search-box-input flex-1">
<n-input
size="large"
round
:placeholder="searchKeyword"
class="border border-gray-600"
>
<template #prefix>
<i class="iconfont icon-search"></i>
</template>
</n-input>
</div>
<div class="user-box">
<n-popselect
v-model:value="value"
:options="options"
trigger="click"
size="small"
>
<i class="iconfont icon-xiasanjiaoxing"></i>
</n-popselect>
<n-avatar
class="ml-2"
circle
size="large"
src="https://picsum.photos/200/300?random=1"
/>
<div class="flex">
<app-menu class="menu" :menus="menus" />
<div class="main">
<div class="search-box flex">
<div class="search-box-input flex-1">
<n-input
size="large"
round
:placeholder="searchKeyword"
class="border border-gray-600"
>
<template #prefix>
<i class="iconfont icon-search"></i>
</template>
</n-input>
</div>
<div class="user-box">
<n-popselect
v-model:value="value"
:options="options"
trigger="click"
size="small"
>
<i class="iconfont icon-xiasanjiaoxing"></i>
</n-popselect>
<n-avatar
class="ml-2"
circle
size="large"
src="https://picsum.photos/200/300?random=1"
/>
</div>
</div>
<router-view class="main-content"></router-view>
</div>
<router-view></router-view>
</div>
<div class="music-play-bar" v-show="isPlay">
<img :src="playMusic.picUrl" width="50" height="50" />
<div class="music-name"></div>
<div class="music-singer"></div>
<div class="music-time"></div>
<!-- 播放音乐 -->
<audio :src="playMusicUrl" autoplay></audio>
<n-button @click="playMusicEvent">playMusicEvent</n-button>
</div>
</div>
</div>
@@ -43,11 +54,25 @@
import { useStore } from 'vuex';
import { AppMenu } from './components';
import { getSearchKeyword } from '@/api/home';
import { ref, onMounted } from 'vue';
import { ref, onMounted, computed } from 'vue';
import type { SongResult } from "@/type/music";
import { getMusicUrl } from '@/api/music';
const store = useStore();
const menus = store.state.menus;
const playMusic = computed(() => store.state.playMusic as SongResult)
const isPlay = computed(() => store.state.isPlay)
const playMusicUrl = ref("");
const playMusicEvent = async () => {
console.log(playMusic);
const { data } = await getMusicUrl(playMusic.value.id);
console.log(data);
playMusicUrl.value = data.data[0].url;
}
const value = 'Drive My Car'
const options = [
@@ -86,7 +111,7 @@ onMounted(() => {
}
.layout-main {
@apply bg-black rounded-lg mb-10 text-white shadow-xl flex;
@apply bg-black rounded-lg mb-10 text-white shadow-xl flex-col relative;
height: 900px;
width: 1500px;
overflow: hidden;
@@ -95,13 +120,21 @@ onMounted(() => {
width: 90px;
}
.main {
@apply pt-6 pr-6 pb-6;
flex: 1;
@apply pt-6 pr-6 flex-1 box-border;
.main-content {
@apply rounded-2xl;
height: 810px;
}
}
.user-box {
@apply ml-6 flex text-lg justify-center items-center rounded-full pl-3 border border-gray-600;
background: #1a1a1a;
}
.music-play-bar {
@apply h-20 w-full absolute bottom-0 left-0 flex rounded-t-2xl overflow-hidden border border-gray-600 box-border px-4 py-2;
background-color: #1a1a1a;
}
}
</style>

View File

@@ -21,6 +21,13 @@
<script lang="ts" setup>
import { onMounted, ref } from "@vue/runtime-core";
import type { PropType } from "vue";
interface AppMenuItem {
href: string;
icon: string;
text: string;
}
const props = defineProps({
isText: {
@@ -36,7 +43,7 @@ const props = defineProps({
default: '#aaa'
},
menus: {
type: Array,
type: Array as PropType<AppMenuItem[]>,
default: []
}
})

View File

@@ -1,4 +1,5 @@
import { createStore } from "vuex";
import { SongResult } from "@/type/music";
let state = {
menus: [
@@ -18,9 +19,25 @@ let state = {
text: "hello",
},
],
isPlay: false,
playMusic: {} as SongResult,
};
let mutations = {};
let mutations = {
setMenus(state: any, menus: any[]) {
state.menus = menus;
},
setPlay(state: any, playMusic: SongResult) {
console.log(playMusic);
state.playMusic = playMusic;
},
setIsPlay(state: any, isPlay: boolean) {
console.log(isPlay);
state.isPlay = isPlay;
},
};
const store = createStore({
state: state,

View File

@@ -1,10 +1,10 @@
export interface IRecommendMusic {
code: number;
category: number;
result: Result[];
result: SongResult[];
}
interface Result {
export interface SongResult {
id: number;
type: number;
name: string;
@@ -154,3 +154,43 @@ interface Artist {
musicSize: number;
topicPerson: number;
}
export interface IPlayMusicUrl {
data: Datum[];
code: number;
}
interface Datum {
id: number;
url: string;
br: number;
size: number;
md5: string;
code: number;
expi: number;
type: string;
gain: number;
fee: number;
uf?: any;
payed: number;
flag: number;
canExtend: boolean;
freeTrialInfo?: any;
level: string;
encodeType: string;
freeTrialPrivilege: FreeTrialPrivilege;
freeTimeTrialPrivilege: FreeTimeTrialPrivilege;
urlSource: number;
}
interface FreeTimeTrialPrivilege {
resConsumable: boolean;
userConsumable: boolean;
type: number;
remainTime: number;
}
interface FreeTrialPrivilege {
resConsumable: boolean;
userConsumable: boolean;
}

View File

@@ -1,6 +1,6 @@
<template>
<n-layout class="h-full bg-black" :native-scrollbar="false">
<div class="main-page">
<div class="main-page pb-20">
<!-- 推荐歌手 -->
<div class="recommend-singer">
<div class="recommend-singer-list">
@@ -33,28 +33,46 @@
<template v-for="(item,index) in playlistCategory?.sub" :key="item.name">
<span
class="play-list-type-item animate__animated animate__bounceIn animate__repeat-1"
:style="getPlaylistTypeStyle(index <= 8 ? index : index - 8)"
v-if="isShowAllPlaylistCategory || index <= 8"
:style="getPlaylistTypeStyle(index <= 13 ? index : index - 13)"
v-if="isShowAllPlaylistCategory || index <= 13"
>{{ item.name }}</span>
</template>
<div
class="play-list-type-showall animate__animated animate__bounceIn animate__repeat-1"
:style="getPlaylistTypeStyle(!isShowAllPlaylistCategory ? 25 : playlistCategory?.sub.length + 30)"
:style="getPlaylistTypeStyle(!isShowAllPlaylistCategory ? 25 : playlistCategory?.sub.length || 100 + 30)"
@click="isShowAllPlaylistCategory = !isShowAllPlaylistCategory"
>{{ !isShowAllPlaylistCategory ? '显示全部' : '隐藏一些' }}</div>
</n-layout>
</div>
<div class="recommend-music">
<div class="title">本周最热音乐</div>
<n-layout class=" recommend-music-list " >
<n-space vertical>
<n-layout class="recommend-music-list">
<n-space vertical size="large">
<!-- 推荐音乐列表 -->
<div>
<img v-for="item in recommendMusic?.result" :src="item.picUrl" width="100" height="50" />
<div>
</div>
</div>
<template v-for="item in recommendMusic?.result" :key="item.id">
<div class="recommend-music-list-item">
<img :src="item.picUrl" class="recommend-music-list-item-img" />
<div class="recommend-music-list-item-content">
<div class="recommend-music-list-item-content-title">
<n-ellipsis class="text-ellipsis" line-clamp="1">{{ item.song.name }}</n-ellipsis>
</div>
<div class="recommend-music-list-item-content-name">
<n-ellipsis
class="text-ellipsis"
line-clamp="1"
>{{ item.song.artists[0].name }}</n-ellipsis>
</div>
</div>
<div class="recommend-music-list-item-operating">
<div class="recommend-music-list-item-operating-like">
<i class="iconfont icon-likefill"></i>
</div>
<div class="recommend-music-list-item-operating-play" @click="playMusic(item)">
<i class="iconfont icon-playfill"></i>
</div>
</div>
</div>
</template>
</n-space>
</n-layout>
</div>
@@ -65,64 +83,68 @@
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import { getHotSinger, getPlaylistCategory, getRecommendMusic } from "@/api/home"
import {
getHotSinger,
getPlaylistCategory,
getRecommendMusic,
} from "@/api/home";
import type { IHotSinger } from "@/type/singer";
import type { IPlayListSort } from "@/type/playlist";
import type { IRecommendMusic } from "@/type/music";
import type { IRecommendMusic, SongResult } from "@/type/music";
import { useStore } from "vuex";
// 歌手信息
const hotSingerData = ref<IHotSinger>()
const hotSingerData = ref<IHotSinger>();
// 歌单分类
const playlistCategory = ref<IPlayListSort>()
const playlistCategory = ref<IPlayListSort>();
// 是否显示全部歌单分类
const isShowAllPlaylistCategory = ref<boolean>(false)
const isShowAllPlaylistCategory = ref<boolean>(false);
// 推荐歌曲
const recommendMusic = ref<IRecommendMusic>()
const recommendMusic = ref<IRecommendMusic>();
// 设置歌手背景图片
const getStyle = (item: any) => {
return {
"background-image": "url(" + item.picUrl + ")"
}
}
return "background-image:" + "url(" + item.picUrl + ")";
};
// 设置歌单分类样式
const getPlaylistTypeStyle = (index: number) => {
return {
"animation-delay": index * 25 + "ms"
}
}
return "animation-delay:" + index * 25 + "ms";
};
//加载推荐歌手
const loadSingerList = async () => {
const { data } = await getHotSinger({ offset: 0, limit: 5 })
hotSingerData.value = data
}
const { data } = await getHotSinger({ offset: 0, limit: 5 });
hotSingerData.value = data;
};
// 加载歌单分类
const loadPlaylistCategory = async () => {
const { data } = await getPlaylistCategory()
playlistCategory.value = data
}
const { data } = await getPlaylistCategory();
playlistCategory.value = data;
};
// 加载推荐歌曲
const loadRecommendMusic = async () => {
const { data } = await getRecommendMusic({ limit: 6 })
recommendMusic.value = data
const { data } = await getRecommendMusic({ limit: 6 });
recommendMusic.value = data;
};
const store = useStore()
const playMusic = (item: SongResult) => {
store.commit('setPlay', item)
store.commit('setIsPlay', true)
}
// 页面初始化
onMounted(() => {
loadSingerList()
loadPlaylistCategory()
loadRecommendMusic()
})
loadSingerList();
loadPlaylistCategory();
loadRecommendMusic();
});
</script>
<style lang="scss" scoped>
.recommend-singer {
&-list {
@apply flex mt-5;
@apply flex;
height: 350px;
}
&-item {
@@ -134,7 +156,7 @@ onMounted(() => {
&-info {
@apply flex items-center p-2;
&-play {
@apply w-12 h-12 bg-green-500 rounded-full flex justify-center items-center;
@apply w-12 h-12 bg-green-500 rounded-full flex justify-center items-center hover:bg-green-600 cursor-pointer;
}
}
}
@@ -159,10 +181,46 @@ onMounted(() => {
}
.recommend-music {
.text-ellipsis {
width: 100%;
}
@apply flex-1 mr-96;
&-list{
&-list {
@apply rounded-3xl p-6 w-full border border-gray-700;
background-color: #0D0D0D;
background-color: #0d0d0d;
&-item {
@apply flex items-center;
&-img {
@apply w-14 h-14 rounded-xl mr-4;
}
&-content {
@apply flex-1;
&-title {
@apply text-lg;
}
&-name {
@apply text-sm mt-1;
@apply text-gray-400;
}
}
&-operating {
@apply flex items-center pl-4 rounded-full border border-gray-700;
background-color: #1a1a1a;
.iconfont {
@apply text-2xl;
}
.icon-likefill {
@apply text-xl text-gray-300 hover:text-red-600 transition;
}
&-like {
@apply mr-2 cursor-pointer;
}
&-play {
@apply bg-green-500 cursor-pointer rounded-full w-10 h-10 flex justify-center items-center hover:bg-green-600 transition;
}
}
}
}
}
}