mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-14 06:30:49 +08:00
优化动画效果,抽出组件
This commit is contained in:
68
src/components/PlaylistType.vue
Normal file
68
src/components/PlaylistType.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<!-- 歌单分类列表 -->
|
||||
<div class="play-list-type">
|
||||
<div class="title animate__animated animate__fadeInLeft">歌单分类</div>
|
||||
<n-layout class="bg-black">
|
||||
<template v-for="(item, index) in playlistCategory?.sub" :key="item.name">
|
||||
<span
|
||||
class="play-list-type-item"
|
||||
:class="setAnimationClass('animate__bounceIn')"
|
||||
:style="setAnimationDelay(index <= 13 ? index : index - 13)"
|
||||
v-if="isShowAllPlaylistCategory || index <= 13"
|
||||
>{{ item.name }}</span>
|
||||
</template>
|
||||
<div
|
||||
class="play-list-type-showall"
|
||||
:class="setAnimationClass('animate__bounceIn')"
|
||||
:style="
|
||||
setAnimationDelay(
|
||||
!isShowAllPlaylistCategory
|
||||
? 25
|
||||
: playlistCategory?.sub.length || 100 + 30
|
||||
)
|
||||
"
|
||||
@click="isShowAllPlaylistCategory = !isShowAllPlaylistCategory"
|
||||
>{{ !isShowAllPlaylistCategory ? "显示全部" : "隐藏一些" }}</div>
|
||||
</n-layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { getPlaylistCategory } from "@/api/home";
|
||||
import type { IPlayListSort } from "@/type/playlist";
|
||||
import { setAnimationDelay, setAnimationClass } from "@/utils";
|
||||
// 歌单分类
|
||||
const playlistCategory = ref<IPlayListSort>();
|
||||
// 是否显示全部歌单分类
|
||||
const isShowAllPlaylistCategory = ref<boolean>(false);
|
||||
|
||||
// 加载歌单分类
|
||||
const loadPlaylistCategory = async () => {
|
||||
const { data } = await getPlaylistCategory();
|
||||
playlistCategory.value = data;
|
||||
};
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
loadPlaylistCategory();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
@apply text-lg font-bold mb-4;
|
||||
}
|
||||
.play-list-type {
|
||||
width: 250px;
|
||||
@apply mr-6;
|
||||
&-item,
|
||||
&-showall {
|
||||
@apply py-2 px-3 mr-3 mb-3 inline-block border border-gray-700 rounded-xl cursor-pointer hover:bg-green-600 transition;
|
||||
background-color: #1a1a1a;
|
||||
}
|
||||
&-showall {
|
||||
@apply block text-center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
70
src/components/RecommendSinger.vue
Normal file
70
src/components/RecommendSinger.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<!-- 推荐歌手 -->
|
||||
<div class="recommend-singer">
|
||||
<div class="recommend-singer-list">
|
||||
<div
|
||||
class="recommend-singer-item relative"
|
||||
:class="setAnimationClass('animate__backInRight')"
|
||||
v-for="(item, index) in hotSingerData?.artists"
|
||||
:style="setAnimationDelay(index, 100)"
|
||||
:key="item.id"
|
||||
>
|
||||
<div :style="setBackgroundImg(item.picUrl)" class="recommend-singer-item-bg"></div>
|
||||
<div
|
||||
class="recommend-singer-item-count p-2 text-base text-gray-200 z-10"
|
||||
>{{ item.musicSize }}首</div>
|
||||
<div class="recommend-singer-item-info z-10">
|
||||
<div class="recommend-singer-item-info-play">
|
||||
<i class="iconfont icon-playfill text-xl"></i>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<div class="recommend-singer-item-info-name">{{ item.name }}</div>
|
||||
<div class="recommend-singer-item-info-name">{{ item.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { setBackgroundImg, setAnimationDelay, setAnimationClass } from "@/utils";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { getHotSinger } from "@/api/home";
|
||||
import type { IHotSinger } from "@/type/singer";
|
||||
|
||||
// 歌手信息
|
||||
const hotSingerData = ref<IHotSinger>();
|
||||
|
||||
//加载推荐歌手
|
||||
const loadSingerList = async () => {
|
||||
const { data } = await getHotSinger({ offset: 0, limit: 5 });
|
||||
hotSingerData.value = data;
|
||||
};
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
loadSingerList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.recommend-singer {
|
||||
&-list {
|
||||
@apply flex;
|
||||
height: 350px;
|
||||
}
|
||||
&-item {
|
||||
@apply flex-1 h-full rounded-3xl p-5 mr-5 flex flex-col justify-between;
|
||||
&-bg {
|
||||
@apply bg-gray-900 bg-no-repeat bg-cover bg-center rounded-3xl absolute w-full h-full top-0 left-0 z-0;
|
||||
filter: brightness(80%);
|
||||
}
|
||||
&-info {
|
||||
@apply flex items-center p-2;
|
||||
&-play {
|
||||
@apply w-12 h-12 bg-green-500 rounded-full flex justify-center items-center hover:bg-green-600 cursor-pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
52
src/components/RecommendSonglist.vue
Normal file
52
src/components/RecommendSonglist.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div class="recommend-music">
|
||||
<div class="title animate__animated animate__fadeInLeft">本周最热音乐</div>
|
||||
<div
|
||||
class="recommend-music-list"
|
||||
:class="setAnimationClass('animate__bounceInUp')"
|
||||
v-show="recommendMusic?.result"
|
||||
>
|
||||
<!-- 推荐音乐列表 -->
|
||||
<template v-for="(item, index) in recommendMusic?.result" :key="item.id">
|
||||
<song-item :item="item" :index="index" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { getRecommendMusic } from "@/api/home";
|
||||
import type { IRecommendMusic } from "@/type/music";
|
||||
import { setAnimationClass } from "@/utils";
|
||||
import SongItem from "./common/SongItem.vue";
|
||||
// 推荐歌曲
|
||||
const recommendMusic = ref<IRecommendMusic>();
|
||||
|
||||
// 加载推荐歌曲
|
||||
const loadRecommendMusic = async () => {
|
||||
const { data } = await getRecommendMusic({ limit: 10 });
|
||||
recommendMusic.value = data;
|
||||
};
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
loadRecommendMusic();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
@apply text-lg font-bold mb-4;
|
||||
}
|
||||
.recommend-music {
|
||||
.text-ellipsis {
|
||||
width: 100%;
|
||||
}
|
||||
@apply flex-1 mr-96;
|
||||
&-list {
|
||||
@apply rounded-3xl p-2 w-full border border-gray-700;
|
||||
background-color: #0d0d0d;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
91
src/components/common/SongItem.vue
Normal file
91
src/components/common/SongItem.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div
|
||||
class="recommend-music-list-item"
|
||||
:class="setAnimationClass('animate__bounceInUp')"
|
||||
:style="setAnimationDelay(index, 200)"
|
||||
>
|
||||
<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>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { setAnimationDelay, setAnimationClass } from "@/utils";
|
||||
import { useStore } from "vuex";
|
||||
import type { SongResult } from "@/type/music";
|
||||
import type { PropType } from "vue";
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object as PropType<SongResult>,
|
||||
required: true
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const store = useStore();
|
||||
const playMusic = (item: SongResult) => {
|
||||
store.commit("setPlay", item);
|
||||
store.commit("setIsPlay", true);
|
||||
};
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.text-ellipsis {
|
||||
width: 100%;
|
||||
}
|
||||
.recommend-music-list-item {
|
||||
@apply rounded-3xl p-3 flex items-center hover:bg-gray-800 transition;
|
||||
&-img {
|
||||
@apply w-12 h-12 rounded-2xl mr-4;
|
||||
}
|
||||
&-content {
|
||||
@apply flex-1;
|
||||
&-title {
|
||||
@apply text-base text-white;
|
||||
}
|
||||
&-name {
|
||||
@apply text-xs;
|
||||
@apply text-gray-400;
|
||||
}
|
||||
}
|
||||
&-operating {
|
||||
@apply flex items-center pl-4 rounded-full border border-gray-700;
|
||||
background-color: #0d0d0d;
|
||||
.iconfont {
|
||||
@apply text-xl;
|
||||
}
|
||||
.icon-likefill {
|
||||
color: #868686;
|
||||
@apply text-xl hover:text-red-600 transition;
|
||||
}
|
||||
&-like {
|
||||
@apply mr-2 cursor-pointer;
|
||||
}
|
||||
&-play {
|
||||
@apply bg-black cursor-pointer border border-gray-500 rounded-full w-10 h-10 flex justify-center items-center hover:bg-green-600 transition;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,105 +2,26 @@
|
||||
<div class="layout-page">
|
||||
<div class="layout-main">
|
||||
<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>
|
||||
<!-- 搜索栏 -->
|
||||
<search-bar />
|
||||
<!-- 主页面路由 -->
|
||||
<router-view class="main-content"></router-view>
|
||||
</div>
|
||||
</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>
|
||||
<!-- 底部音乐播放 -->
|
||||
<play-bar />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useStore } from 'vuex';
|
||||
import { AppMenu } from './components';
|
||||
import { getSearchKeyword } from '@/api/home';
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import type { SongResult } from "@/type/music";
|
||||
import { getMusicUrl } from '@/api/music';
|
||||
|
||||
import { AppMenu, PlayBar, SearchBar } from './components';
|
||||
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 = [
|
||||
{
|
||||
label: 'Girl',
|
||||
value: 'Girl'
|
||||
},
|
||||
{
|
||||
label: 'In My Life',
|
||||
value: 'In My Life'
|
||||
},
|
||||
{
|
||||
label: 'Wait',
|
||||
value: 'Wait'
|
||||
}
|
||||
]
|
||||
|
||||
const searchKeyword = ref<String>("搜索点什么吧...")
|
||||
|
||||
const loadSearchKeyword = async () => {
|
||||
const { data } = await getSearchKeyword();
|
||||
searchKeyword.value = data.data.showKeyword
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadSearchKeyword()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -126,15 +47,5 @@ onMounted(() => {
|
||||
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>
|
||||
61
src/layout/components/PlayBar.vue
Normal file
61
src/layout/components/PlayBar.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="music-play-bar" v-if="isPlay && playMusic">
|
||||
<img class="play-bar-img" :src="playMusic.picUrl" />
|
||||
<div class="music-content">
|
||||
<div class="music-content-title">{{ playMusic.name }}</div>
|
||||
<div class="music-content-name">{{ playMusic.song.artists[0].name }}</div>
|
||||
</div>
|
||||
<div class="music-time"></div>
|
||||
<!-- 播放音乐 -->
|
||||
<audio :src="playMusicUrl" autoplay></audio>
|
||||
<n-button @click="playMusicEvent">playMusicEvent</n-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { useStore } from 'vuex';
|
||||
import type { SongResult } from "@/type/music";
|
||||
import { getMusicUrl } from '@/api/music';
|
||||
|
||||
const store = useStore();
|
||||
const playMusic = computed(() => store.state.playMusic as SongResult)
|
||||
console.log(playMusic.value);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.music-play-bar {
|
||||
@apply h-20 w-full absolute bottom-0 left-0 flex items-center rounded-t-2xl overflow-hidden box-border px-6 py-2;
|
||||
background-color: #212121;
|
||||
}
|
||||
|
||||
.play-bar-img {
|
||||
@apply w-14 h-14 rounded-2xl;
|
||||
}
|
||||
|
||||
.music-content {
|
||||
width: 200px;
|
||||
@apply ml-4;
|
||||
&-title {
|
||||
@apply text-base text-white;
|
||||
}
|
||||
&-name {
|
||||
@apply text-xs mt-1;
|
||||
@apply text-gray-400;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
65
src/layout/components/SearchBar.vue
Normal file
65
src/layout/components/SearchBar.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getSearchKeyword } from '@/api/home';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
|
||||
const searchKeyword = ref<String>("搜索点什么吧...")
|
||||
|
||||
const loadSearchKeyword = async () => {
|
||||
const { data } = await getSearchKeyword();
|
||||
searchKeyword.value = data.data.showKeyword
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadSearchKeyword()
|
||||
})
|
||||
|
||||
|
||||
|
||||
const value = 'Drive My Car'
|
||||
const options = [
|
||||
{
|
||||
label: 'Girl',
|
||||
value: 'Girl'
|
||||
},
|
||||
{
|
||||
label: 'In My Life',
|
||||
value: 'In My Life'
|
||||
},
|
||||
{
|
||||
label: 'Wait',
|
||||
value: 'Wait'
|
||||
}
|
||||
]
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-box {
|
||||
@apply ml-6 flex text-lg justify-center items-center rounded-full pl-3 border border-gray-600;
|
||||
background: #1a1a1a;
|
||||
}
|
||||
</style>
|
||||
@@ -1,3 +1,5 @@
|
||||
import AppMenu from "./AppMenu.vue";
|
||||
import PlayBar from "./PlayBar.vue";
|
||||
import SearchBar from "./SearchBar.vue";
|
||||
|
||||
export { AppMenu };
|
||||
export { AppMenu, PlayBar, SearchBar };
|
||||
|
||||
12
src/utils/index.ts
Normal file
12
src/utils/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// 设置歌手背景图片
|
||||
export const setBackgroundImg = (url: String) => {
|
||||
return "background-image:" + "url(" + url + ")";
|
||||
};
|
||||
// 设置动画类型
|
||||
export const setAnimationClass = (type: String) => {
|
||||
return "animate__animated " + type;
|
||||
};
|
||||
// 设置动画延时
|
||||
export const setAnimationDelay = (index: number = 6, time: number = 50) => {
|
||||
return "animation-delay:" + index * time + "ms";
|
||||
};
|
||||
@@ -2,226 +2,26 @@
|
||||
<n-layout class="h-full bg-black" :native-scrollbar="false">
|
||||
<div class="main-page pb-20">
|
||||
<!-- 推荐歌手 -->
|
||||
<div class="recommend-singer">
|
||||
<div class="recommend-singer-list">
|
||||
<div
|
||||
class="recommend-singer-item relative"
|
||||
v-for="(item,index) in hotSingerData?.artists"
|
||||
:key="item.id"
|
||||
>
|
||||
<div :style="getStyle(item)" class="recommend-singer-item-bg"></div>
|
||||
<div
|
||||
class="recommend-singer-item-count p-2 text-base text-gray-200 z-10"
|
||||
>{{ item.musicSize }}首</div>
|
||||
<div class="recommend-singer-item-info z-10">
|
||||
<div class="recommend-singer-item-info-play">
|
||||
<i class="iconfont icon-playfill text-xl"></i>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<div class="recommend-singer-item-info-name">{{ item.name }}</div>
|
||||
<div class="recommend-singer-item-info-name">{{ item.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<recommend-singer />
|
||||
<div class="main-content">
|
||||
<!-- 歌单分类列表 -->
|
||||
<div class="play-list-type">
|
||||
<div class="title">歌单分类</div>
|
||||
<n-layout class="bg-black">
|
||||
<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 <= 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 || 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 size="large">
|
||||
<!-- 推荐音乐列表 -->
|
||||
<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>
|
||||
<playlist-type />
|
||||
<!-- 本周最热音乐 -->
|
||||
<recommend-songlist />
|
||||
</div>
|
||||
</div>
|
||||
</n-layout>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import {
|
||||
getHotSinger,
|
||||
getPlaylistCategory,
|
||||
getRecommendMusic,
|
||||
} from "@/api/home";
|
||||
import type { IHotSinger } from "@/type/singer";
|
||||
import type { IPlayListSort } from "@/type/playlist";
|
||||
import type { IRecommendMusic, SongResult } from "@/type/music";
|
||||
import { useStore } from "vuex";
|
||||
import RecommendSinger from "@/components/RecommendSinger.vue";
|
||||
import PlaylistType from "@/components/PlaylistType.vue";
|
||||
import RecommendSonglist from "@/components/RecommendSonglist.vue";
|
||||
|
||||
// 歌手信息
|
||||
const hotSingerData = ref<IHotSinger>();
|
||||
// 歌单分类
|
||||
const playlistCategory = ref<IPlayListSort>();
|
||||
// 是否显示全部歌单分类
|
||||
const isShowAllPlaylistCategory = ref<boolean>(false);
|
||||
// 推荐歌曲
|
||||
const recommendMusic = ref<IRecommendMusic>();
|
||||
|
||||
// 设置歌手背景图片
|
||||
const getStyle = (item: any) => {
|
||||
return "background-image:" + "url(" + item.picUrl + ")";
|
||||
};
|
||||
// 设置歌单分类样式
|
||||
const getPlaylistTypeStyle = (index: number) => {
|
||||
return "animation-delay:" + index * 25 + "ms";
|
||||
};
|
||||
//加载推荐歌手
|
||||
const loadSingerList = async () => {
|
||||
const { data } = await getHotSinger({ offset: 0, limit: 5 });
|
||||
hotSingerData.value = data;
|
||||
};
|
||||
// 加载歌单分类
|
||||
const loadPlaylistCategory = async () => {
|
||||
const { data } = await getPlaylistCategory();
|
||||
playlistCategory.value = data;
|
||||
};
|
||||
// 加载推荐歌曲
|
||||
const loadRecommendMusic = async () => {
|
||||
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();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.recommend-singer {
|
||||
&-list {
|
||||
@apply flex;
|
||||
height: 350px;
|
||||
}
|
||||
&-item {
|
||||
@apply flex-1 h-full rounded-3xl p-5 mr-5 flex flex-col justify-between;
|
||||
&-bg {
|
||||
@apply bg-gray-900 bg-no-repeat bg-cover bg-center rounded-3xl absolute w-full h-full top-0 left-0 z-0;
|
||||
filter: brightness(80%);
|
||||
}
|
||||
&-info {
|
||||
@apply flex items-center p-2;
|
||||
&-play {
|
||||
@apply w-12 h-12 bg-green-500 rounded-full flex justify-center items-center hover:bg-green-600 cursor-pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
@apply mt-6 flex;
|
||||
.title {
|
||||
@apply text-lg font-bold mb-4;
|
||||
}
|
||||
.play-list-type {
|
||||
width: 250px;
|
||||
@apply mr-6;
|
||||
&-item,
|
||||
&-showall {
|
||||
@apply py-2 px-3 mr-3 mb-3 inline-block border border-gray-700 rounded-xl cursor-pointer hover:bg-green-600 transition;
|
||||
background-color: #1a1a1a;
|
||||
}
|
||||
&-showall {
|
||||
@apply block text-center;
|
||||
}
|
||||
}
|
||||
|
||||
.recommend-music {
|
||||
.text-ellipsis {
|
||||
width: 100%;
|
||||
}
|
||||
@apply flex-1 mr-96;
|
||||
&-list {
|
||||
@apply rounded-3xl p-6 w-full border border-gray-700;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user