新增 发现-歌单页面

This commit is contained in:
底层用户
2023-01-08 20:37:31 +08:00
parent 14e3b23e2a
commit 5676b1e1fa
28 changed files with 400 additions and 36 deletions

View File

@@ -2,6 +2,8 @@
> 一个简约的在线音乐播放器,**项目尚未完成**,不保证可用性
![播放页面](/screenshots/SPlayer%20-%20%E6%92%AD%E6%94%BE%E9%A1%B5%E9%9D%A2.png)
## 🎉 功能
- 账号登录( 目前仅支持扫码,更多登录方式待添加

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 KiB

View File

@@ -363,6 +363,28 @@ export const getArtistVideos = (id, limit = 30, offset = 0) => {
* 歌单与专辑部分
*/
// 歌单分类
export const getPlayListCatlist = (hot = false) => {
return axios({
method: "GET",
url: `/playlist/${hot?'hot':'catlist'}`
})
}
// 歌单 ( 网友精选碟 )
export const getTopPlaylist = (cat = "全部", limit = 30, offset = 0, order = "hot") => {
return axios({
method: "GET",
url: "/top/playlist",
params: {
cat,
limit,
offset,
order,
}
})
}
// 获取歌单详情
export const getPlayListDetail = (id) => {
return axios({

View File

@@ -179,6 +179,7 @@ const toLink = (id) => {
align-items: center;
.n-icon {
margin-right: 2px;
transform: translateY(-1px);
}
}
}

View File

@@ -437,7 +437,7 @@ const jumpLink = (id, type) => {
// transform: scale(0.99);
// }
&.play {
background-color: #f55e5520;
background-color: $mainSecondaryColor;
border-color: $mainColor;
a,
span,

View File

@@ -240,7 +240,7 @@ const addPlaylists = (data, row) => {
&.play {
td {
color: $mainColor;
background-color: #f55e5520;
background-color: $mainSecondaryColor;
.n-text {
color: $mainColor;
}

View File

@@ -131,7 +131,7 @@ watch(
}
&.play {
color: $mainColor;
background-color: #f55e5520;
background-color: $mainSecondaryColor;
border-color: $mainColor;
}

View File

@@ -73,6 +73,7 @@
v-html="packageJson.author"
/>
</n-text>
<n-text class="point" v-html="'·'" />
<n-a
v-if="icp"
class="beian"
@@ -432,9 +433,15 @@ nav {
.version {
margin-left: 6px;
}
.beian {
&::before {
content: "·";
.n-blockquote {
@media (max-width: 768px) {
display: flex;
flex-direction: column;
.point {
display: none;
}
}
.point {
margin: 0 4px;
}
}

View File

@@ -14,7 +14,7 @@
<n-tab :name="8"> 日本 </n-tab>
<n-tab :name="16"> 韩国 </n-tab>
</n-tabs>
<span class="more">更多</span>
<span class="more" @click="router.push('/discover/artists')">更多</span>
</n-h3>
<ArtistLists :listData="artistsData" :gridCollapsed="true" />
</div>
@@ -22,8 +22,11 @@
<script setup>
import { getArtistList } from "@/api";
import { useRouter } from "vue-router";
import ArtistLists from "@/components/DataList/ArtistLists.vue";
const router = useRouter();
// 歌手数据
let artistsData = ref([]);

View File

@@ -2,7 +2,7 @@
<div class="paplaylists">
<n-h3 class="title" prefix="bar">
推荐歌单
<span class="more">更多</span>
<span class="more" @click="router.push('/discover/playlists')">更多</span>
</n-h3>
<CoverLists
:listData="personalizedData"

View File

@@ -15,23 +15,23 @@ const routes = [{
component: () => import('@/views/Search/index.vue'),
redirect: '/search/songs',
children: [{
path: '/search/songs',
path: 'songs',
name: 's-songs',
component: () => import('@/views/Search/songs.vue'),
}, {
path: '/search/artists',
path: 'artists',
name: 's-artists',
component: () => import('@/views/Search/artists.vue'),
}, {
path: '/search/albums',
path: 'albums',
name: 's-albums',
component: () => import('@/views/Search/albums.vue'),
}, {
path: '/search/videos',
path: 'videos',
name: 's-videos',
component: () => import('@/views/Search/videos.vue'),
}, {
path: '/search/playlists',
path: 'playlists',
name: 's-playlists',
component: () => import('@/views/Search/playlists.vue'),
}, ]
@@ -63,13 +63,27 @@ const routes = [{
title: '视频',
},
component: () => import('@/views/Video/VideoView.vue'),
},{
}, {
path: '/discover',
name: 'discover',
meta: {
title: '发现',
},
component: () => import('@/views/Discover/index.vue'),
redirect: '/discover/playlists',
children: [{
path: 'playlists',
name: 'dsc-playlists',
component: () => import('@/views/Discover/playlists.vue'),
}, {
path: 'toplists',
name: 'dsc-toplists',
component: () => import('@/views/Discover/toplists.vue'),
}, {
path: 'artists',
name: 'dsc-artists',
component: () => import('@/views/Discover/artists.vue'),
}]
}, {
path: '/playlist',
name: 'playlist',

View File

@@ -26,8 +26,10 @@ const useMusicDataStore = defineStore('musicData', {
showPlayList: false,
// 播放状态
playState: false,
// 每日推荐数据
// 每日推荐
dailySongsData: [],
// 歌单分类
catList: [],
// 持久化数据
persistData: {
// 是否处于私人 FM 模式

View File

@@ -7,6 +7,7 @@
// 主题色
$mainColor: #f55e55;
$mainSecondaryColor: #f55e551f;
// 文本超出隐藏
.text-hidden {

View File

@@ -50,10 +50,8 @@ const getArtistAblumsData = (id, limit = 30, offset = 0) => {
} else {
$message.error("搜索内容为空");
}
nextTick(() => {
// 请求后回顶并结束加载条
$mainContent.scrollIntoView({ behavior: "smooth" });
});
// 请求后回顶
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
});
};

View File

@@ -59,10 +59,8 @@ const getArtistVideosData = (id, limit = 30, offset = 0) => {
} else {
$message.error("搜索内容为空");
}
nextTick(() => {
// 请求后回顶并结束加载条
$mainContent.scrollIntoView({ behavior: "smooth" });
});
// 请求后回顶并结束加载条
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
});
};

View File

@@ -0,0 +1,5 @@
<template>
<div class="artists">
666
</div>
</template>

View File

@@ -1,5 +1,66 @@
<template>
<div class="discover">
<n-h2>未完成</n-h2>
<n-text class="title">发现</n-text>
<n-tabs
class="main-tab"
type="segment"
@update:value="tabChange"
v-model:value="tabValue"
>
<n-tab name="playlists"> 歌单 </n-tab>
<n-tab name="toplists"> 排行榜 </n-tab>
<n-tab name="artists"> 歌手 </n-tab>
</n-tabs>
<main class="content">
<router-view v-slot="{ Component }">
<keep-alive>
<Transition name="move" mode="out-in">
<component :is="Component" />
</Transition>
</keep-alive>
</router-view>
</main>
</div>
</template>
</template>
<script setup>
import { useRouter } from "vue-router";
const router = useRouter();
// Tab 默认选中
let tabValue = ref(router.currentRoute.value.path.split("/")[2]);
// Tab 选项卡变化
const tabChange = (value) => {
console.log(value);
router.push({
path: `/discover/${value}`,
});
};
</script>
<style lang="scss" scoped>
.discover {
.title {
display: block;
margin: 30px 0 20px;
font-size: 40px;
font-weight: bold;
}
.content {
margin-top: 20px;
}
}
// 路由跳转动画
.move-enter-active,
.move-leave-active {
transition: all 0.2s ease;
}
.move-enter-from,
.move-leave-to {
opacity: 0;
transform: translateX(10px);
}
</style>

View File

@@ -0,0 +1,245 @@
<template>
<div class="playlists">
<div class="menu">
<!-- 分类选择 -->
<n-button
class="cat"
icon-placement="right"
round
@click="catModelShow = true"
>
<template #icon>
<n-icon class="up" :component="ChevronRightRound" />
</template>
{{ catName }}
</n-button>
<n-modal
class="cat-model"
v-model:show="catModelShow"
style="width: 60vw; min-width: min(24rem, 100vw)"
preset="card"
title="歌单分类"
:bordered="false"
>
<template #header>
歌单分类
<n-tag
round
class="tag"
:type="catName == '全部歌单' ? 'primary' : 'default'"
:style="{
marginLeft: '12px',
fontSize: '12px',
transform: 'translateY(-2px)',
}"
:bordered="false"
@click="changeTagName('全部歌单')"
>
全部歌单
</n-tag>
</template>
<n-scrollbar style="max-height: 80vh">
<div class="all-cat" v-if="music.catList.sub && music.catList.sub[0]">
<n-list>
<n-list-item
v-for="(cat, key) in music.catList.categories"
:key="cat"
>
<template #prefix>
<n-text class="type"> {{ cat }} </n-text>
</template>
<n-space>
<n-tag
round
class="tag"
size="large"
v-for="item in music.catList.sub.filter(
(v) => v.category == key
)"
:key="item"
:bordered="false"
:type="item.name == catName ? 'primary' : 'default'"
@click="changeTagName(item.name)"
>
{{ item.name }}
</n-tag>
</n-space>
</n-list-item>
</n-list>
</div>
<div class="error" v-else>分类数据获取失败</div>
</n-scrollbar>
</n-modal>
</div>
<CoverLists :listData="playlistsData" />
<Pagination
v-if="playlistsData[0]"
:totalCount="totalCount"
@pageSizeChange="pageSizeChange"
@pageNumberChange="pageNumberChange"
/>
</div>
</template>
<script setup>
import { ChevronRightRound } from "@vicons/material";
import { useRouter } from "vue-router";
import { musicStore } from "@/store";
import { getPlayListCatlist, getTopPlaylist } from "@/api";
import { formatNumber } from "@/utils/timeTools.js";
import CoverLists from "@/components/DataList/CoverLists.vue";
import Pagination from "@/components/Pagination/index.vue";
const router = useRouter();
const music = musicStore();
// 分类数据
let catModelShow = ref(false);
let catName = ref(
router.currentRoute.value.query.cat
? router.currentRoute.value.query.cat
: "全部歌单"
);
// 歌单数据
let playlistsData = ref([]);
let totalCount = ref(0);
let pagelimit = ref(30);
let pageNumber = ref(
router.currentRoute.value.query.page
? Number(router.currentRoute.value.query.page)
: 1
);
// 获取歌单分类
const getPlayListCatlistData = () => {
getPlayListCatlist().then((res) => {
if (res.code == 200) {
music.catList = res;
} else {
$message.error("歌单分类获取失败");
}
});
};
// 获取歌单数据
const getPlaylistData = (
cat = "全部歌单",
limit = 30,
offset = 0,
order = "hot"
) => {
getTopPlaylist(cat, limit, offset, order).then((res) => {
console.log(res);
// 数据总数
totalCount.value = res.total;
// 列表数据
playlistsData.value = [];
if (res.playlists) {
res.playlists.forEach((v) => {
playlistsData.value.push({
id: v.id,
cover: v.coverImgUrl,
name: v.name,
artist: v.creator,
playCount: formatNumber(v.playCount),
});
});
} else {
$message.error("歌单分类为空");
}
// 请求后回顶
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
});
};
// 更换标签名
const changeTagName = (name) => {
router.push({
path: "/discover/playlists",
query: {
page: 1,
cat: name,
},
});
catModelShow.value = false;
};
// 每页个数数据变化
const pageSizeChange = (val) => {
console.log(val);
pagelimit.value = val;
getPlaylistData(catName.value, val, (pageNumber.value - 1) * pagelimit.value);
};
// 当前页数数据变化
const pageNumberChange = (val) => {
router.push({
path: "/discover/playlists",
query: {
page: val,
cat: catName.value,
},
});
};
// 监听路由参数变化
watch(
() => router.currentRoute.value,
(val) => {
catName.value = val.query.cat;
pageNumber.value = Number(val.query.page);
if (val.name == "dsc-playlists") {
getPlaylistData(
catName.value,
pagelimit.value,
(pageNumber.value - 1) * pagelimit.value
);
}
}
);
onMounted(() => {
// 获取歌单分类
if (!music.catList.sub) getPlayListCatlistData();
// 获取歌单数据
getPlaylistData(
catName.value,
pagelimit.value,
(pageNumber.value - 1) * pagelimit.value
);
});
</script>
<style lang="scss" scoped>
.playlists {
.menu {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
}
.all-cat {
.type {
white-space: nowrap;
&::after {
content: ">";
margin-left: 6px;
opacity: 0.6;
}
}
}
.tag {
cursor: pointer;
transition: all 0.3s;
&:hover {
background-color: $mainSecondaryColor;
color: $mainColor;
}
&:active {
transform: scale(0.95);
}
}
</style>

View File

@@ -0,0 +1,5 @@
<template>
<div class="toplists">
777
</div>
</template>

View File

@@ -21,7 +21,7 @@
</template>
<script setup>
import { userStore } from "@/store/index";
import { userStore } from "@/store";
import { getLoginState, getQrKey, checkQr } from "@/api";
import { useRouter } from "vue-router";
import QrcodeVue from "qrcode.vue";

View File

@@ -163,7 +163,7 @@ const getAllPlayListData = (id, limit = 30, offset = 0) => {
$message.error("获取歌单内歌曲失败");
}
// 请求后回顶
$mainContent.scrollIntoView({ behavior: "smooth" });
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
});
};

View File

@@ -51,7 +51,7 @@ const getSearchDataList = (keywords, limit = 30, offset = 0, type = 10) => {
$message.error("搜索内容为空");
}
// 请求后回顶并结束加载条
$mainContent.scrollIntoView({ behavior: "smooth" });
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
});
};

View File

@@ -38,7 +38,7 @@ const getSearchDataList = (keywords, limit = 30, offset = 0, type = 100) => {
$message.error("搜索内容为空");
}
// 请求后回顶并结束加载条
$mainContent.scrollIntoView({ behavior: "smooth" });
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
});
};

View File

@@ -50,8 +50,8 @@ const getSearchDataList = (keywords, limit = 30, offset = 0, type = 1000) => {
} else {
$message.error("搜索内容为空");
}
// 请求后回顶并结束加载条
$mainContent.scrollIntoView({ behavior: "smooth" });
// 请求后回顶
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
});
};

