mirror of
https://github.com/imsyy/SPlayer.git
synced 2025-11-25 11:29:26 +08:00
新增 发现-歌单页面
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
> 一个简约的在线音乐播放器,**项目尚未完成**,不保证可用性
|
||||
|
||||

|
||||
|
||||
## 🎉 功能
|
||||
|
||||
- 账号登录( 目前仅支持扫码,更多登录方式待添加 )
|
||||
|
||||
BIN
screenshots/SPlayer - 播放页面.png
Normal file
BIN
screenshots/SPlayer - 播放页面.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 640 KiB |
@@ -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({
|
||||
|
||||
@@ -179,6 +179,7 @@ const toLink = (id) => {
|
||||
align-items: center;
|
||||
.n-icon {
|
||||
margin-right: 2px;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,7 +437,7 @@ const jumpLink = (id, type) => {
|
||||
// transform: scale(0.99);
|
||||
// }
|
||||
&.play {
|
||||
background-color: #f55e5520;
|
||||
background-color: $mainSecondaryColor;
|
||||
border-color: $mainColor;
|
||||
a,
|
||||
span,
|
||||
|
||||
@@ -240,7 +240,7 @@ const addPlaylists = (data, row) => {
|
||||
&.play {
|
||||
td {
|
||||
color: $mainColor;
|
||||
background-color: #f55e5520;
|
||||
background-color: $mainSecondaryColor;
|
||||
.n-text {
|
||||
color: $mainColor;
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ watch(
|
||||
}
|
||||
&.play {
|
||||
color: $mainColor;
|
||||
background-color: #f55e5520;
|
||||
background-color: $mainSecondaryColor;
|
||||
border-color: $mainColor;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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([]);
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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'),
|
||||
}, ]
|
||||
@@ -70,6 +70,20 @@ const routes = [{
|
||||
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',
|
||||
|
||||
@@ -26,8 +26,10 @@ const useMusicDataStore = defineStore('musicData', {
|
||||
showPlayList: false,
|
||||
// 播放状态
|
||||
playState: false,
|
||||
// 每日推荐数据
|
||||
// 每日推荐
|
||||
dailySongsData: [],
|
||||
// 歌单分类
|
||||
catList: [],
|
||||
// 持久化数据
|
||||
persistData: {
|
||||
// 是否处于私人 FM 模式
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
// 主题色
|
||||
$mainColor: #f55e55;
|
||||
$mainSecondaryColor: #f55e551f;
|
||||
|
||||
// 文本超出隐藏
|
||||
.text-hidden {
|
||||
|
||||
@@ -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" });
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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" });
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
5
src/views/Discover/artists.vue
Normal file
5
src/views/Discover/artists.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div class="artists">
|
||||
666
|
||||
</div>
|
||||
</template>
|
||||
@@ -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>
|
||||
|
||||
<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>
|
||||
245
src/views/Discover/playlists.vue
Normal file
245
src/views/Discover/playlists.vue
Normal 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>
|
||||
5
src/views/Discover/toplists.vue
Normal file
5
src/views/Discover/toplists.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div class="toplists">
|
||||
777
|
||||
</div>
|
||||
</template>
|
||||
@@ -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";
|
||||
|
||||
@@ -163,7 +163,7 @@ const getAllPlayListData = (id, limit = 30, offset = 0) => {
|
||||
$message.error("获取歌单内歌曲失败");
|
||||
}
|
||||
// 请求后回顶
|
||||
$mainContent.scrollIntoView({ behavior: "smooth" });
|
||||
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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" });
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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" });
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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" });
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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" });
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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" });
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ const getCloudData = (limit = 30, offset = 0) => {
|
||||
$message.error("搜索内容为空");
|
||||
}
|
||||
// 请求后回顶
|
||||
$mainContent.scrollIntoView({ behavior: "smooth" });
|
||||
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user