mirror of
https://github.com/imsyy/SPlayer.git
synced 2025-11-25 03:14:57 +08:00
feat: 收藏页面新增歌单
This commit is contained in:
33
README.md
33
README.md
@@ -1,10 +1,11 @@
|
||||
> [!IMPORTANT]
|
||||
> ## 🎉 当前项目正在重构中 🎉
|
||||
> ## 严肃警告
|
||||
>
|
||||
> - 目前版本进入维护模式,仅在遇到重大问题时会进行修复
|
||||
> - 支持客户端与网页端
|
||||
> - 支持现有版本所有功能
|
||||
> - 新增支持播放与管理本地歌曲
|
||||
> - 请务必遵守 [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) 许可协议
|
||||
> - 在您的修改、演绎、分发或派生项目中,必须同样采用 **CC BY-NC-SA 4.0** 许可协议,**并在适当的位置包含本项目的许可和版权信息**
|
||||
> - **禁止用于售卖或其他商业用途**,如若发现,作者保留追究法律责任的权利
|
||||
> - 若发现未遵守 **CC BY-NC-SA 4.0** 许可协议的行为,**本项目将永久停更**
|
||||
> - 感谢您的尊重与理解
|
||||
|
||||
<div align="center">
|
||||
<img alt="logo" height="80" src="./public/images/logo/favicon.png" />
|
||||
@@ -191,16 +192,6 @@ pnpm build:mac
|
||||
- [BlurLyric](https://github.com/Project-And-Factory/BlurLyric)
|
||||
- [Vue-mmPlayer](https://github.com/maomao1996/Vue-mmPlayer)
|
||||
|
||||
## 📜 开源许可
|
||||
|
||||
- **本项目仅供个人学习研究使用,禁止用于商业及非法用途**
|
||||
- 本项目基于 [GNU General Public License version 3](https://opensource.org/license/gpl-3-0/) 许可进行开源
|
||||
1. **修改和分发:** 任何对本项目的修改和分发都必须基于 GPL Version 3 进行,源代码必须一并提供
|
||||
2. **派生作品:** 任何派生作品必须同样采用 GPL Version 3,并在适当的地方注明原始项目的许可证
|
||||
3. **免责声明:** 根据 GPL Version 3,本项目不提供任何明示或暗示的担保。请详细阅读 GPL Version 3以了解完整的免责声明内容
|
||||
4. **社区参与:** 欢迎社区的参与和贡献,我们鼓励开发者一同改进和维护本项目
|
||||
5. **许可证链接:** 请阅读 [GNU General Public License version 3](https://opensource.org/license/gpl-3-0/) 了解更多详情
|
||||
|
||||
## 📢 免责声明
|
||||
|
||||
本项目部分功能使用了网易云音乐的第三方 API 服务,**仅供个人学习研究使用,禁止用于商业及非法用途**
|
||||
@@ -210,3 +201,15 @@ pnpm build:mac
|
||||
请使用者在使用本项目时遵守相关法律法规,**不要将本项目用于任何商业及非法用途。如有违反,一切后果由使用者自负。** 同时,使用者应该自行承担因使用本项目而带来的风险和责任。本项目开发者不对本项目所提供的服务和内容做出任何保证
|
||||
|
||||
感谢您的理解
|
||||
|
||||
## 📜 开源许可
|
||||
|
||||
- **本项目仅供个人学习研究使用,禁止用于商业及非法用途**
|
||||
- 本项目基于 [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-nc-sa/4.0/) 许可进行开源
|
||||
1. **修改和分发:** 任何对本项目的修改和分发都必须基于 CC BY-NC-SA 4.0 进行,源代码必须一并提供
|
||||
2. **派生作品:** 任何派生作品必须同样采用 CC BY-NC-SA 4.0,并在适当的地方注明原始项目的许可证
|
||||
3. **免责声明:** 根据 CC BY-NC-SA 4.0,本项目不提供任何明示或暗示的担保。请详细阅读 [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-nc-sa/4.0/) 以了解完整的免责声明内容
|
||||
4. **社区参与:** 欢迎社区的参与和贡献,我们鼓励开发者一同改进和维护本项目
|
||||
5. **许可证链接:** 请阅读 [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-nc-sa/4.0/) 了解更多详情
|
||||
|
||||
<p xmlns:cc="http://creativecommons.org/ns#" xmlns:dct="http://purl.org/dc/terms/"><a property="dct:title" rel="cc:attributionURL" href="https://github.com/imsyy/SPlayer">SPlayer</a> by <a rel="cc:attributionURL dct:creator" property="cc:attributionName" href="https://github.com/imsyy">imsyy</a> is licensed under <a href="http://creativecommons.org/licenses/by-nc-sa/4.0/?ref=chooser-v1" target="_blank" rel="license noopener noreferrer" style="display:inline-block;">CC BY-NC-SA 4.0<img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1"><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1"><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg?ref=chooser-v1"><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/sa.svg?ref=chooser-v1"></a></p>
|
||||
|
||||
@@ -76,10 +76,14 @@ class MainProcess {
|
||||
log.info("主进程初始化");
|
||||
|
||||
// 启动网易云 API
|
||||
this.ncmServer = await startNcmServer({
|
||||
port: import.meta.env.MAIN_VITE_SERVER_PORT,
|
||||
host: import.meta.env.MAIN_VITE_SERVER_HOST,
|
||||
});
|
||||
try {
|
||||
this.ncmServer = await startNcmServer({
|
||||
port: import.meta.env.MAIN_VITE_SERVER_PORT,
|
||||
host: import.meta.env.MAIN_VITE_SERVER_HOST,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("启动网易云 API 失败:", error);
|
||||
}
|
||||
|
||||
// 非开发环境启动代理
|
||||
if (!is.dev) {
|
||||
|
||||
@@ -34,6 +34,7 @@ export const getArtistDetail = (id) => {
|
||||
return axios({
|
||||
method: "GET",
|
||||
url: "/artist/detail",
|
||||
noCookie: true,
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
|
||||
@@ -69,7 +69,12 @@
|
||||
</n-text>
|
||||
<!-- 歌手 -->
|
||||
<div v-if="item.artists" class="artists">
|
||||
<n-text v-for="ar in item.artists" :key="ar.id" class="ar">
|
||||
<n-text
|
||||
v-for="ar in item.artists"
|
||||
:key="ar.id"
|
||||
class="ar"
|
||||
@click.stop="router.push(`/artist?id=${ar.id}`)"
|
||||
>
|
||||
{{ ar.name || ar.userName }}
|
||||
</n-text>
|
||||
</div>
|
||||
|
||||
@@ -188,16 +188,36 @@ const openDropdown = (e, data, song, index, sourceId) => {
|
||||
icon: renderIcon("video"),
|
||||
},
|
||||
{
|
||||
key: "copy",
|
||||
label: "复制歌曲 ID",
|
||||
props: {
|
||||
onClick: () => {
|
||||
const songId = song?.id?.toString();
|
||||
copyData(songId);
|
||||
label: "更多操作",
|
||||
key: "others",
|
||||
show: !isLocalSong,
|
||||
icon: renderIcon("more"),
|
||||
children: [
|
||||
{
|
||||
key: "copy",
|
||||
label: "复制歌曲 ID",
|
||||
props: {
|
||||
onClick: () => {
|
||||
const songId = song?.id?.toString();
|
||||
copyData(songId);
|
||||
},
|
||||
},
|
||||
icon: renderIcon("content-copy"),
|
||||
},
|
||||
},
|
||||
icon: renderIcon("content-copy"),
|
||||
{
|
||||
key: "share",
|
||||
label: "分享歌曲链接",
|
||||
props: {
|
||||
onClick: () => {
|
||||
const shareUrl = `https://music.163.com/song?id=${song?.id?.toString()}`;
|
||||
copyData(shareUrl, "复制歌曲链接");
|
||||
},
|
||||
},
|
||||
icon: renderIcon("share"),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
key: "line-cloud",
|
||||
type: "divider",
|
||||
|
||||
@@ -244,6 +244,11 @@ const routes = [
|
||||
name: "like-videos",
|
||||
component: () => import("@/views/Like/videos.vue"),
|
||||
},
|
||||
{
|
||||
path: "playlists",
|
||||
name: "like-playlists",
|
||||
component: () => import("@/views/Like/playlists.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
// 本地歌曲
|
||||
|
||||
@@ -47,6 +47,7 @@ const formatData = (data, type = "playlist", noTracks = false) => {
|
||||
updateTime: v.updateTime || v.trackNumberUpdateTime,
|
||||
description: v.description,
|
||||
tags: v.tags || v.algTags,
|
||||
userId: v.userId,
|
||||
};
|
||||
// 歌曲
|
||||
case "song":
|
||||
|
||||
@@ -94,20 +94,20 @@ export const getLocalCoverData = async (path, isAlbum = false) => {
|
||||
/**
|
||||
* 内容复制
|
||||
*/
|
||||
export const copyData = async (data) => {
|
||||
export const copyData = async (data, info) => {
|
||||
try {
|
||||
const isElectron = checkPlatform.electron();
|
||||
// electron
|
||||
if (isElectron) {
|
||||
const result = await electron.ipcRenderer.invoke("copyData", data);
|
||||
result ? $message.success("复制成功") : $message.error("复制失败");
|
||||
result ? $message.success(`${info || "复制"}成功`) : $message.error(`${info || "复制"}失败`);
|
||||
}
|
||||
// 浏览器端
|
||||
else {
|
||||
if (navigator.clipboard) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(data);
|
||||
$message.success("复制成功");
|
||||
$message.success(`${info || "复制"}成功`);
|
||||
} catch (error) {
|
||||
console.error("复制出错:", error);
|
||||
$message.error("复制失败");
|
||||
@@ -120,7 +120,9 @@ export const copyData = async (data) => {
|
||||
textArea.select();
|
||||
try {
|
||||
const successful = document.execCommand("copy");
|
||||
successful ? $message.success("复制成功") : $message.error("复制失败");
|
||||
successful
|
||||
? $message.success(`${info || "复制"}成功`)
|
||||
: $message.error(`${info || "复制"}失败`);
|
||||
} catch (err) {
|
||||
console.error("复制出错:", err);
|
||||
$message.error("复制失败");
|
||||
|
||||
@@ -21,6 +21,10 @@ axios.interceptors.request.use(
|
||||
if (!request.noCookie && (isLogin() || getCookie("MUSIC_U") !== null)) {
|
||||
request.params.cookie = `MUSIC_U=${getCookie("MUSIC_U")};`;
|
||||
}
|
||||
// 去除 cookie
|
||||
if (request.noCookie) {
|
||||
request.params.noCookie = true;
|
||||
}
|
||||
// 附加 realIP
|
||||
if (!checkPlatform.electron()) request.params.realIP = "116.25.146.177";
|
||||
// 发送请求
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<!-- 标签页 -->
|
||||
<n-tabs v-model:value="tabValue" class="tabs" type="segment" @update:value="tabChange">
|
||||
<n-tab name="like-albums"> 专辑 </n-tab>
|
||||
<n-tab name="like-playlists"> 歌单 </n-tab>
|
||||
<n-tab name="like-artists"> 歌手 </n-tab>
|
||||
<n-tab name="like-videos"> 视频 </n-tab>
|
||||
</n-tabs>
|
||||
|
||||
106
src/views/Like/playlists.vue
Normal file
106
src/views/Like/playlists.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div class="like-playlists">
|
||||
<Transition name="fade" mode="out-in">
|
||||
<div v-if="userLikeData.playlists?.length" class="pl-list">
|
||||
<!-- 分类 -->
|
||||
<n-space class="type">
|
||||
<n-tag
|
||||
v-for="(item, index) in ['我创建的', '我收藏的']"
|
||||
:key="index"
|
||||
:bordered="false"
|
||||
:type="index === plTypeChoose ? 'primary' : 'default'"
|
||||
class="tag"
|
||||
round
|
||||
@click="plTypeChange(index)"
|
||||
>
|
||||
{{ item }}
|
||||
</n-tag>
|
||||
</n-space>
|
||||
<!-- 列表 -->
|
||||
<Transition name="fade" mode="out-in">
|
||||
<div v-if="plTypeChoose === 0" class="list">
|
||||
<MainCover
|
||||
:data="likePlaylistsData.filter((playlist) => playlist.userId === userData?.userId)"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="list">
|
||||
<MainCover
|
||||
:data="likePlaylistsData.filter((playlist) => playlist.userId !== userData?.userId)"
|
||||
/>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
<n-empty
|
||||
v-else
|
||||
description="当前暂无收藏歌单"
|
||||
class="tip"
|
||||
style="margin-top: 60px"
|
||||
size="large"
|
||||
/>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { storeToRefs } from "pinia";
|
||||
import { siteData } from "@/stores";
|
||||
import { useRouter } from "vue-router";
|
||||
import formatData from "@/utils/formatData";
|
||||
|
||||
const router = useRouter();
|
||||
const data = siteData();
|
||||
const { userData, userLikeData } = storeToRefs(data);
|
||||
|
||||
// 默认分类
|
||||
const plTypeChoose = ref(Number(router.currentRoute.value.query?.type) || 0);
|
||||
|
||||
// 处理视频数据
|
||||
const likePlaylistsData = computed(() => {
|
||||
return formatData(userLikeData.value.playlists);
|
||||
});
|
||||
|
||||
// 默认分类变化
|
||||
const plTypeChange = (type) => {
|
||||
router.push({
|
||||
path: "/like/playlists",
|
||||
query: {
|
||||
type,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 监听路由参数变化
|
||||
watch(
|
||||
() => router.currentRoute.value,
|
||||
(val) => {
|
||||
if (val.name === "like-playlists") {
|
||||
plTypeChoose.value = Number(val.query?.type) || 0;
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.like-playlists {
|
||||
.type {
|
||||
margin-bottom: 20px;
|
||||
.tag {
|
||||
font-size: 13px;
|
||||
padding: 0 16px;
|
||||
line-height: 0;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
transform 0.3s,
|
||||
background-color 0.3s,
|
||||
color 0.3s;
|
||||
&:hover {
|
||||
background-color: var(--main-second-color);
|
||||
color: var(--main-color);
|
||||
}
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -107,6 +107,7 @@
|
||||
:disabled="albumData === 'empty'"
|
||||
type="primary"
|
||||
class="play"
|
||||
tag="div"
|
||||
circle
|
||||
strong
|
||||
secondary
|
||||
@@ -118,7 +119,7 @@
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
<n-button size="large" round strong secondary @click="likeOrDislike(albumId)">
|
||||
<n-button size="large" tag="div" round strong secondary @click="likeOrDislike(albumId)">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<SvgIcon
|
||||
@@ -129,7 +130,7 @@
|
||||
{{ isLikeOrDislike(albumId) ? "收藏专辑" : "取消收藏" }}
|
||||
</n-button>
|
||||
<n-dropdown :options="moreOptions" trigger="hover" placement="bottom-start">
|
||||
<n-button size="large" circle strong secondary>
|
||||
<n-button size="large" tag="div" circle strong secondary>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<SvgIcon icon="format-list-bulleted" />
|
||||
|
||||
@@ -114,6 +114,7 @@
|
||||
:disabled="playListData === 'empty'"
|
||||
type="primary"
|
||||
class="play"
|
||||
tag="div"
|
||||
circle
|
||||
strong
|
||||
secondary
|
||||
@@ -128,6 +129,7 @@
|
||||
<n-button
|
||||
v-if="isUserPLayList"
|
||||
size="large"
|
||||
tag="div"
|
||||
round
|
||||
strong
|
||||
secondary
|
||||
@@ -140,7 +142,15 @@
|
||||
</template>
|
||||
编辑歌单
|
||||
</n-button>
|
||||
<n-button v-else size="large" round strong secondary @click="likeOrDislike(playlistId)">
|
||||
<n-button
|
||||
v-else
|
||||
size="large"
|
||||
tag="div"
|
||||
round
|
||||
strong
|
||||
secondary
|
||||
@click="likeOrDislike(playlistId)"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<SvgIcon
|
||||
@@ -153,7 +163,7 @@
|
||||
{{ isLikeOrDislike(playlistId) ? "收藏歌单" : "取消收藏" }}
|
||||
</n-button>
|
||||
<n-dropdown :options="moreOptions" trigger="hover" placement="bottom-start">
|
||||
<n-button size="large" circle strong secondary>
|
||||
<n-button size="large" tag="div" circle strong secondary>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<SvgIcon icon="format-list-bulleted" />
|
||||
|
||||
Reference in New Issue
Block a user