View File

@@ -60,7 +60,7 @@ const getSearchDataList = (keywords, limit = 30, offset = 0, type = 1) => {
$message.error("搜索内容为空");
}
// 请求后回顶并结束加载条
$mainContent.scrollIntoView({ behavior: "smooth" });
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
});
};

View File

@@ -52,7 +52,7 @@ const getSearchDataList = (keywords, limit = 30, offset = 0, type = 1004) => {
$message.error("搜索内容为空");
}
// 请求后回顶并结束加载条
$mainContent.scrollIntoView({ behavior: "smooth" });
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
});
};

View File

@@ -51,7 +51,7 @@ const getCloudData = (limit = 30, offset = 0) => {
$message.error("搜索内容为空");
}
// 请求后回顶
$mainContent.scrollIntoView({ behavior: "smooth" });
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
});
};

View File

@@ -130,8 +130,6 @@ let commentsCount = ref(0);
// 获取视频数据
const getVideoData = (id) => {
getVideoDetail(id).then((res) => {
// 请求后回顶
$mainContent.scrollIntoView({ behavior: "smooth" });
videoData.value = res.data;
let requests = res.data.brs.map((v) => {
return getVideoUrl(id, v.br);
@@ -156,6 +154,8 @@ const getVideoData = (id) => {
console.error(err);
$message.error("视频加载失败,请重试");
});
// 请求后回顶
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
});
// 获取相似视频
getSimiVideo(id).then((res) => {