mirror of
https://github.com/imsyy/SPlayer.git
synced 2025-11-26 03:44:57 +08:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bd76ce157 | ||
|
|
ee7fe27801 | ||
|
|
73798ba196 | ||
|
|
c30263b019 | ||
|
|
f96b07a43c | ||
|
|
b23c2677e2 | ||
|
|
3c64da89f6 | ||
|
|
0207b92f2a | ||
|
|
0ee896d352 | ||
|
|
0e88d1405f | ||
|
|
0e0a3911c1 | ||
|
|
b5cde21981 | ||
|
|
2370d237a8 | ||
|
|
ffbe6229f9 | ||
|
|
bf1312889d | ||
|
|
2bf3d7db5a | ||
|
|
06ccb969e4 | ||
|
|
6867897e02 | ||
|
|
c78f94ae86 | ||
|
|
40ed0dada1 | ||
|
|
f4d2c5f337 | ||
|
|
aace9e97b0 | ||
|
|
b3641801df | ||
|
|
1dd877832c | ||
|
|
660cd33387 | ||
|
|
8caebf65f9 | ||
|
|
3b432dbd8b | ||
|
|
ee934c89f4 | ||
|
|
921b0eed0a | ||
|
|
7142991f7d | ||
|
|
a60e557ba2 | ||
|
|
bc7031ba0c | ||
|
|
82aabc555a | ||
|
|
8149c2dd71 | ||
|
|
02084c6be0 | ||
|
|
ade3ddbe82 | ||
|
|
72e5b11558 | ||
|
|
c8cb4c2c9e | ||
|
|
396a54f646 | ||
|
|
cf8fd6b7fc | ||
|
|
4709ab3910 | ||
|
|
46e6ac3408 | ||
|
|
a9a03e1cc4 | ||
|
|
69a2855f77 | ||
|
|
bc84e11adf | ||
|
|
6a102a1bff | ||
|
|
ddd12364fe | ||
|
|
144955e7c8 | ||
|
|
43fe04b4fc | ||
|
|
535d0f7493 | ||
|
|
cd05376e18 | ||
|
|
418da81738 |
13
.env
13
.env
@@ -2,10 +2,21 @@
|
|||||||
## 需部署 API,详见 https://github.com/Binaryify/NeteaseCloudMusicApi
|
## 需部署 API,详见 https://github.com/Binaryify/NeteaseCloudMusicApi
|
||||||
VITE_MUSIC_API = "https://api-music.imsyy.top/"
|
VITE_MUSIC_API = "https://api-music.imsyy.top/"
|
||||||
|
|
||||||
# 网易云解灰 API 地址
|
# 网易云解灰 API 地址(可选功能)
|
||||||
## 需部署 API,详见 https://github.com/imsyy/UNM-Server#%E8%BF%90%E8%A1%8C
|
## 需部署 API,详见 https://github.com/imsyy/UNM-Server#%E8%BF%90%E8%A1%8C
|
||||||
VITE_UNM_API = "https://api-unm.imsyy.top/"
|
VITE_UNM_API = "https://api-unm.imsyy.top/"
|
||||||
|
|
||||||
|
# 站点标题
|
||||||
|
VITE_SITE_TITLE = "SPlayer"
|
||||||
|
VITE_SITE_ANTHOR = "無名"
|
||||||
|
VITE_SITE_KEYWORDS = "SPlayer,云音乐,播放器,在线音乐,在线播放器,音乐播放器,在线音乐播放器"
|
||||||
|
VITE_SITE_DES = "一个简约的在线音乐播放器,具有音乐搜索、播放、每日推荐、私人FM、歌词显示、歌曲评论、网易云登录与云盘等功能"
|
||||||
|
VITE_SITE_URL = "imsyy.top"
|
||||||
|
VITE_SITE_LOGO = "/images/logo/favicon.svg"
|
||||||
|
|
||||||
|
# 百度统计(若不需要,请设为空即可)
|
||||||
|
VITE_SITE_BAIDUTONGJI = "c6579e9a33cbc5260fc90231678556ec"
|
||||||
|
|
||||||
# ICP 备案号
|
# ICP 备案号
|
||||||
## 若不需要,请设为空即可
|
## 若不需要,请设为空即可
|
||||||
VITE_ICP = "豫ICP备2022018134号-1"
|
VITE_ICP = "豫ICP备2022018134号-1"
|
||||||
|
|||||||
9
.hintrc
9
.hintrc
@@ -13,6 +13,15 @@
|
|||||||
"backdrop-filter"
|
"backdrop-filter"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"compat-api/html": [
|
||||||
|
"default",
|
||||||
|
{
|
||||||
|
"ignore": [
|
||||||
|
"meta[name=theme-color]"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"apple-touch-icons": "off"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
26
README.md
26
README.md
@@ -6,8 +6,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
> 本项目采用 Vue 3 全家桶及 SCSS 开发
|
## 说明
|
||||||
> 目前主要以 PC 端为主,移动端做了基础适配,仅保证功能
|
|
||||||
|
- 本项目采用 [Vue 3](https://cn.vuejs.org/) 全家桶和 [Naïve UI](https://www.naiveui.com/) 组件库及 `SCSS` 开发
|
||||||
|
- 目前主要以 `Web` 端为主,可能暂时不会考虑使用 `Electron` 构建客户端
|
||||||
|
- 仅对移动端做了基础适配,**不保证功能全部可用**
|
||||||
|
- 欢迎各位大佬指点和 `Star` 哦 😍
|
||||||
|
|
||||||
## 👀 Demo
|
## 👀 Demo
|
||||||
|
|
||||||
@@ -16,7 +20,7 @@
|
|||||||
## 🎉 功能
|
## 🎉 功能
|
||||||
|
|
||||||
- 支持扫码登录
|
- 支持扫码登录
|
||||||
- 支持手机号登录(目前暂时无法使用)
|
- 支持手机号登录(上游接口暂时无法使用)
|
||||||
- 自动进行每日签到及云贝签到
|
- 自动进行每日签到及云贝签到
|
||||||
- 支持 [UnblockNeteaseMusic](https://github.com/UnblockNeteaseMusic/server),自动替换变灰歌曲
|
- 支持 [UnblockNeteaseMusic](https://github.com/UnblockNeteaseMusic/server),自动替换变灰歌曲
|
||||||
- 由于酷我音源不支持 `https`,故网页端替换可能不全面
|
- 由于酷我音源不支持 `https`,故网页端替换可能不全面
|
||||||
@@ -32,16 +36,20 @@
|
|||||||
- 支持逐字歌词
|
- 支持逐字歌词
|
||||||
- 歌词滚动以及歌词翻译
|
- 歌词滚动以及歌词翻译
|
||||||
- MV 与视频播放
|
- MV 与视频播放
|
||||||
- 音乐频谱显示( 实验性功能,需在设置中开启 )
|
- 音乐频谱显示( 暂时去除,还待完善 )
|
||||||
- 音乐渐入渐出
|
- 音乐渐入渐出
|
||||||
- 支持 PWA
|
- 支持 PWA
|
||||||
- 支持评论区及评论点赞
|
- 支持评论区及评论点赞
|
||||||
- 明暗模式自动 / 手动切换
|
- 明暗模式自动 / 手动切换
|
||||||
- 移动端基础适配
|
- 移动端基础适配
|
||||||
|
- `i18n` 支持
|
||||||
|
|
||||||
#### 待办
|
#### 待办
|
||||||
|
|
||||||
|
- [ ] 电台节目支持
|
||||||
|
- [ ] 歌词页面进一步完善
|
||||||
- [ ] 发表评论
|
- [ ] 发表评论
|
||||||
|
- [ ] 重构(写成屎山了) 🤣
|
||||||
|
|
||||||
## 😍 Screenshots
|
## 😍 Screenshots
|
||||||
|
|
||||||
@@ -49,30 +57,35 @@
|
|||||||
<summary>主页面</summary>
|
<summary>主页面</summary>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>播放页面</summary>
|
<summary>播放页面</summary>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>发现页面</summary>
|
<summary>发现页面</summary>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>歌单页面</summary>
|
<summary>歌单页面</summary>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>评论页面</summary>
|
<summary>评论页面</summary>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## ⚙️ 部署
|
## ⚙️ 部署
|
||||||
@@ -86,7 +99,7 @@
|
|||||||
- 请在根目录下的 `.env` 文件中的 `VITE_MUSIC_API` 中填入 API 地址(必需)
|
- 请在根目录下的 `.env` 文件中的 `VITE_MUSIC_API` 中填入 API 地址(必需)
|
||||||
|
|
||||||
```js
|
```js
|
||||||
VITE_MUSIC_API = "your api url"
|
VITE_MUSIC_API = "your api url";
|
||||||
```
|
```
|
||||||
|
|
||||||
### 网易云解灰 API(可选)
|
### 网易云解灰 API(可选)
|
||||||
@@ -131,9 +144,12 @@ npm build
|
|||||||
|
|
||||||
- [NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi)
|
- [NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi)
|
||||||
- [YesPlayMusic](https://github.com/qier222/YesPlayMusic)
|
- [YesPlayMusic](https://github.com/qier222/YesPlayMusic)
|
||||||
|
- [UnblockNeteaseMusic](https://github.com/UnblockNeteaseMusic/server)
|
||||||
|
- [BlurLyric](https://github.com/Project-And-Factory/BlurLyric)
|
||||||
- [Vue-mmPlayer](https://github.com/maomao1996/Vue-mmPlayer)
|
- [Vue-mmPlayer](https://github.com/maomao1996/Vue-mmPlayer)
|
||||||
|
|
||||||
## 📜 开源许可
|
## 📜 开源许可
|
||||||
|
|
||||||
- **本项目仅供个人学习研究使用,禁止用于商业及非法用途**
|
- **本项目仅供个人学习研究使用,禁止用于商业及非法用途**
|
||||||
- 本项目基于 [MIT license](https://opensource.org/license/mit/) 许可进行开源
|
- 本项目基于 [MIT license](https://opensource.org/license/mit/) 许可进行开源
|
||||||
|
|
||||||
|
|||||||
77
index.html
77
index.html
@@ -1,30 +1,79 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
<head>
|
<meta charset="UTF-8" />
|
||||||
<meta charset="UTF-8">
|
<link rel="icon" href="<%- logo %>" />
|
||||||
<link rel="icon" href="/images/logo/favicon.svg">
|
<link rel="apple-touch-icon" href="<%- logo %>" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<link rel="bookmark" href="<%- logo %>" />
|
||||||
|
<link
|
||||||
|
rel="apple-touch-icon-precomposed"
|
||||||
|
sizes="200x200"
|
||||||
|
href="<%- logo %>"
|
||||||
|
/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<!-- <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests" /> -->
|
<!-- <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests" /> -->
|
||||||
<title>SPlayer</title>
|
<title><%- title %></title>
|
||||||
<meta name="keywords" content="SPlayer,云音乐,播放器,在线音乐,在线播放器,音乐播放器,在线音乐播放器" />
|
<meta name="apple-mobile-web-app-title" content="<%- title %>" />
|
||||||
<meta name="description" content="一个简约的在线音乐播放器,具有音乐搜索、播放、每日推荐、私人FM、歌词显示、歌曲评论、网易云登录与云盘等功能" />
|
<meta name="author" content="<%- author %>" />
|
||||||
|
<meta name="keywords" content="<%- keywords %>" />
|
||||||
|
<meta name="description" content="<%- description %>" />
|
||||||
|
<meta name="theme-color" content="#ffffff" />
|
||||||
<!-- HarmonyOS Sans -->
|
<!-- HarmonyOS Sans -->
|
||||||
<link rel="stylesheet" href="https://s1.hdslb.com/bfs/static/jinkela/long/font/regular.css" />
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://s1.hdslb.com/bfs/static/jinkela/long/font/regular.css"
|
||||||
|
/>
|
||||||
|
<!-- IE Out -->
|
||||||
|
<script>
|
||||||
|
if (
|
||||||
|
/*@cc_on!@*/ false ||
|
||||||
|
(!!window.MSInputMethodContext && !!document.documentMode)
|
||||||
|
)
|
||||||
|
window.location.href =
|
||||||
|
"https://support.dmeng.net/upgrade-your-browser.html?referrer=" +
|
||||||
|
encodeURIComponent(window.location.href);
|
||||||
|
</script>
|
||||||
|
<% if (tongji) { %>
|
||||||
|
<!-- 百度统计 -->
|
||||||
<script>
|
<script>
|
||||||
var _hmt = _hmt || [];
|
var _hmt = _hmt || [];
|
||||||
(function () {
|
(function () {
|
||||||
var hm = document.createElement("script");
|
var hm = document.createElement("script");
|
||||||
hm.src = "https://hm.baidu.com/hm.js?c6579e9a33cbc5260fc90231678556ec";
|
hm.src = "https://hm.baidu.com/hm.js?<%- tongji %>";
|
||||||
var s = document.getElementsByTagName("script")[0];
|
var s = document.getElementsByTagName("script")[0];
|
||||||
s.parentNode.insertBefore(hm, s);
|
s.parentNode.insertBefore(hm, s);
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
</head>
|
<% } %>
|
||||||
|
<style>
|
||||||
|
noscript {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: calc(100vh - 16px);
|
||||||
|
font-family: "HarmonyOS_Regular", sans-serif;
|
||||||
|
}
|
||||||
|
noscript .title {
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
noscript .tip {
|
||||||
|
opacity: 0.6;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
<noscript>
|
||||||
|
<img src="<%- logo %>" alt="logo" />
|
||||||
|
<p class="title"><%- title %></p>
|
||||||
|
<p class="tip">请开启 JavaScript</p>
|
||||||
|
</noscript>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
19
package.json
19
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "splayer",
|
"name": "splayer",
|
||||||
"version": "1.1.3",
|
"version": "1.1.7",
|
||||||
"author": "imsyy",
|
"author": "imsyy",
|
||||||
"home": "https://imsyy.top",
|
"home": "https://imsyy.top",
|
||||||
"github": "https://github.com/imsyy/SPlayer",
|
"github": "https://github.com/imsyy/SPlayer",
|
||||||
@@ -14,24 +14,31 @@
|
|||||||
"@ivanv/vue-collapse-transition": "^1.0.2",
|
"@ivanv/vue-collapse-transition": "^1.0.2",
|
||||||
"artplayer": "^4.5.12",
|
"artplayer": "^4.5.12",
|
||||||
"axios": "^1.2.0",
|
"axios": "^1.2.0",
|
||||||
|
"colorthief": "^2.4.0",
|
||||||
|
"howler": "^2.2.3",
|
||||||
"pinia": "^2.0.26",
|
"pinia": "^2.0.26",
|
||||||
"pinia-plugin-persistedstate": "^3.0.1",
|
"pinia-plugin-persistedstate": "^3.0.1",
|
||||||
"plyr": "^3.7.3",
|
"plyr": "^3.7.3",
|
||||||
"qrcode.vue": "^3.3.3",
|
"qrcode.vue": "^3.3.3",
|
||||||
"sass": "^1.56.1",
|
"sass": "^1.56.1",
|
||||||
"screenfull": "^6.0.2",
|
"screenfull": "^6.0.2",
|
||||||
|
"swiper": "^9.3.2",
|
||||||
|
"throttle-debounce": "^5.0.0",
|
||||||
|
"vite-plugin-html": "^3.2.0",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.2.45",
|
||||||
"vue-router": "^4.1.6"
|
"vue-i18n": "^9.2.2",
|
||||||
|
"vue-router": "^4.1.6",
|
||||||
|
"vue-slider-component": "4.1.0-beta.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.14",
|
"@jridgewell/sourcemap-codec": "^1.4.14",
|
||||||
"@rollup/plugin-terser": "^0.4.0",
|
"@rollup/plugin-terser": "^0.4.0",
|
||||||
"@vicons/material": "^0.12.0",
|
"@vicons/material": "^0.12.0",
|
||||||
"@vitejs/plugin-vue": "^3.2.0",
|
"@vitejs/plugin-vue": "^4.2.3",
|
||||||
"naive-ui": "^2.34.2",
|
"naive-ui": "^2.34.4",
|
||||||
"unplugin-auto-import": "^0.12.0",
|
"unplugin-auto-import": "^0.12.0",
|
||||||
"unplugin-vue-components": "^0.22.11",
|
"unplugin-vue-components": "^0.22.11",
|
||||||
"vite": "^3.2.4",
|
"vite": "^4.3.8",
|
||||||
"vite-plugin-pwa": "^0.14.1"
|
"vite-plugin-pwa": "^0.15.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1182
pnpm-lock.yaml
generated
1182
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
BIN
public/images/pic/radar.jpg
Normal file
BIN
public/images/pic/radar.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 158 KiB |
89
src/App.vue
89
src/App.vue
@@ -13,8 +13,11 @@
|
|||||||
<main
|
<main
|
||||||
ref="mainContent"
|
ref="mainContent"
|
||||||
class="main"
|
class="main"
|
||||||
id="main"
|
id="mainContent"
|
||||||
:class="[music.showPlayList ? 'playlist' : null]"
|
:class="{
|
||||||
|
playlist: music.showPlayList,
|
||||||
|
search: site.searchInputActive,
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<n-back-top
|
<n-back-top
|
||||||
:bottom="music.getPlaylists[0] && music.showPlayBar ? 100 : 40"
|
:bottom="music.getPlaylists[0] && music.showPlayBar ? 100 : 40"
|
||||||
@@ -35,18 +38,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { musicStore, userStore, settingStore } from "@/store";
|
import { musicStore, userStore, settingStore, siteStore } from "@/store";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { getLoginState, refreshLogin } from "@/api/login";
|
import { getLoginState, refreshLogin } from "@/api/login";
|
||||||
import { userDailySignin, userYunbeiSign } from "@/api/user";
|
import { userDailySignin, userYunbeiSign } from "@/api/user";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import Provider from "@/components/Provider/index.vue";
|
import Provider from "@/components/Provider/index.vue";
|
||||||
import Nav from "@/components/Nav/index.vue";
|
import Nav from "@/components/Nav/index.vue";
|
||||||
import Player from "@/components/Player/index.vue";
|
import Player from "@/components/Player/index.vue";
|
||||||
import packageJson from "@/../package.json";
|
import packageJson from "@/../package.json";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const music = musicStore();
|
const music = musicStore();
|
||||||
const user = userStore();
|
const user = userStore();
|
||||||
const setting = settingStore();
|
const setting = settingStore();
|
||||||
|
const site = siteStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const mainContent = ref(null);
|
const mainContent = ref(null);
|
||||||
|
|
||||||
@@ -76,11 +82,11 @@ const spacePlayOrPause = (e) => {
|
|||||||
// 更改页面标题
|
// 更改页面标题
|
||||||
const setSiteTitle = (val) => {
|
const setSiteTitle = (val) => {
|
||||||
const title = val
|
const title = val
|
||||||
? val === "SPlayer"
|
? val === import.meta.env.VITE_SITE_TITLE
|
||||||
? val
|
? val
|
||||||
: val + " - SPlayer"
|
: val + " - " + import.meta.env.VITE_SITE_TITLE
|
||||||
: user.siteTitle;
|
: sessionStorage.getItem("siteTitle") ?? import.meta.env.VITE_SITE_TITLE;
|
||||||
user.setSiteTitle(title);
|
site.siteTitle = title;
|
||||||
sessionStorage.setItem("siteTitle", title);
|
sessionStorage.setItem("siteTitle", title);
|
||||||
if (!music.getPlayState) {
|
if (!music.getPlayState) {
|
||||||
window.document.title = title;
|
window.document.title = title;
|
||||||
@@ -118,38 +124,50 @@ const signIn = () => {
|
|||||||
Promise.all(signInPromises)
|
Promise.all(signInPromises)
|
||||||
.then((results) => {
|
.then((results) => {
|
||||||
localStorage.setItem("lastSignInDate", today);
|
localStorage.setItem("lastSignInDate", today);
|
||||||
console.log("签到成功!");
|
console.log(t("general.message.signInSuccess"), results[0], results[1]);
|
||||||
console.log("userDailySignin:", results[0]);
|
|
||||||
console.log("userYunbeiSign:", results[1]);
|
|
||||||
$notification["success"]({
|
$notification["success"]({
|
||||||
content: "签到成功",
|
content: t("general.message.signInSuccess"),
|
||||||
meta: "每日签到及云贝签到成功",
|
meta: t("general.message.signInSuccessDesc"),
|
||||||
duration: 3000,
|
duration: 3000,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("签到失败:", error);
|
console.error(t("general.message.signInFailed"), error);
|
||||||
$message.error("每日签到失败");
|
$message.error(t("general.message.signInFailed"));
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
console.log("今天已经签到过了!");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 系统重置
|
// 系统重置
|
||||||
const cleanAll = () => {
|
const cleanAll = () => {
|
||||||
$message ? $message.success("重置成功") : alert("重置成功");
|
$message ? $message.success(t("other.cleanAll")) : alert(t("other.cleanAll"));
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
window.location.href = "/";
|
document.location.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 滚动至顶部
|
||||||
|
const scrollToTop = () => {
|
||||||
|
nextTick().then(() => {
|
||||||
|
if (mainContent.value) {
|
||||||
|
mainContent.value?.scrollIntoView({ behavior: "smooth" });
|
||||||
|
} else {
|
||||||
|
const mainContent = document.getElementById("mainContent");
|
||||||
|
mainContent?.scrollIntoView({ behavior: "smooth" });
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 挂载至全局
|
// 挂载方法至全局
|
||||||
window.$mainContent = mainContent.value;
|
window.$scrollToTop = scrollToTop;
|
||||||
window.$cleanAll = cleanAll;
|
window.$cleanAll = cleanAll;
|
||||||
window.$signIn = signIn;
|
window.$signIn = signIn;
|
||||||
window.$setSiteTitle = setSiteTitle;
|
window.$setSiteTitle = setSiteTitle;
|
||||||
|
|
||||||
|
// 更改页面语言
|
||||||
|
const html = document.documentElement;
|
||||||
|
if (html) html.setAttribute("lang", setting.language);
|
||||||
|
|
||||||
// 公告
|
// 公告
|
||||||
if (annShow) {
|
if (annShow) {
|
||||||
$notification["info"]({
|
$notification["info"]({
|
||||||
@@ -160,7 +178,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 版权声明
|
// 版权声明
|
||||||
const logoText = "SPlayer";
|
const logoText = import.meta.env.VITE_SITE_TITLE;
|
||||||
const copyrightNotice = `\n\n版本: ${packageJson.version}\n作者: ${packageJson.author}\n作者主页: ${packageJson.home}\nGitHub: ${packageJson.github}`;
|
const copyrightNotice = `\n\n版本: ${packageJson.version}\n作者: ${packageJson.author}\n作者主页: ${packageJson.home}\nGitHub: ${packageJson.github}`;
|
||||||
console.info(
|
console.info(
|
||||||
`%c${logoText} %c ${copyrightNotice}`,
|
`%c${logoText} %c ${copyrightNotice}`,
|
||||||
@@ -188,14 +206,15 @@ onMounted(() => {
|
|||||||
} else {
|
} else {
|
||||||
user.userLogOut();
|
user.userLogOut();
|
||||||
if (music.getPlayListMode === "cloud") {
|
if (music.getPlayListMode === "cloud") {
|
||||||
$message.info("登录已失效,请重新登录");
|
$message.info(t("other.loginExpired"));
|
||||||
music.setPlaylists([]);
|
music.setPlaylists([]);
|
||||||
|
music.setPlayListMode("list");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
$message.error("请求发生错误");
|
console.error(t("general.message.acquisitionFailed"), err);
|
||||||
console.error("请求发生错误" + err);
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
router.push("/500");
|
router.push("/500");
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
@@ -230,12 +249,31 @@ onMounted(() => {
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
div:nth-of-type(2) {
|
div:nth-of-type(2) {
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
transition: all 0.3s;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&.playlist {
|
&.playlist {
|
||||||
div:nth-of-type(2) {
|
div:nth-of-type(2) {
|
||||||
transform: scale(0.98);
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.search {
|
||||||
|
div:nth-of-type(2) {
|
||||||
|
&::after {
|
||||||
|
pointer-events: all;
|
||||||
|
background-color: #00000040;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,6 +286,7 @@ onMounted(() => {
|
|||||||
.scale-enter-from,
|
.scale-enter-from,
|
||||||
.scale-leave-to {
|
.scale-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: scale(0.98);
|
// transform: scale(0.98);
|
||||||
|
transform: translateX(10px);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ export const getMusicDetail = (ids) => {
|
|||||||
url: "/song/detail",
|
url: "/song/detail",
|
||||||
params: {
|
params: {
|
||||||
ids,
|
ids,
|
||||||
|
timestamp: new Date().getTime(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -148,3 +149,22 @@ export const getSongDownload = (id, br = 999000) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 听歌打卡
|
||||||
|
* @param {number} id - 音乐ID
|
||||||
|
* @param {number} sourceid - 来源ID
|
||||||
|
*/
|
||||||
|
export const songScrobble = (id, sourceid = 0, time = 0) => {
|
||||||
|
return axios({
|
||||||
|
method: "GET",
|
||||||
|
url: "/scrobble",
|
||||||
|
hiddenBar: true,
|
||||||
|
params: {
|
||||||
|
id,
|
||||||
|
sourceid,
|
||||||
|
time,
|
||||||
|
timestamp: new Date().getTime(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -36,7 +36,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { getBanner } from "@/api/home";
|
import { getBanner } from "@/api/home";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// 轮播图高度
|
// 轮播图高度
|
||||||
@@ -78,7 +80,7 @@ const bannerJump = (type, id, url) => {
|
|||||||
const time = setTimeout(() => {
|
const time = setTimeout(() => {
|
||||||
window.open(url);
|
window.open(url);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
$message.loading("即将跳转至站外链接", {
|
$message.loading(t("general.message.jumpOut"), {
|
||||||
closable: true,
|
closable: true,
|
||||||
duration: 2000,
|
duration: 2000,
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
@@ -116,7 +118,6 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
console.log("销毁");
|
|
||||||
window.removeEventListener("resize", getBannerHeight);
|
window.removeEventListener("resize", getBannerHeight);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -78,12 +78,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { getCommentTime, formatNumber } from "@/utils/timeTools.js";
|
import { getCommentTime, formatNumber } from "@/utils/timeTools";
|
||||||
import { Local, Time, ThumbsUp } from "@icon-park/vue-next";
|
import { Local, Time, ThumbsUp } from "@icon-park/vue-next";
|
||||||
import { userStore } from "@/store";
|
import { userStore } from "@/store";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { likeComment } from "@/api/comment";
|
import { likeComment } from "@/api/comment";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const user = userStore();
|
const user = userStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -104,15 +106,14 @@ const toLikeComment = () => {
|
|||||||
type
|
type
|
||||||
).then((res) => {
|
).then((res) => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
$message.success(type ? "点赞成功" : "取消点赞成功");
|
|
||||||
props.commentData.liked = !props.commentData.liked;
|
props.commentData.liked = !props.commentData.liked;
|
||||||
props.commentData.likedCount += type ? 1 : -1;
|
props.commentData.likedCount += type ? 1 : -1;
|
||||||
} else {
|
} else {
|
||||||
$message.error("操作失败,请重试");
|
$message.error(t("general.message.operationFailed"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$message.error("请登录账号后使用");
|
$message.error(t("general.message.needLogin"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -196,7 +197,7 @@ const toLikeComment = () => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,7 +214,7 @@ const toLikeComment = () => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,14 +239,14 @@ const toLikeComment = () => {
|
|||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
&.liked {
|
&.liked {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
>
|
>
|
||||||
<n-text
|
<n-text
|
||||||
class="name"
|
class="name"
|
||||||
depth="3"
|
:depth="isDark ? 3 : 0"
|
||||||
v-html="item.name"
|
v-html="item.name"
|
||||||
@click.stop="jumpArtist(item.id)"
|
@click.stop="jumpArtist(item.id)"
|
||||||
/>
|
/>
|
||||||
@@ -31,9 +31,14 @@ const router = useRouter();
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
// 歌手数据
|
// 歌手数据
|
||||||
artistsData: {
|
artistsData: {
|
||||||
type: Object,
|
type: Array,
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
|
// 是否变灰
|
||||||
|
isDark: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 跳转歌手页面
|
// 跳转歌手页面
|
||||||
@@ -59,7 +64,7 @@ const jumpArtist = (id) => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.line {
|
.line {
|
||||||
|
|||||||
@@ -19,16 +19,34 @@
|
|||||||
>
|
>
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
<n-avatar
|
<n-avatar
|
||||||
|
lazy
|
||||||
round
|
round
|
||||||
class="coverImg"
|
class="coverImg"
|
||||||
:src="item.cover.replace(/^http:/, 'https:') + '?param=200y200'"
|
:src="item.cover.replace(/^http:/, 'https:') + '?param=200y200'"
|
||||||
fallback-src="/images/pic/default.png"
|
fallback-src="/images/pic/default.png"
|
||||||
|
>
|
||||||
|
<template #placeholder>
|
||||||
|
<div class="cover-loading">
|
||||||
|
<n-spin size="small" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-avatar>
|
||||||
|
<n-avatar
|
||||||
|
lazy
|
||||||
|
round
|
||||||
|
class="shadow"
|
||||||
|
:src="item.cover.replace(/^http:/, 'https:') + '?param=200y200'"
|
||||||
|
fallback-src="/images/pic/default.png"
|
||||||
/>
|
/>
|
||||||
<n-icon size="40" :component="PeopleSearchOne" />
|
<n-icon size="40" :component="PeopleSearchOne" />
|
||||||
</div>
|
</div>
|
||||||
<n-text class="name text-hidden">{{ item.name }}</n-text>
|
<n-text class="name text-hidden">{{ item.name }}</n-text>
|
||||||
<n-text class="size" :depth="3" v-if="item.size">
|
<n-text class="size" :depth="3" v-if="item.size">
|
||||||
{{ item.size }}首
|
{{
|
||||||
|
$t("general.name.songSize", {
|
||||||
|
size: item.size,
|
||||||
|
})
|
||||||
|
}}
|
||||||
</n-text>
|
</n-text>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
</n-grid>
|
</n-grid>
|
||||||
@@ -65,17 +83,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { PeopleSearchOne } from "@icon-park/vue-next";
|
import { NIcon } from "naive-ui";
|
||||||
|
import { PeopleSearchOne, LinkTwo, Like, Unlike } from "@icon-park/vue-next";
|
||||||
import { likeArtist } from "@/api/artist";
|
import { likeArtist } from "@/api/artist";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { userStore } from "@/store";
|
import { userStore, settingStore } from "@/store";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const user = userStore();
|
const user = userStore();
|
||||||
|
const setting = settingStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
// 列表数据
|
// 列表数据
|
||||||
listData: {
|
listData: {
|
||||||
type: Object,
|
type: Array,
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
// 折叠栅格
|
// 折叠栅格
|
||||||
@@ -101,6 +123,19 @@ const rightMenuY = ref(0);
|
|||||||
const rightMenuShow = ref(false);
|
const rightMenuShow = ref(false);
|
||||||
const rightMenuOptions = ref(null);
|
const rightMenuOptions = ref(null);
|
||||||
|
|
||||||
|
// 图标渲染
|
||||||
|
const renderIcon = (icon) => {
|
||||||
|
return () => {
|
||||||
|
return h(
|
||||||
|
NIcon,
|
||||||
|
{ style: { transform: "translateX(2px)" } },
|
||||||
|
{
|
||||||
|
default: () => icon,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// 打开右键菜单
|
// 打开右键菜单
|
||||||
const openRightMenu = (e, data) => {
|
const openRightMenu = (e, data) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -109,8 +144,11 @@ const openRightMenu = (e, data) => {
|
|||||||
rightMenuOptions.value = [
|
rightMenuOptions.value = [
|
||||||
{
|
{
|
||||||
key: "like",
|
key: "like",
|
||||||
label: isLikeOrDislike(data.id) ? "收藏歌手" : "取消收藏歌手",
|
label: isLikeOrDislike(data.id)
|
||||||
|
? t("menu.collection", { name: t("general.name.artists") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.name.artists") }),
|
||||||
show: user.userLogin && user.getUserArtistLists.has ? true : false,
|
show: user.userLogin && user.getUserArtistLists.has ? true : false,
|
||||||
|
icon: renderIcon(h(isLikeOrDislike(data.id) ? Like : Unlike)),
|
||||||
props: {
|
props: {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
toLikeArtist(data);
|
toLikeArtist(data);
|
||||||
@@ -119,7 +157,11 @@ const openRightMenu = (e, data) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "copy",
|
key: "copy",
|
||||||
label: "复制歌手链接",
|
label: t("menu.copy", {
|
||||||
|
name: t("general.name.artists"),
|
||||||
|
other: t("general.name.link"),
|
||||||
|
}),
|
||||||
|
icon: renderIcon(h(LinkTwo)),
|
||||||
props: {
|
props: {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (navigator.clipboard) {
|
if (navigator.clipboard) {
|
||||||
@@ -127,12 +169,13 @@ const openRightMenu = (e, data) => {
|
|||||||
navigator.clipboard.writeText(
|
navigator.clipboard.writeText(
|
||||||
`https://music.163.com/#/artist?id=${data.id}`
|
`https://music.163.com/#/artist?id=${data.id}`
|
||||||
);
|
);
|
||||||
$message.success("歌手链接复制成功");
|
$message.success(t("general.message.copySuccess"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$message.error("复制失败:", err);
|
console.error(t("general.message.copyFailure"), err);
|
||||||
|
$message.error(t("general.message.copyFailure"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$message.error("您的浏览器暂不支持该操作");
|
$message.error(t("general.message.notSupported"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -152,14 +195,25 @@ const onClickoutside = () => {
|
|||||||
// 收藏/取消收藏歌手
|
// 收藏/取消收藏歌手
|
||||||
const toLikeArtist = (data) => {
|
const toLikeArtist = (data) => {
|
||||||
const type = isLikeOrDislike(data.id) ? 1 : 2;
|
const type = isLikeOrDislike(data.id) ? 1 : 2;
|
||||||
|
const isThereASpace = setting.language === "zh-CN" ? "" : " ";
|
||||||
likeArtist(type, data.id).then((res) => {
|
likeArtist(type, data.id).then((res) => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
$message.success(
|
$message.success(
|
||||||
`${data.name}${type == 1 ? "收藏成功" : "取消收藏成功"}`
|
`${data.name + isThereASpace}${
|
||||||
|
type == 1
|
||||||
|
? t("menu.collection", { name: t("general.dialog.success") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.dialog.success") })
|
||||||
|
}`
|
||||||
);
|
);
|
||||||
user.setUserArtistLists();
|
user.setUserArtistLists();
|
||||||
} else {
|
} else {
|
||||||
$message.error(`${data.name}${type == 1 ? "收藏失败" : "取消收藏失败"}`);
|
$message.error(
|
||||||
|
`${data.name + isThereASpace}${
|
||||||
|
type == 1
|
||||||
|
? t("menu.collection", { name: t("general.dialog.failed") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.dialog.failed") })
|
||||||
|
}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -205,12 +259,44 @@ onMounted(() => {
|
|||||||
box-shadow: 0 4px 16px 0 #00000020;
|
box-shadow: 0 4px 16px 0 #00000020;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
.n-avatar {
|
.coverImg {
|
||||||
filter: brightness(1);
|
filter: brightness(1);
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
z-index: 1;
|
||||||
|
.cover-loading {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
padding-bottom: 100%;
|
||||||
|
background-color: #0001;
|
||||||
|
.n-spin-body {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.shadow {
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
filter: blur(16px) opacity(0.6);
|
||||||
|
transform: scale(0.92, 0.96);
|
||||||
|
z-index: 0;
|
||||||
|
background-size: cover;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
transition: opacity 0.3s;
|
||||||
}
|
}
|
||||||
.n-icon {
|
.n-icon {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@@ -218,6 +304,7 @@ onMounted(() => {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 4px 16px 0 #00000040;
|
box-shadow: 0 4px 16px 0 #00000040;
|
||||||
@@ -225,10 +312,13 @@ onMounted(() => {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
.n-avatar {
|
.coverImg {
|
||||||
filter: brightness(0.8);
|
filter: brightness(0.8);
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
.shadow {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&:active {
|
&:active {
|
||||||
.n-avatar {
|
.n-avatar {
|
||||||
@@ -242,7 +332,7 @@ onMounted(() => {
|
|||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,24 @@
|
|||||||
@contextmenu="openRightMenu($event, item)"
|
@contextmenu="openRightMenu($event, item)"
|
||||||
>
|
>
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
<n-avatar
|
<n-image
|
||||||
|
lazy
|
||||||
class="coverImg"
|
class="coverImg"
|
||||||
:src="item.cover.replace(/^http:/, 'https:') + '?param=300y300'"
|
preview-disabled
|
||||||
|
:src="getCoverUrl(item.cover, 300)"
|
||||||
|
fallback-src="/images/pic/default.png"
|
||||||
|
>
|
||||||
|
<template #placeholder>
|
||||||
|
<div class="cover-loading">
|
||||||
|
<n-spin size="small" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-image>
|
||||||
|
<n-image
|
||||||
|
lazy
|
||||||
|
class="shadow"
|
||||||
|
preview-disabled
|
||||||
|
:src="getCoverUrl(item.cover, 300)"
|
||||||
fallback-src="/images/pic/default.png"
|
fallback-src="/images/pic/default.png"
|
||||||
/>
|
/>
|
||||||
<n-icon class="play" size="40">
|
<n-icon class="play" size="40">
|
||||||
@@ -86,21 +101,34 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { PlayOne, Headset } from "@icon-park/vue-next";
|
import { NIcon } from "naive-ui";
|
||||||
|
import {
|
||||||
|
PlayOne,
|
||||||
|
Headset,
|
||||||
|
LinkTwo,
|
||||||
|
Like,
|
||||||
|
Unlike,
|
||||||
|
Editor,
|
||||||
|
DeleteFour,
|
||||||
|
} from "@icon-park/vue-next";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import { delPlayList, likePlaylist } from "@/api/playlist";
|
import { delPlayList, likePlaylist } from "@/api/playlist";
|
||||||
import { likeAlbum } from "@/api/album";
|
import { likeAlbum } from "@/api/album";
|
||||||
import { musicStore, userStore } from "@/store";
|
import { musicStore, userStore, settingStore } from "@/store";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import AllArtists from "./AllArtists.vue";
|
import AllArtists from "./AllArtists.vue";
|
||||||
import PlaylistUpdate from "@/components/DataModel/PlaylistUpdate.vue";
|
import PlaylistUpdate from "@/components/DataModal/PlaylistUpdate.vue";
|
||||||
|
import getCoverUrl from "@/utils/getCoverUrl";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const music = musicStore();
|
const music = musicStore();
|
||||||
const user = userStore();
|
const user = userStore();
|
||||||
|
const setting = settingStore();
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
// 列表数据
|
// 列表数据
|
||||||
listData: {
|
listData: {
|
||||||
type: Object,
|
type: Array,
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
// 列表类型
|
// 列表类型
|
||||||
@@ -131,6 +159,19 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
const playlistUpdateRef = ref(null);
|
const playlistUpdateRef = ref(null);
|
||||||
|
|
||||||
|
// 图标渲染
|
||||||
|
const renderIcon = (icon) => {
|
||||||
|
return () => {
|
||||||
|
return h(
|
||||||
|
NIcon,
|
||||||
|
{ style: { transform: "translateX(2px)" } },
|
||||||
|
{
|
||||||
|
default: () => icon,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// 右键菜单数据
|
// 右键菜单数据
|
||||||
const rightMenuX = ref(0);
|
const rightMenuX = ref(0);
|
||||||
const rightMenuY = ref(0);
|
const rightMenuY = ref(0);
|
||||||
@@ -145,18 +186,19 @@ const openRightMenu = (e, data) => {
|
|||||||
rightMenuOptions.value = [
|
rightMenuOptions.value = [
|
||||||
{
|
{
|
||||||
key: "update",
|
key: "update",
|
||||||
label: "编辑歌单",
|
label: t("menu.update"),
|
||||||
show:
|
show:
|
||||||
router.currentRoute.value.name === "user-playlists" ? true : false,
|
router.currentRoute.value.name === "user-playlists" ? true : false,
|
||||||
props: {
|
props: {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
playlistUpdateRef.value.openUpdateModel(data);
|
playlistUpdateRef.value.openUpdateModal(data);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
icon: renderIcon(h(Editor)),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "del",
|
key: "del",
|
||||||
label: "删除歌单",
|
label: t("menu.del"),
|
||||||
show:
|
show:
|
||||||
router.currentRoute.value.name === "user-playlists" ? true : false,
|
router.currentRoute.value.name === "user-playlists" ? true : false,
|
||||||
props: {
|
props: {
|
||||||
@@ -164,10 +206,13 @@ const openRightMenu = (e, data) => {
|
|||||||
toDelPlayList(data);
|
toDelPlayList(data);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
icon: renderIcon(h(DeleteFour)),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "likePlaylist",
|
key: "likePlaylist",
|
||||||
label: isLikeOrDislike(data.id) ? "收藏歌单" : "取消收藏歌单",
|
label: isLikeOrDislike(data.id)
|
||||||
|
? t("menu.collection", { name: t("general.name.playlist") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.name.playlist") }),
|
||||||
show:
|
show:
|
||||||
user.userLogin &&
|
user.userLogin &&
|
||||||
user.getUserPlayLists.has &&
|
user.getUserPlayLists.has &&
|
||||||
@@ -180,10 +225,13 @@ const openRightMenu = (e, data) => {
|
|||||||
toChangeLike(data.id);
|
toChangeLike(data.id);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
icon: renderIcon(h(isLikeOrDislike(data.id) ? Like : Unlike)),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "likeAlbum",
|
key: "likeAlbum",
|
||||||
label: isLikeOrDislike(data.id) ? "收藏专辑" : "取消收藏专辑",
|
label: isLikeOrDislike(data.id)
|
||||||
|
? t("menu.collection", { name: t("general.name.album") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.name.album") }),
|
||||||
show:
|
show:
|
||||||
user.userLogin &&
|
user.userLogin &&
|
||||||
user.getUserAlbumLists.has &&
|
user.getUserAlbumLists.has &&
|
||||||
@@ -195,10 +243,17 @@ const openRightMenu = (e, data) => {
|
|||||||
toChangeLike(data.id);
|
toChangeLike(data.id);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
icon: renderIcon(h(isLikeOrDislike(data.id) ? Like : Unlike)),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "copy",
|
key: "copy",
|
||||||
label: `复制${props.listType === "playlist" ? "歌单" : "专辑"}链接`,
|
label: t("menu.copy", {
|
||||||
|
name:
|
||||||
|
props.listType === "playlist"
|
||||||
|
? t("general.name.playlist")
|
||||||
|
: t("general.name.album"),
|
||||||
|
other: t("general.name.link"),
|
||||||
|
}),
|
||||||
props: {
|
props: {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (navigator.clipboard) {
|
if (navigator.clipboard) {
|
||||||
@@ -208,19 +263,17 @@ const openRightMenu = (e, data) => {
|
|||||||
props.listType === "playlist" ? "playlist" : "album"
|
props.listType === "playlist" ? "playlist" : "album"
|
||||||
}?id=${data.id}`
|
}?id=${data.id}`
|
||||||
);
|
);
|
||||||
$message.success(
|
$message.success(t("general.message.copySuccess"));
|
||||||
`${
|
|
||||||
props.listType === "playlist" ? "歌单" : "专辑"
|
|
||||||
}链接复制成功`
|
|
||||||
);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$message.error("复制失败:", err);
|
console.error(t("general.message.copyFailure"), err);
|
||||||
|
$message.error(t("general.message.copyFailure"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$message.error("您的浏览器暂不支持该操作");
|
$message.error(t("general.message.notSupported"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
icon: renderIcon(h(LinkTwo)),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
rightMenuShow.value = true;
|
rightMenuShow.value = true;
|
||||||
@@ -256,16 +309,22 @@ const toLink = (id) => {
|
|||||||
|
|
||||||
// 删除歌单
|
// 删除歌单
|
||||||
const toDelPlayList = (data) => {
|
const toDelPlayList = (data) => {
|
||||||
|
if (data.id === user.getUserPlayLists?.own[0].id) {
|
||||||
|
$message.warning(t("menu.unableToDelete"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
$dialog.warning({
|
$dialog.warning({
|
||||||
class: "s-dialog",
|
class: "s-dialog",
|
||||||
title: "删除歌单",
|
title: t("general.dialog.delete"),
|
||||||
content: "确认删除歌单 " + data.name + "?删除后将不可恢复!",
|
content: t("menu.delQuestion", {
|
||||||
positiveText: "删除",
|
name: data.name,
|
||||||
negativeText: "取消",
|
}),
|
||||||
|
positiveText: t("general.dialog.delete"),
|
||||||
|
negativeText: t("general.dialog.cancel"),
|
||||||
onPositiveClick: () => {
|
onPositiveClick: () => {
|
||||||
delPlayList(data.id).then((res) => {
|
delPlayList(data.id).then((res) => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
$message.success("删除成功");
|
$message.success(t("general.message.deleteSuccess"));
|
||||||
user.setUserPlayLists();
|
user.setUserPlayLists();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -279,10 +338,10 @@ const isLikeOrDislike = (id) => {
|
|||||||
const playlists = user.getUserPlayLists.like;
|
const playlists = user.getUserPlayLists.like;
|
||||||
const albums = user.getUserAlbumLists.list;
|
const albums = user.getUserAlbumLists.list;
|
||||||
if (listType === "playlist" && playlists.length) {
|
if (listType === "playlist" && playlists.length) {
|
||||||
return !playlists.some((item) => item.id === id);
|
return !playlists.some((item) => item.id === Number(id));
|
||||||
}
|
}
|
||||||
if (listType === "album" && albums.length) {
|
if (listType === "album" && albums.length) {
|
||||||
return !albums.some((item) => item.id === id);
|
return !albums.some((item) => item.id === Number(id));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -292,21 +351,48 @@ const toChangeLike = async (id) => {
|
|||||||
const listType = props.listType;
|
const listType = props.listType;
|
||||||
const type = isLikeOrDislike(id) ? 1 : 2;
|
const type = isLikeOrDislike(id) ? 1 : 2;
|
||||||
const likeFn = listType === "playlist" ? likePlaylist : likeAlbum;
|
const likeFn = listType === "playlist" ? likePlaylist : likeAlbum;
|
||||||
const likeMsg = listType === "playlist" ? "歌单" : "专辑";
|
const likeMsg =
|
||||||
|
listType === "playlist"
|
||||||
|
? t("general.name.playlist")
|
||||||
|
: t("general.name.album");
|
||||||
|
const isThereASpace = setting.language === "zh-CN" ? "" : " ";
|
||||||
try {
|
try {
|
||||||
const res = await likeFn(type, id);
|
const res = await likeFn(type, id);
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
$message.success(`${likeMsg}${type == 1 ? "收藏成功" : "取消收藏成功"}`);
|
$message.success(
|
||||||
|
`${likeMsg + isThereASpace}${
|
||||||
|
type == 1
|
||||||
|
? t("menu.collection", { name: t("general.dialog.success") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.dialog.success") })
|
||||||
|
}`
|
||||||
|
);
|
||||||
listType === "playlist"
|
listType === "playlist"
|
||||||
? user.setUserPlayLists()
|
? user.setUserPlayLists()
|
||||||
: user.setUserAlbumLists();
|
: user.setUserAlbumLists();
|
||||||
} else {
|
} else {
|
||||||
$message.error(`${likeMsg}${type == 1 ? "收藏失败" : "取消收藏失败"}`);
|
$message.error(
|
||||||
|
`${likeMsg + isThereASpace}${
|
||||||
|
type == 1
|
||||||
|
? t("menu.collection", { name: t("general.dialog.failed") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.dialog.failed") })
|
||||||
|
}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$message.error(`${likeMsg}${type == 1 ? "收藏失败" : "取消收藏失败"}`);
|
$message.error(
|
||||||
|
`${likeMsg + isThereASpace}${
|
||||||
|
type == 1
|
||||||
|
? t("menu.collection", { name: t("general.dialog.failed") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.dialog.failed") })
|
||||||
|
}`
|
||||||
|
);
|
||||||
console.error(
|
console.error(
|
||||||
`${likeMsg}${type == 1 ? "收藏失败:" : "取消收藏失败:"}` + err
|
`${likeMsg + isThereASpace}${
|
||||||
|
type == 1
|
||||||
|
? t("menu.collection", { name: t("general.dialog.failed") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.dialog.failed") })
|
||||||
|
}`,
|
||||||
|
err
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -356,7 +442,7 @@ onMounted(() => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
// overflow: hidden;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
@@ -365,7 +451,44 @@ onMounted(() => {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
transition: all 0.3s;
|
overflow: hidden;
|
||||||
|
transition: filter 0.3s;
|
||||||
|
z-index: 1;
|
||||||
|
:deep(img) {
|
||||||
|
width: 100%;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
.cover-loading {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
padding-bottom: 100%;
|
||||||
|
background-color: #0001;
|
||||||
|
.n-spin-body {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.shadow {
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
filter: blur(16px) opacity(0.6);
|
||||||
|
transform: scale(0.92, 0.96);
|
||||||
|
z-index: 0;
|
||||||
|
background-size: cover;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
transition: opacity 0.3s;
|
||||||
}
|
}
|
||||||
.play {
|
.play {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@@ -378,6 +501,7 @@ onMounted(() => {
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
transform: scale(0.8);
|
transform: scale(0.8);
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
.description {
|
.description {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -390,7 +514,9 @@ onMounted(() => {
|
|||||||
backdrop-filter: blur(4px);
|
backdrop-filter: blur(4px);
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
border-top-left-radius: 8px;
|
border-top-left-radius: 8px;
|
||||||
|
border-bottom-right-radius: 8px;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
z-index: 1;
|
||||||
.num {
|
.num {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -404,11 +530,12 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 15px 30px rgb(0 0 0 / 10%);
|
|
||||||
.coverImg {
|
.coverImg {
|
||||||
filter: brightness(0.8);
|
filter: brightness(0.8);
|
||||||
|
:deep(img) {
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.play {
|
.play {
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@@ -416,6 +543,9 @@ onMounted(() => {
|
|||||||
.description {
|
.description {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
.shadow {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.98);
|
transform: scale(0.98);
|
||||||
@@ -433,7 +563,7 @@ onMounted(() => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.by {
|
.by {
|
||||||
@@ -443,7 +573,7 @@ onMounted(() => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.artists {
|
.artists {
|
||||||
@@ -460,5 +590,18 @@ onMounted(() => {
|
|||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@media (max-width: 450px) {
|
||||||
|
:deep(.n-grid) {
|
||||||
|
gap: 18px 10px;
|
||||||
|
}
|
||||||
|
.item {
|
||||||
|
.title {
|
||||||
|
margin-top: 8px;
|
||||||
|
.name {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<Transition mode="out-in">
|
<Transition mode="out-in">
|
||||||
<div class="datalists" v-if="listData[0]">
|
<div class="datalists" id="datalists" v-if="listData[0]">
|
||||||
<n-card
|
<n-card
|
||||||
hoverable
|
v-for="item in listData"
|
||||||
|
:key="item"
|
||||||
:class="
|
:class="
|
||||||
music.getPlaySongData
|
music.getPlaySongData && music.getPlaySongData.id == item.id
|
||||||
? music.getPlaySongData.id == item.id
|
|
||||||
? 'songs play'
|
? 'songs play'
|
||||||
: 'songs'
|
: 'songs'
|
||||||
: 'songs'
|
|
||||||
"
|
"
|
||||||
:content-style="{
|
:content-style="{
|
||||||
padding: '16px',
|
padding: '16px',
|
||||||
@@ -17,8 +16,7 @@
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
}"
|
}"
|
||||||
v-for="item in listData"
|
hoverable
|
||||||
:key="item"
|
|
||||||
@dblclick="
|
@dblclick="
|
||||||
setting.listClickMode === 'dblclick' ? playSong(listData, item) : null
|
setting.listClickMode === 'dblclick' ? playSong(listData, item) : null
|
||||||
"
|
"
|
||||||
@@ -27,6 +25,7 @@
|
|||||||
>
|
>
|
||||||
<n-avatar
|
<n-avatar
|
||||||
v-if="item.album?.picUrl"
|
v-if="item.album?.picUrl"
|
||||||
|
lazy
|
||||||
class="pic"
|
class="pic"
|
||||||
:src="item.album.picUrl.replace(/^http:/, 'https:') + '?param=60y60'"
|
:src="item.album.picUrl.replace(/^http:/, 'https:') + '?param=60y60'"
|
||||||
fallback-src="/images/pic/default.png"
|
fallback-src="/images/pic/default.png"
|
||||||
@@ -43,13 +42,13 @@
|
|||||||
@click.stop="jumpLink(item.id, 1)"
|
@click.stop="jumpLink(item.id, 1)"
|
||||||
/>
|
/>
|
||||||
<n-tag
|
<n-tag
|
||||||
v-if="item.fee == 1"
|
v-if="item.fee == 1 || item.fee == 4"
|
||||||
class="vip"
|
class="vip"
|
||||||
round
|
round
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
VIP
|
{{ item.fee == 1 ? "VIP" : "EP" }}
|
||||||
</n-tag>
|
</n-tag>
|
||||||
<n-tag
|
<n-tag
|
||||||
v-if="item.pc"
|
v-if="item.pc"
|
||||||
@@ -59,7 +58,7 @@
|
|||||||
size="small"
|
size="small"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
>
|
>
|
||||||
云盘
|
{{ $t("general.name.cloud") }}
|
||||||
</n-tag>
|
</n-tag>
|
||||||
<n-tag
|
<n-tag
|
||||||
v-if="item.mv"
|
v-if="item.mv"
|
||||||
@@ -110,7 +109,7 @@
|
|||||||
<n-icon
|
<n-icon
|
||||||
class="download"
|
class="download"
|
||||||
size="20"
|
size="20"
|
||||||
@click.stop="downloadSongRef.openDownloadModel(item)"
|
@click.stop="downloadSongRef.openDownloadModal(item)"
|
||||||
>
|
>
|
||||||
<DownloadFour theme="filled" />
|
<DownloadFour theme="filled" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
@@ -166,7 +165,7 @@
|
|||||||
<n-icon size="20">
|
<n-icon size="20">
|
||||||
<PlayOne theme="filled" />
|
<PlayOne theme="filled" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<n-text>立即播放</n-text>
|
<n-text>{{ $t("menu.play") }}</n-text>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="
|
||||||
@@ -184,7 +183,7 @@
|
|||||||
<n-icon size="20">
|
<n-icon size="20">
|
||||||
<AddMusic theme="filled" />
|
<AddMusic theme="filled" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<n-text>下一首播放</n-text>
|
<n-text>{{ $t("menu.nextPlay") }}</n-text>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="item"
|
class="item"
|
||||||
@@ -198,13 +197,13 @@
|
|||||||
<n-icon size="20">
|
<n-icon size="20">
|
||||||
<ListAdd theme="filled" />
|
<ListAdd theme="filled" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<n-text>添加到歌单</n-text>
|
<n-text>{{ $t("menu.add") }}</n-text>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="item"
|
class="item"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
downloadSongRef.openDownloadModel(drawerData);
|
downloadSongRef.openDownloadModal(drawerData);
|
||||||
drawerShow = false;
|
drawerShow = false;
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@@ -212,7 +211,7 @@
|
|||||||
<n-icon size="20">
|
<n-icon size="20">
|
||||||
<DownloadFour theme="filled" />
|
<DownloadFour theme="filled" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<n-text>歌曲下载</n-text>
|
<n-text>{{ $t("menu.download") }}</n-text>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="item"
|
class="item"
|
||||||
@@ -221,7 +220,7 @@
|
|||||||
<n-icon size="20">
|
<n-icon size="20">
|
||||||
<Comments theme="filled" />
|
<Comments theme="filled" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<n-text>前往评论区</n-text>
|
<n-text>{{ $t("menu.comment") }}</n-text>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="item"
|
class="item"
|
||||||
@@ -231,7 +230,7 @@
|
|||||||
<n-icon size="20">
|
<n-icon size="20">
|
||||||
<Video theme="filled" />
|
<Video theme="filled" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<n-text>观看 MV</n-text>
|
<n-text>{{ $t("menu.mv") }}</n-text>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="item"
|
class="item"
|
||||||
@@ -245,14 +244,14 @@
|
|||||||
<n-icon size="20">
|
<n-icon size="20">
|
||||||
<LinkTwo theme="filled" />
|
<LinkTwo theme="filled" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<n-text>复制歌曲链接</n-text>
|
<n-text>{{ $t("menu.copy") }}</n-text>
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<n-icon size="20">
|
<n-icon size="20">
|
||||||
<Voice theme="filled" />
|
<Voice theme="filled" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<n-text>
|
<n-text>
|
||||||
歌手:
|
{{ $t("general.name.artists") }}:
|
||||||
<AllArtists
|
<AllArtists
|
||||||
class="text-hidden"
|
class="text-hidden"
|
||||||
:artistsData="drawerData.artist"
|
:artistsData="drawerData.artist"
|
||||||
@@ -266,7 +265,9 @@
|
|||||||
<n-icon size="20">
|
<n-icon size="20">
|
||||||
<RecordDisc theme="filled" />
|
<RecordDisc theme="filled" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<n-text>专辑:{{ drawerData.album.name }}</n-text>
|
<n-text>
|
||||||
|
{{ $t("general.name.album") }}: {{ drawerData.album.name }}
|
||||||
|
</n-text>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="router.currentRoute.value.name === 'user-cloud'"
|
v-if="router.currentRoute.value.name === 'user-cloud'"
|
||||||
@@ -281,7 +282,7 @@
|
|||||||
<n-icon size="20">
|
<n-icon size="20">
|
||||||
<FileMusic theme="filled" />
|
<FileMusic theme="filled" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<n-text>歌曲信息纠正</n-text>
|
<n-text>{{ $t("menu.match") }}</n-text>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="router.currentRoute.value.name === 'user-cloud'"
|
v-if="router.currentRoute.value.name === 'user-cloud'"
|
||||||
@@ -296,7 +297,7 @@
|
|||||||
<n-icon size="20">
|
<n-icon size="20">
|
||||||
<DeleteFour theme="filled" />
|
<DeleteFour theme="filled" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<n-text>从云盘中删除</n-text>
|
<n-text>{{ $t("menu.delete") }}</n-text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n-drawer-content>
|
</n-drawer-content>
|
||||||
@@ -333,12 +334,15 @@ import { musicStore, settingStore, userStore } from "@/store";
|
|||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { setCloudDel } from "@/api/user";
|
import { setCloudDel } from "@/api/user";
|
||||||
import { NIcon } from "naive-ui";
|
import { NIcon } from "naive-ui";
|
||||||
|
import { soundStop } from "@/utils/Player";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import AllArtists from "./AllArtists.vue";
|
import AllArtists from "./AllArtists.vue";
|
||||||
import AddPlaylist from "@/components/DataModel/AddPlaylist.vue";
|
import AddPlaylist from "@/components/DataModal/AddPlaylist.vue";
|
||||||
import CloudMatch from "@/components/DataModel/CloudMatch.vue";
|
import CloudMatch from "@/components/DataModal/CloudMatch.vue";
|
||||||
import DownloadSong from "@/components/DataModel/DownloadSong.vue";
|
import DownloadSong from "@/components/DataModal/DownloadSong.vue";
|
||||||
import SmallSongData from "./SmallSongData.vue";
|
import SmallSongData from "./SmallSongData.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const music = musicStore();
|
const music = musicStore();
|
||||||
const setting = settingStore();
|
const setting = settingStore();
|
||||||
@@ -350,7 +354,7 @@ const downloadSongRef = ref(null);
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
// 列表数据
|
// 列表数据
|
||||||
listData: {
|
listData: {
|
||||||
type: Object,
|
type: Array,
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
// 专辑隐藏
|
// 专辑隐藏
|
||||||
@@ -370,30 +374,14 @@ const rightMenuOptions = ref(null);
|
|||||||
const drawerShow = ref(false);
|
const drawerShow = ref(false);
|
||||||
const drawerData = ref(null);
|
const drawerData = ref(null);
|
||||||
|
|
||||||
// 复制歌曲链接或ID
|
|
||||||
const copySongData = (id, url = true) => {
|
|
||||||
if (navigator.clipboard) {
|
|
||||||
try {
|
|
||||||
navigator.clipboard.writeText(
|
|
||||||
url ? `https://music.163.com/#/song?id=${id}` : id
|
|
||||||
);
|
|
||||||
$message.success(`歌曲${url ? "链接" : " ID "}复制成功`);
|
|
||||||
} catch (err) {
|
|
||||||
$message.error("复制失败:", err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$message.error("您的浏览器暂不支持该操作");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 图标渲染
|
// 图标渲染
|
||||||
const renderIcon = (icon) => {
|
const renderIcon = (icon, filled = true) => {
|
||||||
return () => {
|
return () => {
|
||||||
return h(
|
return h(
|
||||||
NIcon,
|
NIcon,
|
||||||
{ depth: 2, style: { transform: "translateX(2px)" } },
|
{ depth: 2, style: { transform: "translateX(2px)" } },
|
||||||
{
|
{
|
||||||
default: () => h(icon, { theme: "filled" }),
|
default: () => h(icon, { theme: filled ? "filled" : "outline" }),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -407,7 +395,7 @@ const openRightMenu = (e, data) => {
|
|||||||
rightMenuOptions.value = [
|
rightMenuOptions.value = [
|
||||||
{
|
{
|
||||||
key: "play",
|
key: "play",
|
||||||
label: "立即播放",
|
label: t("menu.play"),
|
||||||
icon: renderIcon(PlayOne),
|
icon: renderIcon(PlayOne),
|
||||||
props: {
|
props: {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
@@ -417,10 +405,10 @@ const openRightMenu = (e, data) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "nextPlay",
|
key: "nextPlay",
|
||||||
label: "下一首播放",
|
label: t("menu.nextPlay"),
|
||||||
icon: renderIcon(AddMusic),
|
icon: renderIcon(AddMusic),
|
||||||
show:
|
show:
|
||||||
music.getPersonalFmMode || music.getPlaySongData.id == data.id
|
music.getPersonalFmMode || music.getPlaySongData?.id == data.id
|
||||||
? false
|
? false
|
||||||
: true,
|
: true,
|
||||||
props: {
|
props: {
|
||||||
@@ -431,7 +419,7 @@ const openRightMenu = (e, data) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "add",
|
key: "add",
|
||||||
label: "添加到歌单",
|
label: t("menu.add"),
|
||||||
icon: renderIcon(ListAdd),
|
icon: renderIcon(ListAdd),
|
||||||
show: user.userLogin ? true : false,
|
show: user.userLogin ? true : false,
|
||||||
props: {
|
props: {
|
||||||
@@ -442,18 +430,18 @@ const openRightMenu = (e, data) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "download",
|
key: "download",
|
||||||
label: "歌曲下载",
|
label: t("menu.download"),
|
||||||
icon: renderIcon(DownloadFour),
|
icon: renderIcon(DownloadFour),
|
||||||
props: {
|
props: {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
downloadSongRef.value.openDownloadModel(data);
|
downloadSongRef.value.openDownloadModal(data);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "comment",
|
key: "comment",
|
||||||
label: "前往评论区",
|
label: t("menu.comment"),
|
||||||
icon: renderIcon(Comments),
|
icon: renderIcon(Comments, false),
|
||||||
props: {
|
props: {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
router.push(`/comment?id=${data.id}`);
|
router.push(`/comment?id=${data.id}`);
|
||||||
@@ -462,8 +450,8 @@ const openRightMenu = (e, data) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "mv",
|
key: "mv",
|
||||||
label: "观看 MV",
|
label: t("menu.mv"),
|
||||||
icon: renderIcon(Video),
|
icon: renderIcon(Video, false),
|
||||||
show: data.mv && data.mv != 0 ? true : false,
|
show: data.mv && data.mv != 0 ? true : false,
|
||||||
props: {
|
props: {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
@@ -478,7 +466,7 @@ const openRightMenu = (e, data) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "delete",
|
key: "delete",
|
||||||
label: "从云盘中删除",
|
label: t("menu.delete"),
|
||||||
icon: renderIcon(DeleteFour),
|
icon: renderIcon(DeleteFour),
|
||||||
show: router.currentRoute.value.name === "user-cloud" ? true : false,
|
show: router.currentRoute.value.name === "user-cloud" ? true : false,
|
||||||
props: {
|
props: {
|
||||||
@@ -489,7 +477,7 @@ const openRightMenu = (e, data) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "match",
|
key: "match",
|
||||||
label: "歌曲信息纠正",
|
label: t("menu.match"),
|
||||||
icon: renderIcon(FileMusic),
|
icon: renderIcon(FileMusic),
|
||||||
show: router.currentRoute.value.name === "user-cloud" ? true : false,
|
show: router.currentRoute.value.name === "user-cloud" ? true : false,
|
||||||
props: {
|
props: {
|
||||||
@@ -504,8 +492,8 @@ const openRightMenu = (e, data) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "search",
|
key: "search",
|
||||||
label: "同名搜索",
|
label: t("menu.search"),
|
||||||
icon: renderIcon(Search),
|
icon: renderIcon(Search, false),
|
||||||
props: {
|
props: {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
router.push({
|
router.push({
|
||||||
@@ -520,8 +508,11 @@ const openRightMenu = (e, data) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "copyId",
|
key: "copyId",
|
||||||
label: "复制歌曲 ID",
|
label: t("menu.copy", {
|
||||||
icon: renderIcon(FileMusic),
|
name: t("general.name.song"),
|
||||||
|
other: "ID",
|
||||||
|
}),
|
||||||
|
icon: renderIcon(FileMusic, false),
|
||||||
props: {
|
props: {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
copySongData(data.id, false);
|
copySongData(data.id, false);
|
||||||
@@ -530,7 +521,10 @@ const openRightMenu = (e, data) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "copy",
|
key: "copy",
|
||||||
label: "复制歌曲链接",
|
label: t("menu.copy", {
|
||||||
|
name: t("general.name.song"),
|
||||||
|
other: t("general.name.link"),
|
||||||
|
}),
|
||||||
icon: renderIcon(LinkTwo),
|
icon: renderIcon(LinkTwo),
|
||||||
props: {
|
props: {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
@@ -550,23 +544,42 @@ const onClickoutside = () => {
|
|||||||
rightMenuShow.value = false;
|
rightMenuShow.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 复制歌曲链接或ID
|
||||||
|
const copySongData = (id, url = true) => {
|
||||||
|
if (navigator.clipboard) {
|
||||||
|
try {
|
||||||
|
navigator.clipboard.writeText(
|
||||||
|
url ? `https://music.163.com/#/song?id=${id}` : id
|
||||||
|
);
|
||||||
|
$message.success(t("general.message.copySuccess"));
|
||||||
|
} catch (err) {
|
||||||
|
console.error(t("general.message.copyFailure"), err);
|
||||||
|
$message.error(t("general.message.copyFailure"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$message.error(t("general.message.notSupported"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 云盘歌曲删除
|
// 云盘歌曲删除
|
||||||
const delCloudSong = (data) => {
|
const delCloudSong = (data) => {
|
||||||
$dialog.warning({
|
$dialog.warning({
|
||||||
class: "s-dialog",
|
class: "s-dialog",
|
||||||
title: "歌曲删除",
|
title: t("general.dialog.delete"),
|
||||||
content: "确认从云盘中删除歌曲 " + data.name + " ?",
|
content: t("menu.deleteQuestion", {
|
||||||
positiveText: "删除",
|
name: data.name,
|
||||||
negativeText: "取消",
|
}),
|
||||||
|
positiveText: t("general.dialog.delete"),
|
||||||
|
negativeText: t("general.dialog.cancel"),
|
||||||
onPositiveClick: () => {
|
onPositiveClick: () => {
|
||||||
setCloudDel(data.id).then((res) => {
|
setCloudDel(data.id).then((res) => {
|
||||||
if (res.code == 200) {
|
if (res.code == 200) {
|
||||||
$message.success("云盘歌曲删除成功");
|
$message.success(t("general.message.deleteSuccess"));
|
||||||
props.listData.forEach((v, i) => {
|
props.listData.forEach((v, i) => {
|
||||||
if (v.id == data.id) props.listData.splice(i, 1);
|
if (v.id == data.id) props.listData.splice(i, 1);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$message.error("云盘歌曲删除失败");
|
$message.error(t("general.message.deleteFailure"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -583,7 +596,11 @@ const openDrawer = (data) => {
|
|||||||
// 播放并添加
|
// 播放并添加
|
||||||
const playSong = (data, song) => {
|
const playSong = (data, song) => {
|
||||||
console.log(data, song);
|
console.log(data, song);
|
||||||
|
if (music.getPersonalFmMode) {
|
||||||
|
soundStop($player);
|
||||||
music.setPersonalFmMode(false);
|
music.setPersonalFmMode(false);
|
||||||
|
}
|
||||||
|
music.setPlayState(true);
|
||||||
if (router.currentRoute.value.name !== "history") music.setPlaylists(data);
|
if (router.currentRoute.value.name !== "history") music.setPlaylists(data);
|
||||||
// 检查是否为云盘歌曲
|
// 检查是否为云盘歌曲
|
||||||
if (router.currentRoute.value.name === "user-cloud") {
|
if (router.currentRoute.value.name === "user-cloud") {
|
||||||
@@ -637,9 +654,10 @@ const jumpLink = (id, type) => {
|
|||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: $mainColor;
|
border-color: var(--main-color);
|
||||||
box-shadow: 0 1px 2px -2px #f55e5526, 0 3px 6px 0 #f55e5530,
|
box-shadow: 0 1px 2px -2px var(--main-boxshadow-color),
|
||||||
0 5px 12px 4px #f55e5505;
|
0 3px 6px 0 var(--main-boxshadow-color),
|
||||||
|
0 5px 12px 4px var(--main-boxshadow-hover-color);
|
||||||
.action {
|
.action {
|
||||||
.like,
|
.like,
|
||||||
.download {
|
.download {
|
||||||
@@ -652,18 +670,18 @@ const jumpLink = (id, type) => {
|
|||||||
// transform: scale(0.99);
|
// transform: scale(0.99);
|
||||||
// }
|
// }
|
||||||
&.play {
|
&.play {
|
||||||
background-color: $mainSecondaryColor;
|
background-color: var(--main-second-color);
|
||||||
border-color: $mainColor;
|
border-color: var(--main-color);
|
||||||
a,
|
a,
|
||||||
span,
|
span,
|
||||||
.n-icon {
|
.n-icon {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
.artists {
|
.artists {
|
||||||
:deep(.artist) {
|
:deep(.artist) {
|
||||||
.name,
|
.name,
|
||||||
.line {
|
.line {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -703,7 +721,7 @@ const jumpLink = (id, type) => {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.n-tag {
|
.n-tag {
|
||||||
@@ -712,8 +730,8 @@ const jumpLink = (id, type) => {
|
|||||||
height: 18px;
|
height: 18px;
|
||||||
}
|
}
|
||||||
.vip {
|
.vip {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
background-color: $mainSecondaryColor;
|
background-color: var(--main-second-color);
|
||||||
}
|
}
|
||||||
.mv {
|
.mv {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -744,7 +762,7 @@ const jumpLink = (id, type) => {
|
|||||||
.n-text {
|
.n-text {
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -770,7 +788,7 @@ const jumpLink = (id, type) => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: scale(0.8);
|
transform: scale(0.8);
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<n-text
|
<n-text
|
||||||
class="text-hidden"
|
class="text-hidden"
|
||||||
depth="2"
|
depth="2"
|
||||||
v-html="songDetail ? songDetail.name : '未知歌曲'"
|
v-html="songDetail ? songDetail.name : $t('general.name.unknownSong')"
|
||||||
@click.stop="router.push(`/song?id=${songDetail.id}`)"
|
@click.stop="router.push(`/song?id=${songDetail.id}`)"
|
||||||
/>
|
/>
|
||||||
<AllArtists
|
<AllArtists
|
||||||
@@ -29,8 +29,10 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { getMusicDetail } from "@/api/song";
|
import { getMusicDetail } from "@/api/song";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import AllArtists from "./AllArtists.vue";
|
import AllArtists from "./AllArtists.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
// 歌曲数据
|
// 歌曲数据
|
||||||
@@ -64,7 +66,7 @@ const getMusicDetailData = (id) => {
|
|||||||
id: res.songs[0].id,
|
id: res.songs[0].id,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
$message.error("歌曲信息获取失败");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -105,6 +107,7 @@ onMounted(() => {
|
|||||||
.pic {
|
.pic {
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
min-width: 48px;
|
||||||
}
|
}
|
||||||
.name {
|
.name {
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
@@ -113,7 +116,7 @@ onMounted(() => {
|
|||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.artists {
|
.artists {
|
||||||
|
|||||||
@@ -66,7 +66,6 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { PlayOne, Youtube } from "@icon-park/vue-next";
|
import { PlayOne, Youtube } from "@icon-park/vue-next";
|
||||||
import { PlayArrowRound, OndemandVideoFilled } from "@vicons/material";
|
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import AllArtists from "./AllArtists.vue";
|
import AllArtists from "./AllArtists.vue";
|
||||||
|
|
||||||
@@ -74,7 +73,7 @@ const router = useRouter();
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
// 列表数据
|
// 列表数据
|
||||||
listData: {
|
listData: {
|
||||||
type: Object,
|
type: Array,
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -177,7 +176,7 @@ const props = defineProps({
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.by {
|
.by {
|
||||||
@@ -187,7 +186,7 @@ const props = defineProps({
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.artists {
|
.artists {
|
||||||
|
|||||||
@@ -4,13 +4,13 @@
|
|||||||
class="s-modal"
|
class="s-modal"
|
||||||
v-model:show="showAboutModal"
|
v-model:show="showAboutModal"
|
||||||
preset="card"
|
preset="card"
|
||||||
title="关于本站"
|
:title="$t('nav.avatar.about')"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
transform-origin="center"
|
transform-origin="center"
|
||||||
>
|
>
|
||||||
<div class="copyright">
|
<div class="copyright">
|
||||||
<div class="desc">
|
<div class="desc">
|
||||||
<n-text class="name">SPlayer</n-text>
|
<n-text class="name">{{ siteTitle }}</n-text>
|
||||||
<n-text class="version" :depth="3">
|
<n-text class="version" :depth="3">
|
||||||
v {{ packageJson.version }}
|
v {{ packageJson.version }}
|
||||||
</n-text>
|
</n-text>
|
||||||
@@ -53,6 +53,7 @@ import { GithubOne } from "@icon-park/vue-next";
|
|||||||
import packageJson from "@/../package.json";
|
import packageJson from "@/../package.json";
|
||||||
|
|
||||||
// 关于本站数据
|
// 关于本站数据
|
||||||
|
const siteTitle = import.meta.env.VITE_SITE_TITLE;
|
||||||
const showAboutModal = ref(false);
|
const showAboutModal = ref(false);
|
||||||
const icp = ref(import.meta.env.VITE_ICP ? import.meta.env.VITE_ICP : null);
|
const icp = ref(import.meta.env.VITE_ICP ? import.meta.env.VITE_ICP : null);
|
||||||
|
|
||||||
158
src/components/DataModal/AddPlaylist.vue
Normal file
158
src/components/DataModal/AddPlaylist.vue
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
<template>
|
||||||
|
<n-modal
|
||||||
|
class="add-playlist s-modal"
|
||||||
|
v-model:show="addToPlaylistModal"
|
||||||
|
preset="card"
|
||||||
|
:bordered="false"
|
||||||
|
:on-after-leave="closeAddToPlaylist"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
{{ $t("menu.add") }}
|
||||||
|
<n-tag
|
||||||
|
round
|
||||||
|
class="tag"
|
||||||
|
type="primary"
|
||||||
|
:style="{
|
||||||
|
marginLeft: '12px',
|
||||||
|
fontSize: '13px',
|
||||||
|
transform: 'translateY(-2px)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
}"
|
||||||
|
:bordered="false"
|
||||||
|
@click="createPlaylistRef.openCreatePlaylist()"
|
||||||
|
>
|
||||||
|
{{ $t("menu.create") }}
|
||||||
|
</n-tag>
|
||||||
|
</template>
|
||||||
|
<Transition mode="out-in">
|
||||||
|
<div v-if="user.getUserPlayLists.own[0]">
|
||||||
|
<n-space vertical class="list">
|
||||||
|
<div
|
||||||
|
class="item"
|
||||||
|
v-for="item in user.getUserPlayLists.own.slice(1)"
|
||||||
|
:key="item"
|
||||||
|
@click="addToPlayList(item.id, addToPlaylistId)"
|
||||||
|
>
|
||||||
|
<n-avatar
|
||||||
|
class="pic"
|
||||||
|
:src="
|
||||||
|
item.cover
|
||||||
|
? item.cover.replace(/^http:/, 'https:') + '?param=60y60'
|
||||||
|
: '/images/pic/default.png'
|
||||||
|
"
|
||||||
|
fallback-src="/images/pic/default.png"
|
||||||
|
/>
|
||||||
|
<div class="desc">
|
||||||
|
<n-text class="name">{{ item.name }}</n-text>
|
||||||
|
<n-text class="num">{{
|
||||||
|
$t("general.name.songSize", {
|
||||||
|
size: item.trackCount,
|
||||||
|
})
|
||||||
|
}}</n-text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-space>
|
||||||
|
</div>
|
||||||
|
<n-text v-else>{{ $t("general.message.isLoading") }}</n-text>
|
||||||
|
</Transition>
|
||||||
|
</n-modal>
|
||||||
|
<!-- 新建歌单 -->
|
||||||
|
<CreatePlaylist ref="createPlaylistRef" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { addSongToPlayList } from "@/api/playlist";
|
||||||
|
import { userStore } from "@/store";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import CreatePlaylist from "./CreatePlaylist.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const user = userStore();
|
||||||
|
const createPlaylistRef = ref(null);
|
||||||
|
|
||||||
|
// 收藏到歌单数据
|
||||||
|
const addToPlaylistModal = ref(false);
|
||||||
|
const addToPlaylistId = ref(null);
|
||||||
|
|
||||||
|
// 收藏到歌单
|
||||||
|
const addToPlayList = (pid, tracks) => {
|
||||||
|
addSongToPlayList(pid, tracks).then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
if (res.status === 200) {
|
||||||
|
$message.success(t("general.message.addSuccess"));
|
||||||
|
closeAddToPlaylist();
|
||||||
|
user.setUserPlayLists();
|
||||||
|
} else {
|
||||||
|
$message.error(t("general.message.addFailure"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开启收藏到歌单
|
||||||
|
const openAddToPlaylist = (id) => {
|
||||||
|
if (!user.userLogin) {
|
||||||
|
$message.error(t("general.message.needLogin"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!user.getUserPlayLists.has && !user.getUserPlayLists.isLoading) {
|
||||||
|
user.setUserPlayLists();
|
||||||
|
}
|
||||||
|
addToPlaylistModal.value = true;
|
||||||
|
addToPlaylistId.value = id;
|
||||||
|
console.log("开启", addToPlaylistModal.value, addToPlaylistId.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭收藏到歌单
|
||||||
|
const closeAddToPlaylist = () => {
|
||||||
|
addToPlaylistModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 暴露方法
|
||||||
|
defineExpose({
|
||||||
|
openAddToPlaylist,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.add-playlist {
|
||||||
|
.v-enter-active,
|
||||||
|
.v-leave-active {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-enter-from,
|
||||||
|
.v-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.list {
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--n-border-color);
|
||||||
|
}
|
||||||
|
.pic {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
.desc {
|
||||||
|
margin-left: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
.name {
|
||||||
|
// font-weight: bold;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
.num {
|
||||||
|
margin-top: 2px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-modal
|
<n-modal
|
||||||
class="s-modal"
|
class="s-modal"
|
||||||
v-model:show="cloudMatchModel"
|
v-model:show="cloudMatchModal"
|
||||||
preset="card"
|
preset="card"
|
||||||
title="歌曲信息纠正"
|
:title="$t('menu.match')"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
:on-after-leave="closeCloudMatch"
|
:on-after-leave="closeCloudMatch"
|
||||||
>
|
>
|
||||||
<n-form class="cloud-match" :label-width="80" :model="cloudMatchValue">
|
<n-form class="cloud-match" :label-width="80" :model="cloudMatchValue">
|
||||||
<n-form-item label="原歌曲信息">
|
<n-form-item :label="$t('other.sData')">
|
||||||
<n-card content-style="padding: 16px" :bordered="false" embedded>
|
<n-card content-style="padding: 16px" :bordered="false" embedded>
|
||||||
<SmallSongData :songData="cloudMatchBeforeData" notJump />
|
<SmallSongData :songData="cloudMatchBeforeData" notJump />
|
||||||
</n-card>
|
</n-card>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="匹配 ID" path="asid">
|
<n-form-item :label="$t('other.asId')" path="asid">
|
||||||
<n-input-number
|
<n-input-number
|
||||||
v-model:value="cloudMatchValue.asid"
|
v-model:value="cloudMatchValue.asid"
|
||||||
placeholder="请输入要匹配的歌曲 ID"
|
:placeholder="$t('other.asIdDes')"
|
||||||
:show-button="false"
|
:show-button="false"
|
||||||
/>
|
/>
|
||||||
<n-button
|
<n-button
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
:disabled="!cloudMatchValue.asid"
|
:disabled="!cloudMatchValue.asid"
|
||||||
@click="cloudMatchId = cloudMatchValue.asid.toString()"
|
@click="cloudMatchId = cloudMatchValue.asid.toString()"
|
||||||
>
|
>
|
||||||
检查
|
{{ $t("general.dialog.check") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
@@ -42,13 +42,15 @@
|
|||||||
</n-card>
|
</n-card>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<n-space justify="end">
|
<n-space justify="end">
|
||||||
<n-button @click="closeCloudMatch"> 取消 </n-button>
|
<n-button @click="closeCloudMatch">
|
||||||
|
{{ $t("general.dialog.cancel") }}
|
||||||
|
</n-button>
|
||||||
<n-button
|
<n-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="setCloudMatchBtn(cloudMatchValue)"
|
@click="setCloudMatchBtn(cloudMatchValue)"
|
||||||
:disabled="!cloudMatchValue.asid"
|
:disabled="!cloudMatchValue.asid"
|
||||||
>
|
>
|
||||||
纠正歌曲
|
{{ $t("general.dialog.match") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
@@ -58,14 +60,16 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { setCloudMatch } from "@/api/user";
|
import { setCloudMatch } from "@/api/user";
|
||||||
import { userStore } from "@/store";
|
import { userStore } from "@/store";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import SmallSongData from "@/components/DataList/SmallSongData.vue";
|
import SmallSongData from "@/components/DataList/SmallSongData.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const user = userStore();
|
const user = userStore();
|
||||||
|
|
||||||
// 歌曲信息纠正数据
|
// 歌曲信息纠正数据
|
||||||
const cloudDataLoad = inject("cloudDataLoad", null);
|
const cloudDataLoad = inject("cloudDataLoad", null);
|
||||||
const smallSongDataRef = ref(null);
|
const smallSongDataRef = ref(null);
|
||||||
const cloudMatchModel = ref(false);
|
const cloudMatchModal = ref(false);
|
||||||
const cloudMatchBeforeData = ref(null);
|
const cloudMatchBeforeData = ref(null);
|
||||||
const cloudMatchId = ref(null);
|
const cloudMatchId = ref(null);
|
||||||
const cloudMatchValue = ref({
|
const cloudMatchValue = ref({
|
||||||
@@ -77,23 +81,23 @@ const cloudMatchValue = ref({
|
|||||||
// 歌曲纠正
|
// 歌曲纠正
|
||||||
const setCloudMatchBtn = (data) => {
|
const setCloudMatchBtn = (data) => {
|
||||||
if (data.sid == data.asid) {
|
if (data.sid == data.asid) {
|
||||||
$message.error("与原歌曲 ID 一致,无需纠正");
|
$message.error(t("other.noNeedMatch"));
|
||||||
} else {
|
} else {
|
||||||
if (!smallSongDataRef.value) {
|
if (!smallSongDataRef.value) {
|
||||||
$message.error("请先检查");
|
$message.error(t("other.plaseCheck"));
|
||||||
} else if (smallSongDataRef.value.checkSongData()) {
|
} else if (smallSongDataRef.value.checkSongData()) {
|
||||||
setCloudMatch(data.uid, data.sid, data.asid).then((res) => {
|
setCloudMatch(data.uid, data.sid, data.asid).then((res) => {
|
||||||
console.log(res);
|
console.log(res);
|
||||||
if (res.data) {
|
if (res.data) {
|
||||||
closeCloudMatch();
|
closeCloudMatch();
|
||||||
$message.success("歌曲纠正成功");
|
$message.success(t("other.matchSuccess"));
|
||||||
cloudDataLoad();
|
cloudDataLoad();
|
||||||
} else {
|
} else {
|
||||||
$message.error("歌曲纠正失败,请重试");
|
$message.error(t("other.matchFailed"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$message.error("非正常歌曲 ID,无法匹配");
|
$message.error(t("other.matchError"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -102,7 +106,7 @@ const setCloudMatchBtn = (data) => {
|
|||||||
const openCloudMatch = (data) => {
|
const openCloudMatch = (data) => {
|
||||||
cloudMatchValue.value.sid = data.id;
|
cloudMatchValue.value.sid = data.id;
|
||||||
cloudMatchBeforeData.value = data;
|
cloudMatchBeforeData.value = data;
|
||||||
cloudMatchModel.value = true;
|
cloudMatchModal.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 关闭歌曲纠正
|
// 关闭歌曲纠正
|
||||||
@@ -110,7 +114,7 @@ const closeCloudMatch = () => {
|
|||||||
cloudMatchBeforeData.value = null;
|
cloudMatchBeforeData.value = null;
|
||||||
cloudMatchId.value = null;
|
cloudMatchId.value = null;
|
||||||
cloudMatchValue.value.asid = null;
|
cloudMatchValue.value.asid = null;
|
||||||
cloudMatchModel.value = false;
|
cloudMatchModal.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 暴露方法
|
// 暴露方法
|
||||||
78
src/components/DataModal/CreatePlaylist.vue
Normal file
78
src/components/DataModal/CreatePlaylist.vue
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<template>
|
||||||
|
<n-modal
|
||||||
|
class="s-modal"
|
||||||
|
v-model:show="createPlaylistShow"
|
||||||
|
preset="card"
|
||||||
|
:title="$t('menu.create')"
|
||||||
|
:bordered="false"
|
||||||
|
:on-after-leave="closeCreatePlaylist"
|
||||||
|
>
|
||||||
|
<n-input
|
||||||
|
style="margin-bottom: 12px"
|
||||||
|
v-model:value="createName"
|
||||||
|
type="text"
|
||||||
|
:placeholder="$t('other.newPlaylistName')"
|
||||||
|
/>
|
||||||
|
<n-checkbox v-model:checked="createPrivacy">
|
||||||
|
{{ $t("other.setPrivacy") }}
|
||||||
|
</n-checkbox>
|
||||||
|
<template #footer>
|
||||||
|
<n-space justify="end">
|
||||||
|
<n-button @click="closeCreatePlaylist">
|
||||||
|
{{ $t("general.dialog.cancel") }}
|
||||||
|
</n-button>
|
||||||
|
<n-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!createName"
|
||||||
|
@click="createPlaylistBtn(createName, createPrivacy)"
|
||||||
|
>
|
||||||
|
{{ $t("general.dialog.create") }}
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { userStore } from "@/store";
|
||||||
|
import { createPlaylist } from "@/api/playlist";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const user = userStore();
|
||||||
|
|
||||||
|
// 新建歌单数据
|
||||||
|
const createPlaylistShow = ref(false);
|
||||||
|
const createPrivacy = ref(false);
|
||||||
|
const createName = ref(null);
|
||||||
|
|
||||||
|
// 新建歌单
|
||||||
|
const createPlaylistBtn = (name, privacy = false) => {
|
||||||
|
createPlaylist(name, privacy ? "10" : null).then((res) => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
closeCreatePlaylist();
|
||||||
|
$message.success(t("general.message.createSuccess"));
|
||||||
|
user.setUserPlayLists();
|
||||||
|
} else {
|
||||||
|
$message.error(t("general.message.createFailed"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开启新建歌单
|
||||||
|
const openCreatePlaylist = () => {
|
||||||
|
createPlaylistShow.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消新建歌单
|
||||||
|
const closeCreatePlaylist = () => {
|
||||||
|
createName.value = null;
|
||||||
|
createPrivacy.value = false;
|
||||||
|
createPlaylistShow.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 暴露方法
|
||||||
|
defineExpose({
|
||||||
|
openCreatePlaylist,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-modal
|
<n-modal
|
||||||
class="s-modal downloadModel"
|
class="s-modal downloadModal"
|
||||||
v-model:show="downloadModel"
|
v-model:show="downloadModal"
|
||||||
preset="card"
|
preset="card"
|
||||||
title="歌曲下载"
|
:title="$t('menu.download')"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
:on-after-leave="closeDownloadModel"
|
:on-after-leave="closeDownloadModal"
|
||||||
>
|
>
|
||||||
<Transition mode="out-in">
|
<Transition mode="out-in">
|
||||||
<div v-if="songData">
|
<div v-if="songData">
|
||||||
<SmallSongData ref="smallSongDataRef" :songData="songData" notJump />
|
<SmallSongData ref="smallSongDataRef" :songData="songData" notJump />
|
||||||
<n-alert v-if="songData.pc" class="tip" type="info" :show-icon="false">
|
<n-alert v-if="songData.pc" class="tip" type="info" :show-icon="false">
|
||||||
当前为云盘歌曲,下载的文件均为最高音质
|
{{ $t("other.cloudTip") }}
|
||||||
</n-alert>
|
</n-alert>
|
||||||
<n-radio-group
|
<n-radio-group
|
||||||
class="downloadGroup"
|
class="downloadGroup"
|
||||||
@@ -31,18 +31,20 @@
|
|||||||
{{ item.size }}
|
{{ item.size }}
|
||||||
</n-text>
|
</n-text>
|
||||||
<n-text v-else-if="!item.disabled" class="error" :depth="3">
|
<n-text v-else-if="!item.disabled" class="error" :depth="3">
|
||||||
无法获取
|
{{ $t("general.message.acquisitionFailed") }}
|
||||||
</n-text>
|
</n-text>
|
||||||
</div>
|
</div>
|
||||||
</n-radio>
|
</n-radio>
|
||||||
</n-space>
|
</n-space>
|
||||||
</n-radio-group>
|
</n-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<n-text v-else>正在获取歌曲下载数据</n-text>
|
<n-text v-else>{{ $t("general.message.isLoading") }}</n-text>
|
||||||
</Transition>
|
</Transition>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<n-space justify="end">
|
<n-space justify="end">
|
||||||
<n-button @click="closeDownloadModel"> 取消 </n-button>
|
<n-button @click="closeDownloadModal">
|
||||||
|
{{ $t("general.dialog.cancel") }}
|
||||||
|
</n-button>
|
||||||
<n-button
|
<n-button
|
||||||
:disabled="!downloadChoose"
|
:disabled="!downloadChoose"
|
||||||
:loading="downloadStatus"
|
:loading="downloadStatus"
|
||||||
@@ -55,7 +57,11 @@
|
|||||||
)
|
)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{ downloadStatus ? "正在下载" : "下载" }}
|
{{
|
||||||
|
downloadStatus
|
||||||
|
? $t("general.dialog.downloadingNow")
|
||||||
|
: $t("general.dialog.download")
|
||||||
|
}}
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
@@ -66,8 +72,10 @@
|
|||||||
import { userStore } from "@/store";
|
import { userStore } from "@/store";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { getMusicDetail, getSongDownload } from "@/api/song";
|
import { getMusicDetail, getSongDownload } from "@/api/song";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import SmallSongData from "@/components/DataList/SmallSongData.vue";
|
import SmallSongData from "@/components/DataList/SmallSongData.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const user = userStore();
|
const user = userStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -75,7 +83,7 @@ const router = useRouter();
|
|||||||
const songId = ref(null);
|
const songId = ref(null);
|
||||||
const songData = ref(null);
|
const songData = ref(null);
|
||||||
const downloadStatus = ref(false);
|
const downloadStatus = ref(false);
|
||||||
const downloadModel = ref(false);
|
const downloadModal = ref(false);
|
||||||
const downloadChoose = ref(null);
|
const downloadChoose = ref(null);
|
||||||
const downloadLevel = ref(null);
|
const downloadLevel = ref(null);
|
||||||
|
|
||||||
@@ -87,8 +95,8 @@ const toSongDownload = (id, br, name) => {
|
|||||||
console.log(name, res);
|
console.log(name, res);
|
||||||
if (res.data.url) {
|
if (res.data.url) {
|
||||||
const type = res.data.type.toLowerCase();
|
const type = res.data.type.toLowerCase();
|
||||||
const songName = name ? name : "未知曲目";
|
const songName = name ? name : t("general.name.unknownSong");
|
||||||
fetch(res.data.url)
|
fetch(res.data.url.replace(/^http:/, "https:"))
|
||||||
.then((response) => response.blob())
|
.then((response) => response.blob())
|
||||||
.then((blob) => {
|
.then((blob) => {
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
@@ -98,19 +106,19 @@ const toSongDownload = (id, br, name) => {
|
|||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
a.remove();
|
a.remove();
|
||||||
closeDownloadModel();
|
closeDownloadModal();
|
||||||
downloadStatus.value = false;
|
downloadStatus.value = false;
|
||||||
$message.success(name + " 下载完成");
|
$message.success(t("general.message.downloadSuccess", { name }));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
downloadStatus.value = false;
|
downloadStatus.value = false;
|
||||||
$message.error("下载失败,请尝试其他音质");
|
$message.error(t("general.message.downloadFailure"));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
closeDownloadModel();
|
closeDownloadModal();
|
||||||
console.error("下载出现错误:" + err);
|
console.error(t("general.message.downloadError"), err);
|
||||||
$message.error("下载出现错误,请重试");
|
$message.error(t("general.message.downloadError"));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -129,13 +137,13 @@ const getMusicDetailData = (id) => {
|
|||||||
// 生成音质列表
|
// 生成音质列表
|
||||||
generateLists(res);
|
generateLists(res);
|
||||||
} else {
|
} else {
|
||||||
$message.error("歌曲信息获取失败");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
closeDownloadModel();
|
closeDownloadModal();
|
||||||
console.error("歌曲信息获取出现错误:" + err);
|
console.error(t("general.message.acquisitionFailed"), err);
|
||||||
$message.error("歌曲信息获取出现错误,请重试");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -145,25 +153,25 @@ const generateLists = (data) => {
|
|||||||
downloadLevel.value = [
|
downloadLevel.value = [
|
||||||
{
|
{
|
||||||
value: "128000",
|
value: "128000",
|
||||||
label: "标准音质",
|
label: t("general.type.quality.l"),
|
||||||
disabled: br >= 128000 ? false : true,
|
disabled: br >= 128000 ? false : true,
|
||||||
size: getSongSize(data, "l"),
|
size: getSongSize(data, "l"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "192000",
|
value: "192000",
|
||||||
label: "较高音质",
|
label: t("general.type.quality.m"),
|
||||||
disabled: br >= 192000 ? false : true,
|
disabled: br >= 192000 ? false : true,
|
||||||
size: getSongSize(data, "m"),
|
size: getSongSize(data, "m"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "320000",
|
value: "320000",
|
||||||
label: "极高音质",
|
label: t("general.type.quality.h"),
|
||||||
disabled: br >= 320000 ? false : true,
|
disabled: br >= 320000 ? false : true,
|
||||||
size: getSongSize(data, "h"),
|
size: getSongSize(data, "h"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "420000",
|
value: "420000",
|
||||||
label: "无损音质",
|
label: t("general.type.quality.sq"),
|
||||||
disabled: [128000, 192000, 320000].includes(parseInt(br)),
|
disabled: [128000, 192000, 320000].includes(parseInt(br)),
|
||||||
size: getSongSize(data, "sq"),
|
size: getSongSize(data, "sq"),
|
||||||
},
|
},
|
||||||
@@ -200,7 +208,7 @@ const getSongSize = (data, type) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 开启歌曲下载
|
// 开启歌曲下载
|
||||||
const openDownloadModel = (data) => {
|
const openDownloadModal = (data) => {
|
||||||
if (user.userLogin) {
|
if (user.userLogin) {
|
||||||
if (
|
if (
|
||||||
router.currentRoute.value.name === "user-cloud" ||
|
router.currentRoute.value.name === "user-cloud" ||
|
||||||
@@ -209,33 +217,33 @@ const openDownloadModel = (data) => {
|
|||||||
data?.pc
|
data?.pc
|
||||||
) {
|
) {
|
||||||
songId.value = data.id;
|
songId.value = data.id;
|
||||||
downloadModel.value = true;
|
downloadModal.value = true;
|
||||||
getMusicDetailData(data.id);
|
getMusicDetailData(data.id);
|
||||||
} else {
|
} else {
|
||||||
$message.error("该歌曲需使用黑胶会员下载");
|
$message.error(t("general.message.needVip"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$message.error("请登录后使用该功能");
|
$message.error(t("general.message.needLogin"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 关闭歌曲下载
|
// 关闭歌曲下载
|
||||||
const closeDownloadModel = () => {
|
const closeDownloadModal = () => {
|
||||||
songId.value = null;
|
songId.value = null;
|
||||||
songData.value = null;
|
songData.value = null;
|
||||||
downloadStatus.value = false;
|
downloadStatus.value = false;
|
||||||
downloadModel.value = false;
|
downloadModal.value = false;
|
||||||
downloadChoose.value = null;
|
downloadChoose.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 暴露方法
|
// 暴露方法
|
||||||
defineExpose({
|
defineExpose({
|
||||||
openDownloadModel,
|
openDownloadModal,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.downloadModel {
|
.downloadModal {
|
||||||
.v-enter-active,
|
.v-enter-active,
|
||||||
.v-leave-active {
|
.v-leave-active {
|
||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
189
src/components/DataModal/LyricSetting.vue
Normal file
189
src/components/DataModal/LyricSetting.vue
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 歌词设置 -->
|
||||||
|
<n-modal
|
||||||
|
:bordered="false"
|
||||||
|
:z-index="10000"
|
||||||
|
class="s-modal lyric-set"
|
||||||
|
v-model:show="LyricSettingModal"
|
||||||
|
preset="card"
|
||||||
|
title="歌词设置"
|
||||||
|
>
|
||||||
|
<n-scrollbar>
|
||||||
|
<div class="set">
|
||||||
|
<n-card class="set-item">
|
||||||
|
<div class="name">
|
||||||
|
播放页快捷设置
|
||||||
|
<span class="tip">关闭后需在设置页开启</span>
|
||||||
|
</div>
|
||||||
|
<n-switch v-model:value="showLyricSetting" :round="false" />
|
||||||
|
</n-card>
|
||||||
|
<n-card class="set-item">
|
||||||
|
<div class="name">
|
||||||
|
显示逐字歌词
|
||||||
|
<span class="tip">是否在歌曲具有逐字歌词时显示,实验性功能</span>
|
||||||
|
</div>
|
||||||
|
<n-switch v-model:value="showYrc" :round="false" />
|
||||||
|
</n-card>
|
||||||
|
<n-card class="set-item">
|
||||||
|
<div class="name">
|
||||||
|
显示歌词翻译
|
||||||
|
<span class="tip">是否在具有翻译歌词时显示</span>
|
||||||
|
</div>
|
||||||
|
<n-switch v-model:value="showTransl" :round="false" />
|
||||||
|
</n-card>
|
||||||
|
<n-card class="set-item">
|
||||||
|
<div class="name">
|
||||||
|
显示歌词音译
|
||||||
|
<span class="tip">是否在具有音译歌词时显示</span>
|
||||||
|
</div>
|
||||||
|
<n-switch v-model:value="showRoma" :round="false" />
|
||||||
|
</n-card>
|
||||||
|
<n-card class="set-item">
|
||||||
|
<div class="name">
|
||||||
|
显示前奏等待
|
||||||
|
<span class="tip">部分歌曲前奏可能存在显示错误</span>
|
||||||
|
</div>
|
||||||
|
<n-switch v-model:value="countDownShow" :round="false" />
|
||||||
|
</n-card>
|
||||||
|
<n-card class="set-item">
|
||||||
|
<div class="name">
|
||||||
|
智能暂停滚动
|
||||||
|
<span class="tip">鼠标移入歌词区域是否暂停滚动</span>
|
||||||
|
</div>
|
||||||
|
<n-switch v-model:value="lrcMousePause" :round="false" />
|
||||||
|
</n-card>
|
||||||
|
<n-card class="set-item">
|
||||||
|
<div class="name">
|
||||||
|
歌词模糊
|
||||||
|
<span class="tip">未播放或已播放歌词模糊显示,实验性功能</span>
|
||||||
|
</div>
|
||||||
|
<n-switch v-model:value="lyricsBlur" :round="false" />
|
||||||
|
</n-card>
|
||||||
|
<n-space justify="center">
|
||||||
|
<n-button
|
||||||
|
class="more"
|
||||||
|
size="large"
|
||||||
|
strong
|
||||||
|
secondary
|
||||||
|
round
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
LyricSettingModal = false;
|
||||||
|
music.setBigPlayerState(false);
|
||||||
|
router.push('/setting/player');
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
更多设置
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</div>
|
||||||
|
</n-scrollbar>
|
||||||
|
</n-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
import { settingStore } from "@/store";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { musicStore } from "@/store";
|
||||||
|
|
||||||
|
const setting = settingStore();
|
||||||
|
const router = useRouter();
|
||||||
|
const music = musicStore();
|
||||||
|
|
||||||
|
// 歌词设置弹窗
|
||||||
|
const LyricSettingModal = ref(false);
|
||||||
|
|
||||||
|
// 设置数据
|
||||||
|
const {
|
||||||
|
showTransl,
|
||||||
|
lyricsBlur,
|
||||||
|
lrcMousePause,
|
||||||
|
showYrc,
|
||||||
|
showRoma,
|
||||||
|
countDownShow,
|
||||||
|
showLyricSetting,
|
||||||
|
} = storeToRefs(setting);
|
||||||
|
|
||||||
|
// 开启歌词设置弹窗
|
||||||
|
const openLyricSetting = () => {
|
||||||
|
LyricSettingModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 暴露方法
|
||||||
|
defineExpose({
|
||||||
|
openLyricSetting,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.n-card {
|
||||||
|
&.lyric-set {
|
||||||
|
background-color: #ffffff40;
|
||||||
|
color: #fff;
|
||||||
|
.n-card-header {
|
||||||
|
.n-card-header__main,
|
||||||
|
.n-card-header__close {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.set {
|
||||||
|
width: 100%;
|
||||||
|
:deep(.set-item) {
|
||||||
|
width: 100%;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
background-color: #ffffff40;
|
||||||
|
border-color: transparent;
|
||||||
|
.n-card__content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
.name {
|
||||||
|
font-size: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-right: 20px;
|
||||||
|
.tip {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.n-switch {
|
||||||
|
--n-box-shadow-focus: none;
|
||||||
|
&.n-switch--active {
|
||||||
|
.n-switch__rail {
|
||||||
|
background-color: #ffffff78;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.n-switch__rail {
|
||||||
|
background-color: #ffffff24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.set {
|
||||||
|
width: 200px;
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
width: 140px;
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.more {
|
||||||
|
margin: 12px 0;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #ffffff40;
|
||||||
|
&:hover {
|
||||||
|
background-color: #ffffff20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,16 +2,26 @@
|
|||||||
<n-drawer
|
<n-drawer
|
||||||
class="playlist-drawer"
|
class="playlist-drawer"
|
||||||
v-model:show="playListShow"
|
v-model:show="playListShow"
|
||||||
:z-index="2000"
|
:z-index="1"
|
||||||
:width="400"
|
:width="400"
|
||||||
:trap-focus="false"
|
:trap-focus="false"
|
||||||
:block-scroll="false"
|
:block-scroll="false"
|
||||||
placement="right"
|
placement="right"
|
||||||
to="#main"
|
to="#mainContent"
|
||||||
@after-leave="music.showPlayList = false"
|
@after-leave="music.showPlayList = false"
|
||||||
@mask-click="music.showPlayList = false"
|
@mask-click="music.showPlayList = false"
|
||||||
>
|
>
|
||||||
<n-drawer-content title="播放列表" :native-scrollbar="false" closable>
|
<n-drawer-content :native-scrollbar="false" closable>
|
||||||
|
<template #header>
|
||||||
|
<div class="text">
|
||||||
|
<n-text class="name">{{ $t("general.name.playlists") }}</n-text>
|
||||||
|
<n-text class="num" :depth="3" v-if="music.getPlaylists.length > 0">
|
||||||
|
{{
|
||||||
|
$t("general.name.songSize", { size: music.getPlaylists.length })
|
||||||
|
}}
|
||||||
|
</n-text>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<Transition mode="out-in">
|
<Transition mode="out-in">
|
||||||
<div v-if="music.getPlaylists[0]">
|
<div v-if="music.getPlaylists[0]">
|
||||||
<n-card
|
<n-card
|
||||||
@@ -31,9 +41,13 @@
|
|||||||
@click="changeIndex(index)"
|
@click="changeIndex(index)"
|
||||||
>
|
>
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<div v-if="index !== music.persistData.playSongIndex" class="num">
|
<n-text
|
||||||
|
v-if="index !== music.persistData.playSongIndex"
|
||||||
|
:depth="3"
|
||||||
|
class="num"
|
||||||
|
>
|
||||||
{{ index + 1 }}
|
{{ index + 1 }}
|
||||||
</div>
|
</n-text>
|
||||||
<div v-else class="bar">
|
<div v-else class="bar">
|
||||||
<div
|
<div
|
||||||
v-for="item in 3"
|
v-for="item in 3"
|
||||||
@@ -61,7 +75,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</n-card>
|
</n-card>
|
||||||
</div>
|
</div>
|
||||||
<n-text v-else>暂无歌曲,请前往列表添加</n-text>
|
<n-text v-else>{{ $t("other.playlistEmpty") }}</n-text>
|
||||||
</Transition>
|
</Transition>
|
||||||
</n-drawer-content>
|
</n-drawer-content>
|
||||||
</n-drawer>
|
</n-drawer>
|
||||||
@@ -70,8 +84,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { musicStore } from "@/store";
|
import { musicStore } from "@/store";
|
||||||
import { DeleteFour } from "@icon-park/vue-next";
|
import { DeleteFour } from "@icon-park/vue-next";
|
||||||
|
import { soundStop } from "@/utils/Player";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import AllArtists from "@/components/DataList/AllArtists.vue";
|
import AllArtists from "@/components/DataList/AllArtists.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const music = musicStore();
|
const music = musicStore();
|
||||||
|
|
||||||
// 播放列表显隐
|
// 播放列表显隐
|
||||||
@@ -79,8 +96,17 @@ const playListShow = ref(false);
|
|||||||
|
|
||||||
// 改变播放索引
|
// 改变播放索引
|
||||||
const changeIndex = (index) => {
|
const changeIndex = (index) => {
|
||||||
|
try {
|
||||||
|
if (music.persistData.playSongIndex !== index) {
|
||||||
|
if (typeof $player !== "undefined") soundStop($player);
|
||||||
music.persistData.playSongIndex = index;
|
music.persistData.playSongIndex = index;
|
||||||
|
music.isLoadingSong = true;
|
||||||
music.setPlayState(true);
|
music.setPlayState(true);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(t("general.message.operationFailed"), err);
|
||||||
|
$message.error(t("general.message.operationFailed"));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听播放列表显隐
|
// 监听播放列表显隐
|
||||||
@@ -89,7 +115,7 @@ watch(
|
|||||||
() => music.showPlayList,
|
() => music.showPlayList,
|
||||||
(val) => {
|
(val) => {
|
||||||
playListShow.value = val;
|
playListShow.value = val;
|
||||||
nextTick(() => {
|
nextTick().then(() => {
|
||||||
if (val && music.getPlaylists[0]) {
|
if (val && music.getPlaylists[0]) {
|
||||||
const el = document.getElementById(
|
const el = document.getElementById(
|
||||||
`playlist${music.persistData.playSongIndex}`
|
`playlist${music.persistData.playSongIndex}`
|
||||||
@@ -100,7 +126,7 @@ watch(
|
|||||||
behavior: "smooth",
|
behavior: "smooth",
|
||||||
block: "center",
|
block: "center",
|
||||||
});
|
});
|
||||||
}, 300);
|
}, 500);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
clearTimeout(timeOut.value);
|
clearTimeout(timeOut.value);
|
||||||
@@ -129,6 +155,17 @@ onBeforeUnmount(() => {
|
|||||||
.v-leave-to {
|
.v-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
.text {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.num {
|
||||||
|
font-size: 14px;
|
||||||
|
&::before {
|
||||||
|
content: "-";
|
||||||
|
margin: 0 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.songs {
|
.songs {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -150,20 +187,20 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.play {
|
&.play {
|
||||||
background-color: $mainSecondaryColor;
|
background-color: var(--main-second-color);
|
||||||
border-color: $mainColor;
|
border-color: var(--main-color);
|
||||||
a,
|
a,
|
||||||
span,
|
span,
|
||||||
div,
|
div,
|
||||||
.n-icon {
|
.n-icon {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
:deep(span) {
|
:deep(span) {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
.right {
|
.right {
|
||||||
.remove {
|
.remove {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--n-action-color);
|
background-color: var(--n-action-color);
|
||||||
}
|
}
|
||||||
@@ -186,7 +223,7 @@ onBeforeUnmount(() => {
|
|||||||
.line {
|
.line {
|
||||||
width: 3px;
|
width: 3px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
background-color: $mainColor;
|
background-color: var(--main-color);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
animation: lineMove 1s ease-in-out infinite;
|
animation: lineMove 1s ease-in-out infinite;
|
||||||
@@ -229,7 +266,7 @@ onBeforeUnmount(() => {
|
|||||||
color: #999;
|
color: #999;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
background-color: var(--n-border-color);
|
background-color: var(--n-border-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-modal
|
<n-modal
|
||||||
class="s-modal"
|
class="s-modal"
|
||||||
v-model:show="playlistUpdateModel"
|
v-model:show="playlistUpdateModal"
|
||||||
preset="card"
|
preset="card"
|
||||||
title="歌单编辑"
|
:title="$t('menu.update')"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
:on-after-leave="closeUpdateModel"
|
:on-after-leave="closeUpdateModal"
|
||||||
>
|
>
|
||||||
<n-form
|
<n-form
|
||||||
ref="playlistUpdateRef"
|
ref="playlistUpdateRef"
|
||||||
@@ -13,28 +13,28 @@
|
|||||||
:label-width="80"
|
:label-width="80"
|
||||||
:model="playlistUpdateValue"
|
:model="playlistUpdateValue"
|
||||||
>
|
>
|
||||||
<n-form-item label="歌单名称" path="name">
|
<n-form-item :label="$t('other.plName')" path="name">
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="playlistUpdateValue.name"
|
v-model:value="playlistUpdateValue.name"
|
||||||
placeholder="请输入歌单名称"
|
:placeholder="$t('other.plNameTip')"
|
||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="歌单描述" path="desc">
|
<n-form-item :label="$t('other.plDes')" path="desc">
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="playlistUpdateValue.desc"
|
v-model:value="playlistUpdateValue.desc"
|
||||||
placeholder="请输入歌单描述"
|
|
||||||
type="textarea"
|
type="textarea"
|
||||||
|
:placeholder="$t('other.plDesTip')"
|
||||||
:autosize="{
|
:autosize="{
|
||||||
minRows: 3,
|
minRows: 3,
|
||||||
maxRows: 5,
|
maxRows: 5,
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="歌单标签" path="tags">
|
<n-form-item :label="$t('other.plTag')" path="tags">
|
||||||
<n-select
|
<n-select
|
||||||
multiple
|
multiple
|
||||||
v-model:value="playlistUpdateValue.tags"
|
v-model:value="playlistUpdateValue.tags"
|
||||||
placeholder="请输入歌单标签"
|
:placeholder="$t('other.plTagTip')"
|
||||||
:options="playlistTags"
|
:options="playlistTags"
|
||||||
@click="openSelect"
|
@click="openSelect"
|
||||||
/>
|
/>
|
||||||
@@ -42,8 +42,12 @@
|
|||||||
</n-form>
|
</n-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<n-space justify="end">
|
<n-space justify="end">
|
||||||
<n-button @click="closeUpdateModel"> 取消 </n-button>
|
<n-button @click="closeUpdateModal">
|
||||||
<n-button type="primary" @click="toUpdatePlayList"> 编辑 </n-button>
|
{{ $t("general.dialog.cancel") }}
|
||||||
|
</n-button>
|
||||||
|
<n-button type="primary" @click="toUpdatePlayList">
|
||||||
|
{{ $t("general.dialog.editor") }}
|
||||||
|
</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
@@ -51,9 +55,11 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { playlistUpdate } from "@/api/playlist";
|
import { playlistUpdate } from "@/api/playlist";
|
||||||
import { formRules } from "@/utils/formRules.js";
|
import { formRules } from "@/utils/formRules";
|
||||||
import { musicStore, userStore } from "@/store";
|
import { musicStore, userStore } from "@/store";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const { textRule } = formRules();
|
const { textRule } = formRules();
|
||||||
const music = musicStore();
|
const music = musicStore();
|
||||||
const user = userStore();
|
const user = userStore();
|
||||||
@@ -61,7 +67,7 @@ const user = userStore();
|
|||||||
// 更新歌单数据
|
// 更新歌单数据
|
||||||
const playlistUpdateId = ref(null);
|
const playlistUpdateId = ref(null);
|
||||||
const playlistUpdateRef = ref(null);
|
const playlistUpdateRef = ref(null);
|
||||||
const playlistUpdateModel = ref(false);
|
const playlistUpdateModal = ref(false);
|
||||||
const playlistUpdateRules = {
|
const playlistUpdateRules = {
|
||||||
name: textRule,
|
name: textRule,
|
||||||
};
|
};
|
||||||
@@ -85,16 +91,16 @@ const toUpdatePlayList = (e) => {
|
|||||||
).then((res) => {
|
).then((res) => {
|
||||||
console.log(res);
|
console.log(res);
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
$message.success("编辑成功");
|
$message.success(t("general.message.editorSuccess"));
|
||||||
closeUpdateModel();
|
closeUpdateModal();
|
||||||
user.setUserPlayLists();
|
user.setUserPlayLists();
|
||||||
} else {
|
} else {
|
||||||
$message.error("编辑失败,请重试");
|
$message.error(t("general.message.editorFailed"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$loadingBar.error();
|
$loadingBar.error();
|
||||||
$message.error("请检查您的输入");
|
$message.error(t("general.message.needCheck"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -113,24 +119,24 @@ const openSelect = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 开启编辑歌单
|
// 开启编辑歌单
|
||||||
const openUpdateModel = (data) => {
|
const openUpdateModal = (data) => {
|
||||||
playlistUpdateValue.value = {
|
playlistUpdateValue.value = {
|
||||||
name: data.name,
|
name: data.name,
|
||||||
desc: data.desc,
|
desc: data.desc,
|
||||||
tags: data.tags,
|
tags: data.tags,
|
||||||
};
|
};
|
||||||
playlistUpdateId.value = data.id;
|
playlistUpdateId.value = data.id;
|
||||||
playlistUpdateModel.value = true;
|
playlistUpdateModal.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 关闭更新歌单弹窗
|
// 关闭更新歌单弹窗
|
||||||
const closeUpdateModel = () => {
|
const closeUpdateModal = () => {
|
||||||
playlistUpdateModel.value = false;
|
playlistUpdateModal.value = false;
|
||||||
playlistUpdateId.value = null;
|
playlistUpdateId.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 暴露方法
|
// 暴露方法
|
||||||
defineExpose({
|
defineExpose({
|
||||||
openUpdateModel,
|
openUpdateModal,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
<template>
|
|
||||||
<n-modal
|
|
||||||
class="add-playlist s-modal"
|
|
||||||
v-model:show="addToPlaylistModel"
|
|
||||||
preset="card"
|
|
||||||
title="添加到歌单"
|
|
||||||
:bordered="false"
|
|
||||||
:on-after-leave="closeAddToPlaylist"
|
|
||||||
>
|
|
||||||
<n-space vertical class="list" v-if="user.getUserPlayLists.own[0]">
|
|
||||||
<div
|
|
||||||
class="item"
|
|
||||||
v-for="item in user.getUserPlayLists.own"
|
|
||||||
:key="item"
|
|
||||||
@click="addToPlayList(item.id, addToPlaylistId)"
|
|
||||||
>
|
|
||||||
<n-avatar
|
|
||||||
class="pic"
|
|
||||||
:src="
|
|
||||||
item.cover
|
|
||||||
? item.cover.replace(/^http:/, 'https:') + '?param=60y60'
|
|
||||||
: '/images/pic/default.png'
|
|
||||||
"
|
|
||||||
fallback-src="/images/pic/default.png"
|
|
||||||
/>
|
|
||||||
<div class="desc">
|
|
||||||
<n-text class="name">{{ item.name }}</n-text>
|
|
||||||
<n-text class="num">{{ item.trackCount }}首</n-text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-space>
|
|
||||||
<n-text v-else>歌单列表加载中</n-text>
|
|
||||||
</n-modal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { addSongToPlayList } from "@/api/playlist";
|
|
||||||
import { userStore } from "@/store";
|
|
||||||
|
|
||||||
const user = userStore();
|
|
||||||
|
|
||||||
// 收藏到歌单数据
|
|
||||||
const addToPlaylistModel = ref(false);
|
|
||||||
const addToPlaylistId = ref(null);
|
|
||||||
|
|
||||||
// 收藏到歌单
|
|
||||||
const addToPlayList = (pid, tracks) => {
|
|
||||||
console.log("添加" + tracks + "到" + pid);
|
|
||||||
addSongToPlayList(pid, tracks).then((res) => {
|
|
||||||
console.log(res);
|
|
||||||
if (res.status === 200) {
|
|
||||||
$message.success("添加歌曲至歌单成功");
|
|
||||||
closeAddToPlaylist();
|
|
||||||
user.setUserPlayLists();
|
|
||||||
} else {
|
|
||||||
$message.error("添加失败,请重试");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 开启收藏到歌单
|
|
||||||
const openAddToPlaylist = (id) => {
|
|
||||||
if (!user.userLogin) {
|
|
||||||
$message.error("请登录账号后使用");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!user.getUserPlayLists.has && !user.getUserPlayLists.isLoading) {
|
|
||||||
user.setUserPlayLists();
|
|
||||||
}
|
|
||||||
addToPlaylistModel.value = true;
|
|
||||||
addToPlaylistId.value = id;
|
|
||||||
console.log("开启", addToPlaylistModel.value, addToPlaylistId.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 关闭收藏到歌单
|
|
||||||
const closeAddToPlaylist = () => {
|
|
||||||
addToPlaylistModel.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 暴露方法
|
|
||||||
defineExpose({
|
|
||||||
openAddToPlaylist,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.add-playlist {
|
|
||||||
.list {
|
|
||||||
.item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s;
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--n-border-color);
|
|
||||||
}
|
|
||||||
.pic {
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
.desc {
|
|
||||||
margin-left: 12px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
.name {
|
|
||||||
// font-weight: bold;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
.num {
|
|
||||||
margin-top: 2px;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -2,31 +2,42 @@
|
|||||||
<nav>
|
<nav>
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<div class="logo" @click="router.push('/')">
|
<div class="logo" @click="router.push('/')">
|
||||||
<img src="/images/logo/favicon.svg" alt="logo" />
|
<img :src="logoUrl" alt="logo" />
|
||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
<Transition name="fade" mode="out-in">
|
||||||
|
<div v-show="!site.searchInputActive" class="controls">
|
||||||
<n-icon size="22" :component="Left" @click="router.go(-1)" />
|
<n-icon size="22" :component="Left" @click="router.go(-1)" />
|
||||||
<n-icon size="22" :component="Right" @click="router.go(1)" />
|
<n-icon size="22" :component="Right" @click="router.go(1)" />
|
||||||
</div>
|
</div>
|
||||||
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<router-link class="link" to="/">首页</router-link>
|
<router-link class="link" to="/">{{ $t("nav.home") }}</router-link>
|
||||||
<n-dropdown
|
<n-dropdown
|
||||||
trigger="hover"
|
trigger="hover"
|
||||||
:options="discoverOptions"
|
:options="discoverOptions"
|
||||||
@select="menuSelect"
|
@select="menuSelect"
|
||||||
>
|
>
|
||||||
<router-link class="link" to="/discover">发现</router-link>
|
<router-link class="link" to="/discover">
|
||||||
|
{{ $t("nav.discover") }}
|
||||||
|
</router-link>
|
||||||
</n-dropdown>
|
</n-dropdown>
|
||||||
<n-dropdown trigger="hover" :options="userOptions" @select="menuSelect">
|
<n-dropdown trigger="hover" :options="userOptions" @select="menuSelect">
|
||||||
<router-link class="link" to="/user">我的</router-link>
|
<router-link class="link" to="/user">{{ $t("nav.user") }}</router-link>
|
||||||
</n-dropdown>
|
</n-dropdown>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<SearchInp />
|
<SearchInp />
|
||||||
|
<!-- 移动端菜单 -->
|
||||||
|
<n-dropdown trigger="click" :options="mbMenuOptions" @select="menuSelect">
|
||||||
|
<n-button class="mb-menu" circle>
|
||||||
|
<template #icon>
|
||||||
|
<n-icon :component="HamburgerButton" />
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</n-dropdown>
|
||||||
<!-- 下拉菜单 -->
|
<!-- 下拉菜单 -->
|
||||||
<n-dropdown
|
<n-dropdown
|
||||||
class="dropdown"
|
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
:show="showDropdown"
|
:show="showDropdown"
|
||||||
:show-arrow="true"
|
:show-arrow="true"
|
||||||
@@ -67,16 +78,25 @@ import {
|
|||||||
History,
|
History,
|
||||||
SunOne,
|
SunOne,
|
||||||
Moon,
|
Moon,
|
||||||
|
HamburgerButton,
|
||||||
|
HomeTwo,
|
||||||
|
FindOne,
|
||||||
|
Me,
|
||||||
} from "@icon-park/vue-next";
|
} from "@icon-park/vue-next";
|
||||||
import { userStore, settingStore } from "@/store";
|
import { userStore, settingStore, siteStore } from "@/store";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import AboutSite from "@/components/DataModel/AboutSite.vue";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import AboutSite from "@/components/DataModal/AboutSite.vue";
|
||||||
import SearchInp from "@/components/SearchInp/index.vue";
|
import SearchInp from "@/components/SearchInp/index.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const user = userStore();
|
const user = userStore();
|
||||||
|
const site = siteStore();
|
||||||
const setting = settingStore();
|
const setting = settingStore();
|
||||||
const aboutSiteRef = ref(null);
|
const aboutSiteRef = ref(null);
|
||||||
|
const timeOut = ref(null);
|
||||||
|
const logoUrl = import.meta.env.VITE_SITE_LOGO;
|
||||||
|
|
||||||
// 下拉菜单显隐
|
// 下拉菜单显隐
|
||||||
const showDropdown = ref(false);
|
const showDropdown = ref(false);
|
||||||
@@ -89,6 +109,19 @@ const closeDropdown = (event) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 图标渲染
|
||||||
|
const renderIcon = (icon) => {
|
||||||
|
return () => {
|
||||||
|
return h(
|
||||||
|
NIcon,
|
||||||
|
{ style: { transform: "translateX(2px)" } },
|
||||||
|
{
|
||||||
|
default: () => icon,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// 用户数据模块
|
// 用户数据模块
|
||||||
const userDataRender = () => {
|
const userDataRender = () => {
|
||||||
return h(
|
return h(
|
||||||
@@ -118,7 +151,9 @@ const userDataRender = () => {
|
|||||||
{ depth: 2 },
|
{ depth: 2 },
|
||||||
{
|
{
|
||||||
default: () =>
|
default: () =>
|
||||||
user.userLogin ? user.getUserData.nickname : "未登录",
|
user.userLogin
|
||||||
|
? user.getUserData.nickname
|
||||||
|
: t("nav.avatar.notLogin"),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
@@ -137,15 +172,15 @@ const userDataRender = () => {
|
|||||||
type: "line",
|
type: "line",
|
||||||
percentage:
|
percentage:
|
||||||
user.getUserOtherData.level.progress * 100,
|
user.getUserOtherData.level.progress * 100,
|
||||||
color: "#f55e55",
|
color: setting.themeData.primaryColor,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
default: () =>
|
default: () =>
|
||||||
"Lv." + user.getUserOtherData.level.level,
|
"Lv." + user.getUserOtherData.level.level,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
: "等级信息获取失败"
|
: t("nav.avatar.loginError")
|
||||||
: "登录后享受完整功能",
|
: t("nav.avatar.notLoginSubtitle"),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
@@ -155,52 +190,60 @@ const userDataRender = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 下拉框数据
|
// 下拉框数据
|
||||||
const discoverOptions = ref([
|
const discoverOptions = ref([]);
|
||||||
|
const userOptions = ref([]);
|
||||||
|
const dropdownOptions = ref([]);
|
||||||
|
|
||||||
|
// 写入下拉框数据
|
||||||
|
const changeDiscoverOptions = () => {
|
||||||
|
discoverOptions.value = [
|
||||||
{
|
{
|
||||||
label: "歌单",
|
label: t("nav.discoverChildren.playlists"),
|
||||||
key: "/discover/playlists",
|
key: "/discover/playlists",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "排行榜",
|
label: t("nav.discoverChildren.toplists"),
|
||||||
key: "/discover/toplists",
|
key: "/discover/toplists",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "歌手",
|
label: t("nav.discoverChildren.artists"),
|
||||||
key: "/discover/artists",
|
key: "/discover/artists",
|
||||||
},
|
},
|
||||||
]);
|
];
|
||||||
const userOptions = ref(
|
};
|
||||||
user.userLogin
|
const changeUserOptions = (val) => {
|
||||||
|
userOptions.value = val
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: "我的歌单",
|
label: t("nav.userChildren.playlist"),
|
||||||
key: "/user/playlists",
|
key: "/user/playlists",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "收藏的歌单",
|
label: t("nav.userChildren.like"),
|
||||||
key: "/user/like",
|
key: "/user/like",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "收藏的专辑",
|
label: t("nav.userChildren.album"),
|
||||||
key: "/user/album",
|
key: "/user/album",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "收藏的歌手",
|
label: t("nav.userChildren.artist"),
|
||||||
key: "/user/artists",
|
key: "/user/artists",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "音乐云盘",
|
label: t("nav.userChildren.cloud"),
|
||||||
key: "/user/cloud",
|
key: "/user/cloud",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
label: "登录账号",
|
label: t("nav.userChildren.login"),
|
||||||
key: "/login",
|
key: "/login",
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
);
|
};
|
||||||
const dropdownOptions = ref([
|
const changeDropdownOptions = () => {
|
||||||
|
dropdownOptions.value = [
|
||||||
{
|
{
|
||||||
key: "header",
|
key: "header",
|
||||||
type: "render",
|
type: "render",
|
||||||
@@ -217,7 +260,9 @@ const dropdownOptions = ref([
|
|||||||
{ style: { transform: "translateX(2px)" } },
|
{ style: { transform: "translateX(2px)" } },
|
||||||
{
|
{
|
||||||
default: () =>
|
default: () =>
|
||||||
setting.getSiteTheme == "light" ? "深色模式" : "浅色模式",
|
setting.getSiteTheme == "light"
|
||||||
|
? t("nav.avatar.dark")
|
||||||
|
: t("nav.avatar.light"),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -234,30 +279,14 @@ const dropdownOptions = ref([
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "播放历史",
|
label: t("nav.avatar.history"),
|
||||||
key: "history",
|
key: "history",
|
||||||
icon: () => {
|
icon: renderIcon(h(History)),
|
||||||
return h(
|
|
||||||
NIcon,
|
|
||||||
{ style: { transform: "translateX(2px)" } },
|
|
||||||
{
|
|
||||||
default: () => h(History),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "全局设置",
|
label: t("nav.avatar.setting"),
|
||||||
key: "setting",
|
key: "setting",
|
||||||
icon: () => {
|
icon: renderIcon(h(SettingTwo)),
|
||||||
return h(
|
|
||||||
NIcon,
|
|
||||||
{ style: { transform: "translateX(2px)" } },
|
|
||||||
{
|
|
||||||
default: () => h(SettingTwo),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: () => {
|
label: () => {
|
||||||
@@ -265,7 +294,8 @@ const dropdownOptions = ref([
|
|||||||
NText,
|
NText,
|
||||||
{ style: { transform: "translateX(2px)" } },
|
{ style: { transform: "translateX(2px)" } },
|
||||||
{
|
{
|
||||||
default: () => (user.userLogin ? "退出登录" : "登录账号"),
|
default: () =>
|
||||||
|
user.userLogin ? t("nav.avatar.logout") : t("nav.avatar.login"),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -281,21 +311,36 @@ const dropdownOptions = ref([
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "关于本站",
|
label: t("nav.avatar.about"),
|
||||||
key: "about",
|
key: "about",
|
||||||
icon: () => {
|
icon: renderIcon(h(Info)),
|
||||||
return h(
|
|
||||||
NIcon,
|
|
||||||
{ style: { transform: "translateX(2px)" } },
|
|
||||||
{
|
|
||||||
default: () => h(Info),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
];
|
||||||
]);
|
};
|
||||||
|
|
||||||
// 下拉框事件
|
// 移动端菜单
|
||||||
|
const mbMenuOptions = ref([]);
|
||||||
|
const changeMbMenuOptions = () => {
|
||||||
|
mbMenuOptions.value = [
|
||||||
|
{
|
||||||
|
label: t("nav.home"),
|
||||||
|
key: "/",
|
||||||
|
icon: renderIcon(h(HomeTwo)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("nav.discover"),
|
||||||
|
key: "/discover",
|
||||||
|
icon: renderIcon(h(FindOne)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("nav.user"),
|
||||||
|
key: "/user",
|
||||||
|
icon: renderIcon(h(Me)),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下拉框点击事件
|
||||||
const menuSelect = (key) => {
|
const menuSelect = (key) => {
|
||||||
router.push(key);
|
router.push(key);
|
||||||
};
|
};
|
||||||
@@ -307,6 +352,7 @@ const dropdownSelect = (key) => {
|
|||||||
setting.getSiteTheme == "light"
|
setting.getSiteTheme == "light"
|
||||||
? setting.setSiteTheme("dark")
|
? setting.setSiteTheme("dark")
|
||||||
: setting.setSiteTheme("light");
|
: setting.setSiteTheme("light");
|
||||||
|
setting.themeAuto = false;
|
||||||
break;
|
break;
|
||||||
// 播放历史
|
// 播放历史
|
||||||
case "history":
|
case "history":
|
||||||
@@ -322,13 +368,17 @@ const dropdownSelect = (key) => {
|
|||||||
// 退出登录
|
// 退出登录
|
||||||
$dialog.warning({
|
$dialog.warning({
|
||||||
class: "s-dialog",
|
class: "s-dialog",
|
||||||
title: "退出登录",
|
title: t("nav.avatar.logout"),
|
||||||
content: "确认退出当前用户登录?",
|
content: t("nav.avatar.tip"),
|
||||||
positiveText: "退出登录",
|
positiveText: t("nav.avatar.logout"),
|
||||||
negativeText: "取消",
|
negativeText: t("general.dialog.cancel"),
|
||||||
onPositiveClick: () => {
|
onPositiveClick: () => {
|
||||||
user.userLogOut();
|
user.userLogOut();
|
||||||
$message.success("已退出登录");
|
$message.success(t("nav.avatar.success"));
|
||||||
|
// 刷新页面
|
||||||
|
timeOut.value = setTimeout(() => {
|
||||||
|
document.location.reload();
|
||||||
|
}, 1000);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -344,6 +394,36 @@ const dropdownSelect = (key) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 监听登录状态变化
|
||||||
|
watch(
|
||||||
|
() => user.userLogin,
|
||||||
|
(val) => {
|
||||||
|
changeUserOptions(val);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听语言变化
|
||||||
|
watch(
|
||||||
|
() => setting.language,
|
||||||
|
() => {
|
||||||
|
changeDiscoverOptions();
|
||||||
|
changeMbMenuOptions();
|
||||||
|
changeDropdownOptions();
|
||||||
|
changeUserOptions(user.userLogin);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
changeDiscoverOptions();
|
||||||
|
changeMbMenuOptions();
|
||||||
|
changeDropdownOptions();
|
||||||
|
changeUserOptions(user.userLogin);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
clearTimeout(timeOut.value);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -356,6 +436,17 @@ nav {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
max-width: 1400px;
|
max-width: 1400px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.5s ease;
|
||||||
|
}
|
||||||
|
.fade-enter-active {
|
||||||
|
transition-delay: 0.5s;
|
||||||
|
}
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
.left {
|
.left {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
@@ -369,28 +460,36 @@ nav {
|
|||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
|
transition: all 0.3s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.controls {
|
.controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@media (max-width: 520px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.n-icon {
|
.n-icon {
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
@media (min-width: 640px) {
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--n-border-color);
|
background-color: var(--n-border-color);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
@@ -416,8 +515,8 @@ nav {
|
|||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $mainColor;
|
background-color: var(--main-color);
|
||||||
color: var(--n-color);
|
color: rgba(255, 255, 255, 0.9);
|
||||||
}
|
}
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
@@ -425,8 +524,8 @@ nav {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.router-link-active {
|
.router-link-active {
|
||||||
background-color: $mainColor;
|
background-color: var(--main-color);
|
||||||
color: var(--n-color);
|
color: rgba(255, 255, 255, 0.9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.right {
|
.right {
|
||||||
@@ -436,6 +535,10 @@ nav {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
}
|
||||||
.avatar {
|
.avatar {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
min-width: 30px;
|
min-width: 30px;
|
||||||
@@ -444,6 +547,13 @@ nav {
|
|||||||
box-shadow: 0 4px 12px -2px rgb(0 0 0 / 10%);
|
box-shadow: 0 4px 12px -2px rgb(0 0 0 / 10%);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.mb-menu {
|
||||||
|
margin-left: 12px;
|
||||||
|
display: none;
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -10,12 +10,17 @@
|
|||||||
:show-size-picker="showSizePicker"
|
:show-size-picker="showSizePicker"
|
||||||
:show-quick-jumper="showQuickJumper"
|
:show-quick-jumper="showQuickJumper"
|
||||||
>
|
>
|
||||||
<template #prefix="{ itemCount }"> 共 {{ itemCount }} 项 </template>
|
<template #prefix="{ itemCount }">
|
||||||
<template #goto> 前往 </template>
|
{{ $t("general.name.itemCount", { size: itemCount }) }}
|
||||||
|
</template>
|
||||||
|
<template #goto> {{ $t("general.name.goto") }} </template>
|
||||||
</n-pagination>
|
</n-pagination>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
// 数据总量
|
// 数据总量
|
||||||
totalCount: {
|
totalCount: {
|
||||||
@@ -47,21 +52,25 @@ const currentPageNumber = ref(1);
|
|||||||
// 自定义每页数量
|
// 自定义每页数量
|
||||||
const pageSizes = ref([
|
const pageSizes = ref([
|
||||||
{
|
{
|
||||||
label: "10条/页",
|
label: t("general.name.pageSizes", { num: 10 }),
|
||||||
value: 10,
|
value: 10,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "20条/页",
|
label: t("general.name.pageSizes", { num: 20 }),
|
||||||
value: 20,
|
value: 20,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "30条/页",
|
label: t("general.name.pageSizes", { num: 30 }),
|
||||||
value: 30,
|
value: 30,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "50条/页",
|
label: t("general.name.pageSizes", { num: 50 }),
|
||||||
value: 50,
|
value: 50,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: t("general.name.pageSizes", { num: 100 }),
|
||||||
|
value: 100,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 每页个数数据变化
|
// 每页个数数据变化
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="paalbum">
|
<div class="paalbum">
|
||||||
<n-h3 class="title" prefix="bar">
|
<n-h3 class="title" prefix="bar">
|
||||||
新碟上架
|
{{ $t("home.title.newAlbum") }}
|
||||||
<span class="more" @click="router.push('/new-album?page=1')">更多</span>
|
<span class="more" @click="router.push('/new-album?page=1')">
|
||||||
|
{{ $t("home.title.more") }}
|
||||||
|
</span>
|
||||||
</n-h3>
|
</n-h3>
|
||||||
<CoverLists
|
<CoverLists
|
||||||
listType="album"
|
listType="album"
|
||||||
@@ -16,7 +18,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { getNewAlbum } from "@/api/home";
|
import { getNewAlbum } from "@/api/home";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { getLongTime } from "@/utils/timeTools.js";
|
import { getLongTime } from "@/utils/timeTools";
|
||||||
import CoverLists from "@/components/DataList/CoverLists.vue";
|
import CoverLists from "@/components/DataList/CoverLists.vue";
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -66,7 +68,7 @@ onMounted(() => {
|
|||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="paartists">
|
<div class="paartists">
|
||||||
<n-h3 class="title" prefix="bar">
|
<n-h3 class="title" prefix="bar">
|
||||||
歌手推荐
|
{{ $t("home.title.artists") }}
|
||||||
<n-tabs
|
<n-tabs
|
||||||
class="tab"
|
class="tab"
|
||||||
:default-value="-1"
|
:default-value="-1"
|
||||||
size="small"
|
size="small"
|
||||||
@update:value="tabChange"
|
@update:value="tabChange"
|
||||||
>
|
>
|
||||||
<n-tab :name="-1"> 全部 </n-tab>
|
<n-tab :name="-1"> {{ $t("general.type.all") }} </n-tab>
|
||||||
<n-tab :name="7"> 华语 </n-tab>
|
<n-tab :name="7"> {{ $t("general.type.china") }} </n-tab>
|
||||||
<n-tab :name="96"> 欧美 </n-tab>
|
<n-tab :name="96"> {{ $t("general.type.western") }} </n-tab>
|
||||||
<n-tab :name="8"> 日本 </n-tab>
|
<n-tab :name="8"> {{ $t("general.type.japan") }} </n-tab>
|
||||||
<n-tab :name="16"> 韩国 </n-tab>
|
<n-tab :name="16"> {{ $t("general.type.korea") }} </n-tab>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
<span class="more" @click="router.push('/discover/artists?page=1')">
|
<span class="more" @click="router.push('/discover/artists?page=1')">
|
||||||
更多
|
{{ $t("home.title.more") }}
|
||||||
</span>
|
</span>
|
||||||
</n-h3>
|
</n-h3>
|
||||||
<ArtistLists :listData="artistsData" :gridCollapsed="true" />
|
<ArtistLists :listData="artistsData" :gridCollapsed="true" />
|
||||||
@@ -92,7 +92,7 @@ onMounted(() => {
|
|||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="padailysongs" @click="router.push('/dailySongs')">
|
<div
|
||||||
<img
|
class="padailysongs"
|
||||||
class="pic"
|
:style="`background-image: url(${cardImage})`"
|
||||||
:src="
|
@click="router.push('/dailySongs')"
|
||||||
music.getDailySongs[0]
|
>
|
||||||
? music.getDailySongs[
|
<div class="gray" />
|
||||||
Math.floor(Math.random() * music.getDailySongs.length)
|
|
||||||
].album.picUrl.replace(/^http:/, 'https:') + '?param=800y800'
|
|
||||||
: '/images/pic/pic.jpg'
|
|
||||||
"
|
|
||||||
alt="pic"
|
|
||||||
/>
|
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<span class="title">每日推荐</span>
|
<div class="date">
|
||||||
<span class="tip">根据你的音乐口味生成 · 每天 6:00 更新</span>
|
<n-icon class="calendar" :component="CalendarTodayFilled" size="40" />
|
||||||
|
<n-text class="num" v-html="new Date().getDate()" />
|
||||||
|
</div>
|
||||||
|
<div class="desc">
|
||||||
|
<n-text class="title">{{ $t("home.modules.dailySongs.title") }}</n-text>
|
||||||
|
<n-text class="tip">{{ $t("home.modules.dailySongs.subtitle") }}</n-text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<n-avatar
|
||||||
|
class="cover"
|
||||||
|
:src="cardImage"
|
||||||
|
fallback-src="/images/pic/default.png"
|
||||||
|
/>
|
||||||
|
<n-icon
|
||||||
|
class="play"
|
||||||
|
:component="PlayCircleFilled"
|
||||||
|
size="50"
|
||||||
|
@click.stop="playThisSong"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -22,24 +35,71 @@
|
|||||||
import { getDailySongs } from "@/api/home";
|
import { getDailySongs } from "@/api/home";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { musicStore, userStore } from "@/store";
|
import { musicStore, userStore } from "@/store";
|
||||||
|
import { PlayCircleFilled, CalendarTodayFilled } from "@vicons/material";
|
||||||
|
|
||||||
const music = musicStore();
|
const music = musicStore();
|
||||||
const user = userStore();
|
const user = userStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 卡片背景
|
||||||
|
const cardImage = ref(null);
|
||||||
|
|
||||||
|
// 随机数
|
||||||
|
const randomNumber = Math.floor(Math.random() * music.getDailySongs.length);
|
||||||
|
|
||||||
|
// 生成卡片背景
|
||||||
|
const getCardImage = () => {
|
||||||
|
if (user.userLogin && music.getDailySongs[0]) {
|
||||||
|
cardImage.value =
|
||||||
|
music.getDailySongs[randomNumber]?.album.picUrl.replace(
|
||||||
|
/^http:/,
|
||||||
|
"https:"
|
||||||
|
) + "?param=100y100";
|
||||||
|
} else {
|
||||||
|
cardImage.value = "/images/pic/pic.jpg";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 获取每日推荐数据
|
// 获取每日推荐数据
|
||||||
const getDailySongsData = () => {
|
const getDailySongsData = () => {
|
||||||
|
getCardImage();
|
||||||
|
if (music.getDailySongs.length === 0 && user.userLogin) {
|
||||||
getDailySongs().then((res) => {
|
getDailySongs().then((res) => {
|
||||||
if (res.data.dailySongs) {
|
if (res.data.dailySongs) {
|
||||||
music.setDailySongs(res.data.dailySongs);
|
music.setDailySongs(res.data.dailySongs);
|
||||||
|
getCardImage();
|
||||||
} else {
|
} else {
|
||||||
$message.error("每日推荐获取失败");
|
$message.error("每日推荐获取失败");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从当前歌曲开始播放
|
||||||
|
const playThisSong = () => {
|
||||||
|
if (user.userLogin) {
|
||||||
|
if (music.getDailySongs.length !== 0) {
|
||||||
|
// 正在播放的歌曲id
|
||||||
|
const songId = music.getPlaySongData?.id;
|
||||||
|
// 查找是否在日推中
|
||||||
|
const isHas = music.getDailySongs.findIndex((o) => o.id === songId);
|
||||||
|
console.log(isHas);
|
||||||
|
music.setPersonalFmMode(false);
|
||||||
|
music.setPlayState(true);
|
||||||
|
if (isHas === -1) {
|
||||||
|
music.setPlaylists(music.getDailySongs);
|
||||||
|
music.addSongToPlaylists(music.getDailySongs[randomNumber]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$message.error("每日推荐获取失败,请刷新后重试");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$message.error("请登录账号后使用");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (music.getDailySongs.length === 0 && user.userLogin) getDailySongsData();
|
getDailySongsData();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -47,66 +107,130 @@ onMounted(() => {
|
|||||||
.padailysongs {
|
.padailysongs {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
justify-content: space-evenly;
|
align-items: center;
|
||||||
padding: 20px;
|
justify-content: space-between;
|
||||||
height: 200px;
|
height: 110px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
padding: 0 28px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
|
margin-bottom: 20px;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 120% 120%;
|
||||||
|
background-position: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
.pic {
|
.control {
|
||||||
transform: translateY(calc(-50% + 13vh)) scale(1.2);
|
.cover {
|
||||||
filter: brightness(50%);
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.play {
|
||||||
|
transform: rotate(0);
|
||||||
|
opacity: 1;
|
||||||
|
right: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:active {
|
|
||||||
transform: scale(0.98);
|
|
||||||
}
|
}
|
||||||
.pic {
|
.gray {
|
||||||
transition: all 0.3s;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
transform: translateY(calc(-50% + 13vh));
|
|
||||||
filter: brightness(70%);
|
|
||||||
z-index: -1;
|
|
||||||
@media (min-width: 750px) and (max-width: 1056px) {
|
|
||||||
transform: none;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
background-color: #00000040;
|
||||||
@media (max-width: 500px) {
|
-webkit-backdrop-filter: blur(20px);
|
||||||
transform: none;
|
backdrop-filter: blur(20px);
|
||||||
}
|
z-index: -1;
|
||||||
}
|
}
|
||||||
.text {
|
.text {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
.date {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-shadow: 0px 0px 8px #00000082;
|
margin-right: 18px;
|
||||||
.title {
|
.num {
|
||||||
font-size: 34px;
|
margin-top: 8px;
|
||||||
|
position: absolute;
|
||||||
|
font-size: 14px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: 8px;
|
color: #fff;
|
||||||
letter-spacing: 2px;
|
|
||||||
}
|
|
||||||
@media (min-width: 750px) and (max-width: 1056px) {
|
|
||||||
.tip {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.desc {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
span {
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
@media (max-width: 500px) {
|
|
||||||
height: 160px;
|
|
||||||
.text {
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 30px;
|
font-size: 20px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 2px;
|
||||||
|
@media (max-width: 1020px) {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tip {
|
||||||
|
color: #e9e9e9;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.control {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-left: 12px;
|
||||||
|
.cover {
|
||||||
|
position: relative;
|
||||||
|
background-color: transparent;
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
z-index: 0;
|
||||||
|
overflow: inherit;
|
||||||
|
:deep(img) {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
background-color: #fff;
|
||||||
|
opacity: 0.6;
|
||||||
|
transform: scale(0.85) translateX(11px);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
transform: scale(0.7) translateX(27px);
|
||||||
|
opacity: 0.4;
|
||||||
|
z-index: -2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.play {
|
||||||
|
color: #fff;
|
||||||
|
opacity: 0;
|
||||||
|
right: -70px;
|
||||||
|
position: absolute;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
transition: all 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
169
src/components/Personalized/PaLikeSongs.vue
Normal file
169
src/components/Personalized/PaLikeSongs.vue
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 喜欢的音乐 -->
|
||||||
|
<div
|
||||||
|
class="like-song"
|
||||||
|
:style="`background-image: url(${cardImage})`"
|
||||||
|
@click="toLikeSongs"
|
||||||
|
>
|
||||||
|
<div class="gray" />
|
||||||
|
<div class="left">
|
||||||
|
<n-icon class="icon" :component="CollectionRecords" size="30" />
|
||||||
|
<div class="title">
|
||||||
|
<n-text class="name">{{ $t("home.modules.likeSong.title") }}</n-text>
|
||||||
|
<n-text class="tip">{{ $t("home.modules.likeSong.subtitle") }}</n-text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<n-icon class="icon" :component="Right" size="20" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { userStore } from "@/store";
|
||||||
|
import { CollectionRecords, Right } from "@icon-park/vue-next";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const user = userStore();
|
||||||
|
|
||||||
|
// 卡片背景
|
||||||
|
const cardImage = ref(null);
|
||||||
|
|
||||||
|
// 生成卡片背景
|
||||||
|
const getCardImage = (index) => {
|
||||||
|
if (user.userLogin && user.getUserPlayLists.own[0]) {
|
||||||
|
const num =
|
||||||
|
index ?? Math.floor(Math.random() * user.getUserPlayLists.own.length);
|
||||||
|
cardImage.value =
|
||||||
|
user.getUserPlayLists.own[num]?.cover.replace(/^http:/, "https:") +
|
||||||
|
"?param=100y100";
|
||||||
|
} else {
|
||||||
|
cardImage.value = "/images/pic/pic.jpg";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 跳转喜欢的音乐
|
||||||
|
const toLikeSongs = () => {
|
||||||
|
if (user.userLogin) {
|
||||||
|
const id = user.getUserPlayLists.own[0]?.id;
|
||||||
|
if (id) {
|
||||||
|
router.push(`/playlist?id=${id}&page=1`);
|
||||||
|
} else {
|
||||||
|
console.error("发生错误");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$message.error("请登录账号后使用");
|
||||||
|
router.push("/login");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getCardImage();
|
||||||
|
if (
|
||||||
|
user.userLogin &&
|
||||||
|
!user.getUserPlayLists.has &&
|
||||||
|
!user.getUserPlayLists.isLoading
|
||||||
|
) {
|
||||||
|
user.setUserPlayLists(() => {
|
||||||
|
getCardImage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.like-song {
|
||||||
|
position: relative;
|
||||||
|
color: #fff;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0 18px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 120% 120%;
|
||||||
|
background-position: center;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
&:hover {
|
||||||
|
.left {
|
||||||
|
.title {
|
||||||
|
.name {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-50px);
|
||||||
|
}
|
||||||
|
.tip {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
.icon {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.gray {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #00000040;
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
.left {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.icon {
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
.name {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 18px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
@media (max-width: 1020px) {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tip {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
color: #fff;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(50px);
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
display: flex;
|
||||||
|
.icon {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-8px);
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="papersonalfm"
|
class="papersonalfm"
|
||||||
v-if="music.getPersonalFmData.id"
|
v-if="music.getPersonalFmData?.id"
|
||||||
:style="
|
:style="`background-image: url(${music.getPersonalFmData.album.picUrl.replace(
|
||||||
'background-image: url(' +
|
/^http:/,
|
||||||
music.getPersonalFmData.album.picUrl.replace(/^http:/, 'https:') +
|
'https:'
|
||||||
'?param=300y300)'
|
)}?param=300y300)`"
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<div class="gray" />
|
<div class="gray" />
|
||||||
<img
|
<img
|
||||||
@@ -58,9 +57,11 @@
|
|||||||
<div class="radio">
|
<div class="radio">
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<n-icon size="20" :component="RadioFilled" />
|
<n-icon size="20" :component="RadioFilled" />
|
||||||
<span>私人FM</span>
|
<span>{{ $t("home.modules.papersonalfm.title") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="tip" v-if="!user.userLogin">未登录模式</span>
|
<span class="tip" v-if="!user.userLogin">
|
||||||
|
{{ $t("home.modules.papersonalfm.subtitle") }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,7 +111,7 @@ onMounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
height: 200px;
|
height: 100%;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -127,8 +128,8 @@ onMounted(() => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: #00000040;
|
background-color: #00000040;
|
||||||
-webkit-backdrop-filter: blur(80px);
|
-webkit-backdrop-filter: blur(40px);
|
||||||
backdrop-filter: blur(80px);
|
backdrop-filter: blur(40px);
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
.pic {
|
.pic {
|
||||||
@@ -172,8 +173,7 @@ onMounted(() => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
.state {
|
.state {
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
transform: scale(1);
|
transition: transform 0.3s;
|
||||||
transition: all 0.3s;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
@@ -190,9 +190,11 @@ onMounted(() => {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
@media (min-width: 640px) {
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #ffffff30;
|
background-color: #ffffff30;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.9);
|
transform: scale(0.9);
|
||||||
}
|
}
|
||||||
@@ -234,7 +236,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 500px) {
|
@media (max-width: 1020px) {
|
||||||
.pic {
|
.pic {
|
||||||
height: 96px;
|
height: 96px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="paplaylists">
|
<div class="paplaylists">
|
||||||
<n-h3 class="title" prefix="bar">
|
<n-h3 class="title" prefix="bar">
|
||||||
推荐歌单
|
{{ $t("home.title.playlists") }}
|
||||||
<span class="more" @click="router.push('/discover/playlists?page=1')">
|
<span class="more" @click="router.push('/discover/playlists?page=1')">
|
||||||
更多
|
{{ $t("home.title.more") }}
|
||||||
</span>
|
</span>
|
||||||
</n-h3>
|
</n-h3>
|
||||||
<CoverLists
|
<CoverLists
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { getPersonalized } from "@/api/home";
|
import { getPersonalized } from "@/api/home";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { formatNumber } from "@/utils/timeTools.js";
|
import { formatNumber } from "@/utils/timeTools";
|
||||||
import CoverLists from "@/components/DataList/CoverLists.vue";
|
import CoverLists from "@/components/DataList/CoverLists.vue";
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ onMounted(() => {
|
|||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
123
src/components/Personalized/PaRadar.vue
Normal file
123
src/components/Personalized/PaRadar.vue
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 私人雷达 -->
|
||||||
|
<div class="radar" @click="router.push(`/playlist?id=${radarId}&page=1`)">
|
||||||
|
<div class="gray" />
|
||||||
|
<div class="left">
|
||||||
|
<n-icon class="icon" :component="RadarThree" size="30" />
|
||||||
|
<div class="title">
|
||||||
|
<n-text class="name">{{ $t("home.modules.radar.title") }}</n-text>
|
||||||
|
<n-text class="tip">{{ $t("home.modules.radar.subtitle") }}</n-text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<n-icon class="icon" :component="Right" size="20" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { RadarThree, Right } from "@icon-park/vue-next";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 私人雷达歌单
|
||||||
|
const radarId = ref(3136952023);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.radar {
|
||||||
|
position: relative;
|
||||||
|
color: #fff;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0 18px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-image: url("/images/pic/radar.jpg");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 120% 120%;
|
||||||
|
background-position: center;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
&:hover {
|
||||||
|
.left {
|
||||||
|
.title {
|
||||||
|
.name {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-50px);
|
||||||
|
}
|
||||||
|
.tip {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
.icon {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.gray {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #00000010;
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
.left {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.icon {
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
.name {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 18px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
@media (max-width: 1020px) {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tip {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
color: #fff;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(50px);
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
display: flex;
|
||||||
|
.icon {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-8px);
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,30 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<Transition name="up">
|
<Transition name="up">
|
||||||
<div
|
<div
|
||||||
v-show="music.showBigPlayer"
|
v-if="music.showBigPlayer"
|
||||||
class="bplayer"
|
class="bplayer"
|
||||||
:style="
|
:style="[
|
||||||
music.getPlaySongData
|
music.getPlaySongData && setting.backgroundImageShow === 'blur'
|
||||||
? 'background-image: url(' +
|
? 'background-image: url(' +
|
||||||
music.getPlaySongData.album.picUrl.replace(/^http:/, 'https:') +
|
music.getPlaySongData.album.picUrl.replace(/^http:/, 'https:') +
|
||||||
'?param=50y50)'
|
'?param=50y50)'
|
||||||
: ''
|
: '',
|
||||||
"
|
`backgroundColor: ${site.songPicColor}`,
|
||||||
|
]"
|
||||||
>
|
>
|
||||||
<div class="gray" />
|
<div
|
||||||
|
:class="setting.backgroundImageShow === 'blur' ? 'gray blur' : 'gray'"
|
||||||
|
/>
|
||||||
<div class="icon-menu">
|
<div class="icon-menu">
|
||||||
<div class="menu-left">
|
<div class="menu-left">
|
||||||
<div class="icon">
|
<div v-if="setting.showLyricSetting" class="icon">
|
||||||
<n-icon
|
<n-icon
|
||||||
class="setting"
|
class="setting"
|
||||||
size="30"
|
size="30"
|
||||||
:component="SettingsRound"
|
:component="SettingsRound"
|
||||||
@click="
|
@click="LyricSettingRef.openLyricSetting()"
|
||||||
() => {
|
|
||||||
music.setBigPlayerState(false);
|
|
||||||
router.push('/setting/player');
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +43,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
:class="
|
:class="
|
||||||
music.getPlaySongLyric.lrc[0] && music.getPlaySongLyric.lrc.length > 4
|
music.getPlaySongLyric.lrc[0] && music.getPlaySongLyric.lrc.length > 4
|
||||||
@@ -56,7 +53,7 @@
|
|||||||
<!-- 提示文本 -->
|
<!-- 提示文本 -->
|
||||||
<Transition name="lrc">
|
<Transition name="lrc">
|
||||||
<div class="tip" v-show="lrcMouseStatus">
|
<div class="tip" v-show="lrcMouseStatus">
|
||||||
<n-text>点击选中的歌词以调整播放进度</n-text>
|
<n-text>{{ $t("other.lrcClicks") }}</n-text>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
<div class="left">
|
<div class="left">
|
||||||
@@ -81,7 +78,7 @@
|
|||||||
<span>{{
|
<span>{{
|
||||||
music.getPlaySongData
|
music.getPlaySongData
|
||||||
? music.getPlaySongData.name
|
? music.getPlaySongData.name
|
||||||
: "暂无歌曲"
|
: $t("other.noSong")
|
||||||
}}</span>
|
}}</span>
|
||||||
<span
|
<span
|
||||||
v-if="music.getPlaySongData && music.getPlaySongData.alia"
|
v-if="music.getPlaySongData && music.getPlaySongData.alia"
|
||||||
@@ -118,9 +115,9 @@
|
|||||||
>
|
>
|
||||||
<n-icon
|
<n-icon
|
||||||
v-if="music.getPlaySongTransl"
|
v-if="music.getPlaySongTransl"
|
||||||
:class="setting.getShowTransl ? 'open' : ''"
|
:class="setting.showTransl ? 'open' : ''"
|
||||||
:component="GTranslateFilled"
|
:component="GTranslateFilled"
|
||||||
@click="setting.setShowTransl(!setting.getShowTransl)"
|
@click="setting.setShowTransl(!setting.showTransl)"
|
||||||
/>
|
/>
|
||||||
<n-icon
|
<n-icon
|
||||||
class="open"
|
class="open"
|
||||||
@@ -132,9 +129,10 @@
|
|||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="canvas">
|
<!-- 音乐频谱 -->
|
||||||
<canvas v-if="setting.musicFrequency" class="avBars" ref="avBars" />
|
<!-- <Spectrum v-if="setting.musicFrequency" /> -->
|
||||||
</div>
|
<!-- 歌词设置 -->
|
||||||
|
<LyricSetting ref="LyricSettingRef" />
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</template>
|
</template>
|
||||||
@@ -148,28 +146,31 @@ import {
|
|||||||
FullscreenExitRound,
|
FullscreenExitRound,
|
||||||
SettingsRound,
|
SettingsRound,
|
||||||
} from "@vicons/material";
|
} from "@vicons/material";
|
||||||
import { musicStore, settingStore } from "@/store";
|
import { musicStore, settingStore, siteStore } from "@/store";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import MusicFrequency from "@/utils/MusicFrequency.js";
|
import { setSeek } from "@/utils/Player";
|
||||||
import PlayerRecord from "./PlayerRecord.vue";
|
import PlayerRecord from "./PlayerRecord.vue";
|
||||||
import PlayerCover from "./PlayerCover.vue";
|
import PlayerCover from "./PlayerCover.vue";
|
||||||
import RollingLyrics from "./RollingLyrics.vue";
|
import RollingLyrics from "./RollingLyrics.vue";
|
||||||
|
// import Spectrum from "./Spectrum.vue";
|
||||||
|
import LyricSetting from "@/components/DataModal/LyricSetting.vue";
|
||||||
import screenfull from "screenfull";
|
import screenfull from "screenfull";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const music = musicStore();
|
const music = musicStore();
|
||||||
|
const site = siteStore();
|
||||||
const setting = settingStore();
|
const setting = settingStore();
|
||||||
|
|
||||||
// 工具栏显隐
|
// 工具栏显隐
|
||||||
const menuShow = ref(false);
|
const menuShow = ref(false);
|
||||||
|
|
||||||
// 音乐频谱
|
// 歌词设置弹窗
|
||||||
const avBars = ref(null);
|
const LyricSettingRef = ref(null);
|
||||||
const musicFrequency = ref(null);
|
|
||||||
|
|
||||||
// 歌词文本点击事件
|
// 歌词文本点击事件
|
||||||
const lrcTextClick = (time) => {
|
const lrcTextClick = (time) => {
|
||||||
if ($player) $player.currentTime = time;
|
if (typeof $player !== "undefined") setSeek($player, time);
|
||||||
|
music.setPlayState(true);
|
||||||
lrcMouseStatus.value = false;
|
lrcMouseStatus.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -221,7 +222,7 @@ const lyricsScroll = (index) => {
|
|||||||
const scrollDistance =
|
const scrollDistance =
|
||||||
el.offsetTop -
|
el.offsetTop -
|
||||||
container.offsetTop -
|
container.offsetTop -
|
||||||
(type === "center" ? containerHeight / 2 - el.offsetHeight / 2 : 100);
|
(type === "center" ? containerHeight / 2 - el.offsetHeight / 2 : 80);
|
||||||
container.scrollTo({
|
container.scrollTo({
|
||||||
top: scrollDistance,
|
top: scrollDistance,
|
||||||
behavior: "smooth",
|
behavior: "smooth",
|
||||||
@@ -229,21 +230,22 @@ const lyricsScroll = (index) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
// 改变 PWA 应用标题栏颜色
|
||||||
nextTick(() => {
|
const changePwaColor = () => {
|
||||||
if (setting.musicFrequency) {
|
const themeColorMeta = document.querySelector('meta[name="theme-color"]');
|
||||||
$player.crossOrigin = "anonymous";
|
if (music.showBigPlayer) {
|
||||||
musicFrequency.value = new MusicFrequency(
|
themeColorMeta.setAttribute("content", site.songPicColor);
|
||||||
avBars.value,
|
} else {
|
||||||
$player,
|
if (setting.getSiteTheme === "light") {
|
||||||
null,
|
themeColorMeta.setAttribute("content", "#ffffff");
|
||||||
50,
|
} else if (setting.getSiteTheme === "dark") {
|
||||||
null,
|
themeColorMeta.setAttribute("content", "#18181c");
|
||||||
null,
|
|
||||||
5
|
|
||||||
);
|
|
||||||
musicFrequency.value.drawSpectrum();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick().then(() => {
|
||||||
// 滚动歌词
|
// 滚动歌词
|
||||||
lyricsScroll(music.getPlaySongLyricIndex);
|
lyricsScroll(music.getPlaySongLyricIndex);
|
||||||
});
|
});
|
||||||
@@ -257,11 +259,12 @@ onBeforeUnmount(() => {
|
|||||||
watch(
|
watch(
|
||||||
() => music.showBigPlayer,
|
() => music.showBigPlayer,
|
||||||
(val) => {
|
(val) => {
|
||||||
|
changePwaColor();
|
||||||
if (val) {
|
if (val) {
|
||||||
console.log("开启播放器", music.getPlaySongLyricIndex);
|
console.log("开启播放器", music.getPlaySongLyricIndex);
|
||||||
nextTick(() => {
|
nextTick().then(() => {
|
||||||
lyricsScroll(music.getPlaySongLyricIndex);
|
|
||||||
music.showPlayList = false;
|
music.showPlayList = false;
|
||||||
|
lyricsScroll(music.getPlaySongLyricIndex);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,6 +275,12 @@ watch(
|
|||||||
() => music.getPlaySongLyricIndex,
|
() => music.getPlaySongLyricIndex,
|
||||||
(val) => lyricsScroll(val)
|
(val) => lyricsScroll(val)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 监听主题色改变
|
||||||
|
watch(
|
||||||
|
() => site.songPicColor,
|
||||||
|
() => changePwaColor()
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -324,10 +333,13 @@ watch(
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: #00000060;
|
background-color: #00000030;
|
||||||
-webkit-backdrop-filter: blur(80px);
|
-webkit-backdrop-filter: blur(80px);
|
||||||
backdrop-filter: blur(80px);
|
backdrop-filter: blur(80px);
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
|
&.blur {
|
||||||
|
background-color: #00000060;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.icon-menu {
|
.icon-menu {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
@@ -378,38 +390,6 @@ watch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
.close,
|
|
||||||
.screenfull,
|
|
||||||
.setting {
|
|
||||||
position: absolute;
|
|
||||||
top: 24px;
|
|
||||||
right: 24px;
|
|
||||||
opacity: 0.3;
|
|
||||||
color: #fff;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 8px;
|
|
||||||
transition: all 0.3s;
|
|
||||||
z-index: 2;
|
|
||||||
&:hover {
|
|
||||||
background-color: #ffffff20;
|
|
||||||
transform: scale(1.05);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
&:active {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.screenfull {
|
|
||||||
right: 80px;
|
|
||||||
padding: 2px;
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.setting {
|
|
||||||
left: 24px;
|
|
||||||
}*/
|
|
||||||
.all {
|
.all {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -422,6 +402,12 @@ watch(
|
|||||||
.left {
|
.left {
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
transform: translateX(25vh);
|
transform: translateX(25vh);
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
transform: translateX(22.2vh);
|
||||||
|
}
|
||||||
|
@media (min-width: 769px) and (max-width: 869px) {
|
||||||
|
transform: translateX(20.1vh);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.left {
|
.left {
|
||||||
@@ -494,7 +480,7 @@ watch(
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
.data {
|
.data {
|
||||||
padding: 0 20px;
|
padding: 0 3vh;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
.name {
|
.name {
|
||||||
font-size: 3vh;
|
font-size: 3vh;
|
||||||
@@ -523,7 +509,7 @@ watch(
|
|||||||
}
|
}
|
||||||
.menu {
|
.menu {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
padding: 0 20px;
|
padding: 0 3vh;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
:style="{ animationPlayState: music.getPlayState ? 'running' : 'paused' }"
|
:style="{ animationPlayState: music.getPlayState ? 'running' : 'paused' }"
|
||||||
v-if="
|
v-if="
|
||||||
remainingPoint <= 2 &&
|
remainingPoint <= 2 &&
|
||||||
totalDuration > 3 &&
|
totalDuration > 1 &&
|
||||||
music.getPlaySongLyric.lrc[0]
|
music.getPlaySongLyric.lrc[0]
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -11,13 +11,25 @@
|
|||||||
"
|
"
|
||||||
alt="cover"
|
alt="cover"
|
||||||
/>
|
/>
|
||||||
|
<img
|
||||||
|
class="shadow"
|
||||||
|
:src="
|
||||||
|
music.getPlaySongData
|
||||||
|
? music.getPlaySongData.album.picUrl.replace(/^http:/, 'https:') +
|
||||||
|
'?param=1024y1024'
|
||||||
|
: '/images/pic/default.png'
|
||||||
|
"
|
||||||
|
alt="shadow"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<div class="data">
|
<div class="data">
|
||||||
<div class="desc">
|
<div class="desc">
|
||||||
<span class="name text-hidden">
|
<span class="name text-hidden">
|
||||||
{{
|
{{
|
||||||
music.getPlaySongData ? music.getPlaySongData.name : "暂无歌曲"
|
music.getPlaySongData
|
||||||
|
? music.getPlaySongData.name
|
||||||
|
: $t("other.noSong")
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<div v-if="music.getPlaySongData" class="message">
|
<div v-if="music.getPlaySongData" class="message">
|
||||||
@@ -61,11 +73,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="time">
|
<div class="time">
|
||||||
<span>{{ music.getPlaySongTime.songTimePlayed }}</span>
|
<span>{{ music.getPlaySongTime.songTimePlayed }}</span>
|
||||||
<n-slider
|
<vue-slider
|
||||||
v-model:value="music.getPlaySongTime.barMoveDistance"
|
v-model="music.getPlaySongTime.barMoveDistance"
|
||||||
class="progress"
|
@drag-start="music.setPlayState(false)"
|
||||||
:step="0.01"
|
@drag-end="sliderDragEnd"
|
||||||
@update:value="songTimeSliderUpdate"
|
@click.stop="
|
||||||
|
songTimeSliderUpdate(music.getPlaySongTime.barMoveDistance)
|
||||||
|
"
|
||||||
|
:tooltip="'none'"
|
||||||
/>
|
/>
|
||||||
<span>{{ music.getPlaySongTime.songTimeDuration }}</span>
|
<span>{{ music.getPlaySongTime.songTimeDuration }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,6 +113,7 @@
|
|||||||
v-else
|
v-else
|
||||||
class="dislike"
|
class="dislike"
|
||||||
size="20"
|
size="20"
|
||||||
|
:style="!user.userLogin ? 'opacity: 0.2;pointer-events: none;' : null"
|
||||||
:component="ThumbDownRound"
|
:component="ThumbDownRound"
|
||||||
@click="music.setFmDislike(music.getPersonalFmData.id)"
|
@click="music.setFmDislike(music.getPersonalFmData.id)"
|
||||||
/>
|
/>
|
||||||
@@ -143,16 +159,25 @@ import {
|
|||||||
import { PlayCycle, PlayOnce, ShuffleOne } from "@icon-park/vue-next";
|
import { PlayCycle, PlayOnce, ShuffleOne } from "@icon-park/vue-next";
|
||||||
import { musicStore, userStore } from "@/store";
|
import { musicStore, userStore } from "@/store";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
import { setSeek } from "@/utils/Player";
|
||||||
import AllArtists from "@/components/DataList/AllArtists.vue";
|
import AllArtists from "@/components/DataList/AllArtists.vue";
|
||||||
|
import VueSlider from "vue-slider-component";
|
||||||
|
import "vue-slider-component/theme/default.css";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const music = musicStore();
|
const music = musicStore();
|
||||||
const user = userStore();
|
const user = userStore();
|
||||||
|
|
||||||
// 歌曲进度条更新
|
// 歌曲进度条更新
|
||||||
|
const sliderDragEnd = () => {
|
||||||
|
songTimeSliderUpdate(music.getPlaySongTime.barMoveDistance);
|
||||||
|
music.setPlayState(true);
|
||||||
|
};
|
||||||
const songTimeSliderUpdate = (val) => {
|
const songTimeSliderUpdate = (val) => {
|
||||||
if ($player && music.getPlaySongTime && music.getPlaySongTime.duration)
|
if (typeof $player !== "undefined" && music.getPlaySongTime?.duration) {
|
||||||
$player.currentTime = (music.getPlaySongTime.duration / 100) * val;
|
const currentTime = (music.getPlaySongTime.duration / 100) * val;
|
||||||
|
setSeek($player, currentTime);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 页面跳转
|
// 页面跳转
|
||||||
@@ -168,11 +193,11 @@ const routerJump = (url, query) => {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.cover {
|
.cover {
|
||||||
.pic {
|
.pic {
|
||||||
|
position: relative;
|
||||||
width: 50vh;
|
width: 50vh;
|
||||||
height: 50vh;
|
height: 50vh;
|
||||||
border-radius: 8px;
|
z-index: 1;
|
||||||
overflow: hidden;
|
// overflow: hidden;
|
||||||
box-shadow: 0 0 40px 14px rgb(0 0 0 / 20%);
|
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
width: 44vh;
|
width: 44vh;
|
||||||
height: 44vh;
|
height: 44vh;
|
||||||
@@ -184,6 +209,19 @@ const routerJump = (url, query) => {
|
|||||||
.album {
|
.album {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.shadow {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 12px;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
filter: blur(16px) opacity(0.6);
|
||||||
|
transform: scale(0.92, 0.96);
|
||||||
|
z-index: -1;
|
||||||
|
background-size: cover;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.control {
|
.control {
|
||||||
@@ -268,13 +306,26 @@ const routerJump = (url, query) => {
|
|||||||
span {
|
span {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
.progress {
|
.vue-slider {
|
||||||
margin: 0 12px;
|
margin: 0 10px;
|
||||||
--n-handle-size: 12px;
|
width: 100% !important;
|
||||||
--n-fill-color: #fff;
|
transform: translateY(-1px);
|
||||||
--n-fill-color-hover: #fff;
|
cursor: pointer;
|
||||||
--n-rail-color: #ffffff20;
|
:deep(.vue-slider-rail) {
|
||||||
--n-rail-color-hover: #ffffff30;
|
background-color: #ffffff20;
|
||||||
|
border-radius: 25px;
|
||||||
|
.vue-slider-process {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.vue-slider-dot {
|
||||||
|
width: 12px !important;
|
||||||
|
height: 12px !important;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.vue-slider-dot-handle-focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttons {
|
.buttons {
|
||||||
|
|||||||
327
src/components/Player/RollingLyrics-old.vue
Normal file
327
src/components/Player/RollingLyrics-old.vue
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 歌词滚动 -->
|
||||||
|
<div
|
||||||
|
v-if="music.getPlaySongLyric.lrc[0]"
|
||||||
|
:class="[
|
||||||
|
setting.playerStyle === 'cover' ? 'lrc-all cover' : 'lrc-all record',
|
||||||
|
setting.lyricsBlock === 'center' ? 'center' : 'top',
|
||||||
|
]"
|
||||||
|
:style="
|
||||||
|
setting.lyricsPosition === 'center'
|
||||||
|
? { textAlign: 'center', paddingRight: '0' }
|
||||||
|
: null
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="placeholder"
|
||||||
|
:id="
|
||||||
|
!music.getPlaySongLyric.hasYrc || !setting.showYrc ? 'lrc-1' : 'yrc-1'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<CountDown
|
||||||
|
v-if="setting.countDownShow"
|
||||||
|
:style="{ fontSize: setting.lyricsFontSize + 'vh' }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- 普通歌词 -->
|
||||||
|
<template v-if="!music.getPlaySongLyric.hasYrc || !setting.showYrc">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in music.getPlaySongLyric.lrc"
|
||||||
|
:class="music.getPlaySongLyricIndex == index ? 'lrc on' : 'lrc'"
|
||||||
|
:style="{ marginBottom: setting.lyricsFontSize - 1.6 + 'vh' }"
|
||||||
|
:key="item"
|
||||||
|
:id="'lrc' + index"
|
||||||
|
@click="lrcTextClick(item.time)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="setting.lyricsBlur ? 'lrc-text blur' : 'lrc-text'"
|
||||||
|
:style="{
|
||||||
|
transformOrigin:
|
||||||
|
setting.lyricsPosition === 'center' ? 'center' : null,
|
||||||
|
filter: setting.lyricsBlur
|
||||||
|
? `blur(${getFilter(music.getPlaySongLyricIndex, index)}px)`
|
||||||
|
: null,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="lyric"
|
||||||
|
:style="{ fontSize: setting.lyricsFontSize + 'vh' }"
|
||||||
|
>
|
||||||
|
{{ item.content }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-show="
|
||||||
|
music.getPlaySongLyric.hasLrcTran &&
|
||||||
|
setting.showTransl &&
|
||||||
|
item.tran
|
||||||
|
"
|
||||||
|
:style="{ fontSize: setting.lyricsFontSize - 1 + 'vh' }"
|
||||||
|
class="lyric-fy"
|
||||||
|
>
|
||||||
|
{{ item.tran }}</span
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-show="
|
||||||
|
music.getPlaySongLyric.hasLrcRoma && setting.showRoma && item.roma
|
||||||
|
"
|
||||||
|
:style="{ fontSize: setting.lyricsFontSize - 1.5 + 'vh' }"
|
||||||
|
class="lyric-roma"
|
||||||
|
>
|
||||||
|
{{ item.roma }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- 逐字歌词 -->
|
||||||
|
<template v-else>
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in music.getPlaySongLyric.yrc"
|
||||||
|
:class="music.getPlaySongLyricIndex == index ? 'yrc on' : 'yrc'"
|
||||||
|
:key="item"
|
||||||
|
:id="'yrc' + index"
|
||||||
|
@click="lrcTextClick(item.time)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="setting.lyricsBlur ? 'yrc-text blur' : 'yrc-text'"
|
||||||
|
:style="{
|
||||||
|
transformOrigin:
|
||||||
|
setting.lyricsPosition === 'center' ? 'center' : null,
|
||||||
|
filter: setting.lyricsBlur
|
||||||
|
? `blur(${getFilter(music.getPlaySongLyricIndex, index)}px)`
|
||||||
|
: null,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="lyric"
|
||||||
|
:style="{ fontSize: setting.lyricsFontSize + 'vh' }"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-for="(v, i) in item.content"
|
||||||
|
:key="i"
|
||||||
|
:style="{
|
||||||
|
'--dur': `${Math.max(v.duration - 0.15, 0.1)}s`,
|
||||||
|
}"
|
||||||
|
:class="
|
||||||
|
music.getPlaySongLyricIndex === index &&
|
||||||
|
music.getPlaySongTime.currentTime + 0.15 > v.time
|
||||||
|
? 'text fill'
|
||||||
|
: 'text'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ v.content }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
v-show="
|
||||||
|
music.getPlaySongLyric.hasYrcTran &&
|
||||||
|
setting.showTransl &&
|
||||||
|
item.tran
|
||||||
|
"
|
||||||
|
:style="{ fontSize: setting.lyricsFontSize - 1 + 'vh' }"
|
||||||
|
class="lyric-fy"
|
||||||
|
>
|
||||||
|
{{ item.tran }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-show="
|
||||||
|
music.getPlaySongLyric.hasYrcRoma && setting.showRoma && item.roma
|
||||||
|
"
|
||||||
|
:style="{ fontSize: setting.lyricsFontSize - 1.5 + 'vh' }"
|
||||||
|
class="lyric-roma"
|
||||||
|
>
|
||||||
|
{{ item.roma }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="placeholder"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { musicStore, settingStore } from "@/store";
|
||||||
|
import CountDown from "./CountDown.vue";
|
||||||
|
|
||||||
|
const music = musicStore();
|
||||||
|
const setting = settingStore();
|
||||||
|
|
||||||
|
// 发送方法
|
||||||
|
const emit = defineEmits(["lrcTextClick"]);
|
||||||
|
|
||||||
|
// 歌词模糊数值
|
||||||
|
const getFilter = (lrcIndex, index) => {
|
||||||
|
if (lrcIndex >= index) {
|
||||||
|
return lrcIndex - index;
|
||||||
|
} else {
|
||||||
|
return index - lrcIndex;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 歌词文本点击
|
||||||
|
const lrcTextClick = (time) => {
|
||||||
|
emit("lrcTextClick", time);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.lrc-all {
|
||||||
|
margin-right: 20%;
|
||||||
|
scrollbar-width: none;
|
||||||
|
// max-width: 460px;
|
||||||
|
max-width: 52vh;
|
||||||
|
overflow: auto;
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&.cover {
|
||||||
|
height: 80vh;
|
||||||
|
}
|
||||||
|
&.record {
|
||||||
|
height: 60vh;
|
||||||
|
}
|
||||||
|
&.center {
|
||||||
|
mask: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
hsla(0, 0%, 100%, 0) 0,
|
||||||
|
hsla(0, 0%, 100%, 0.6) 15%,
|
||||||
|
#fff 25%,
|
||||||
|
#fff 75%,
|
||||||
|
hsla(0, 0%, 100%, 0.6) 85%,
|
||||||
|
hsla(0, 0%, 100%, 0)
|
||||||
|
);
|
||||||
|
-webkit-mask: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
hsla(0, 0%, 100%, 0) 0,
|
||||||
|
hsla(0, 0%, 100%, 0.6) 15%,
|
||||||
|
#fff 25%,
|
||||||
|
#fff 75%,
|
||||||
|
hsla(0, 0%, 100%, 0.6) 85%,
|
||||||
|
hsla(0, 0%, 100%, 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
&.top {
|
||||||
|
mask: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
hsla(0, 0%, 100%, 0) 0,
|
||||||
|
hsla(0, 0%, 100%, 0.6) 5%,
|
||||||
|
#fff 10%,
|
||||||
|
#fff 75%,
|
||||||
|
hsla(0, 0%, 100%, 0.6) 85%,
|
||||||
|
hsla(0, 0%, 100%, 0)
|
||||||
|
);
|
||||||
|
-webkit-mask: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
hsla(0, 0%, 100%, 0) 0,
|
||||||
|
hsla(0, 0%, 100%, 0.6) 5%,
|
||||||
|
#fff 10%,
|
||||||
|
#fff 75%,
|
||||||
|
hsla(0, 0%, 100%, 0.6) 85%,
|
||||||
|
hsla(0, 0%, 100%, 0)
|
||||||
|
);
|
||||||
|
.placeholder {
|
||||||
|
&:nth-of-type(1) {
|
||||||
|
height: 16%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
.lrc-text,
|
||||||
|
.yrc-text {
|
||||||
|
&.blur {
|
||||||
|
filter: blur(0) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
height: 70vh;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
.placeholder {
|
||||||
|
width: 100%;
|
||||||
|
&:nth-of-type(1) {
|
||||||
|
height: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
padding: 0 0 0.8vh 3vh;
|
||||||
|
}
|
||||||
|
&:nth-last-of-type(1) {
|
||||||
|
height: 80%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.lrc,
|
||||||
|
.yrc {
|
||||||
|
opacity: 0.3;
|
||||||
|
transition: all 0.3s;
|
||||||
|
margin-bottom: 0.8vh;
|
||||||
|
padding: 1.8vh 4vh 1.8vh 3vh;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
transform-origin: left bottom;
|
||||||
|
cursor: pointer;
|
||||||
|
.lrc-text,
|
||||||
|
.yrc-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transition: all 0.35s ease-in-out;
|
||||||
|
transform: scale(0.95);
|
||||||
|
transform-origin: left bottom;
|
||||||
|
.lyric {
|
||||||
|
font-weight: bold;
|
||||||
|
transition: all 0.3s;
|
||||||
|
.text {
|
||||||
|
transition: all var(--dur);
|
||||||
|
color: #ffffff66;
|
||||||
|
&.fill {
|
||||||
|
text-shadow: 0 0 40px rgb(255 255 255 / 40%);
|
||||||
|
background-image: linear-gradient(to right, #fff 0%, #fff 0%);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 0% 100%;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
color: #ffffff66;
|
||||||
|
animation: toRight var(--dur) forwards linear;
|
||||||
|
}
|
||||||
|
@keyframes toRight {
|
||||||
|
to {
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.lyric-fy,
|
||||||
|
.lyric-roma {
|
||||||
|
margin-top: 4px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.on {
|
||||||
|
opacity: 1;
|
||||||
|
.lrc-text {
|
||||||
|
transform: scale(1.05);
|
||||||
|
.lyric {
|
||||||
|
text-shadow: 0 0 40px rgb(255 255 255 / 40%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.yrc-text {
|
||||||
|
transform: scale(1.05);
|
||||||
|
.lyric {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
background-color: #ffffff20;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.yrc {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- 歌词滚动 -->
|
<!-- 滚动歌词 -->
|
||||||
<div
|
<div
|
||||||
v-if="music.getPlaySongLyric.lrc[0]"
|
v-if="music.getPlaySongLyric.lrc[0]"
|
||||||
:class="[
|
:class="[
|
||||||
@@ -18,26 +18,31 @@
|
|||||||
!music.getPlaySongLyric.hasYrc || !setting.showYrc ? 'lrc-1' : 'yrc-1'
|
!music.getPlaySongLyric.hasYrc || !setting.showYrc ? 'lrc-1' : 'yrc-1'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<CountDown :style="{ fontSize: setting.lyricsFontSize + 'vh' }" />
|
<CountDown
|
||||||
|
v-if="setting.countDownShow"
|
||||||
|
:style="{ fontSize: setting.lyricsFontSize + 'vh' }"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 普通歌词 -->
|
||||||
<template v-if="!music.getPlaySongLyric.hasYrc || !setting.showYrc">
|
<template v-if="!music.getPlaySongLyric.hasYrc || !setting.showYrc">
|
||||||
<div
|
<div
|
||||||
|
class="lrc"
|
||||||
v-for="(item, index) in music.getPlaySongLyric.lrc"
|
v-for="(item, index) in music.getPlaySongLyric.lrc"
|
||||||
:class="music.getPlaySongLyricIndex == index ? 'lrc on' : 'lrc'"
|
:class="{
|
||||||
:style="{ marginBottom: setting.lyricsFontSize - 1.6 + 'vh' }"
|
on: music.getPlaySongLyricIndex == index,
|
||||||
:key="item"
|
blur: setting.lyricsBlur,
|
||||||
:id="'lrc' + index"
|
}"
|
||||||
@click="lrcTextClick(item.time)"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
:class="setting.lyricsBlur ? 'lrc-text blur' : 'lrc-text'"
|
|
||||||
:style="{
|
:style="{
|
||||||
|
marginBottom: setting.lyricsFontSize - 1.6 + 'vh',
|
||||||
transformOrigin:
|
transformOrigin:
|
||||||
setting.lyricsPosition === 'center' ? 'center' : null,
|
setting.lyricsPosition === 'center' ? 'center' : null,
|
||||||
filter: setting.lyricsBlur
|
filter: setting.lyricsBlur
|
||||||
? `blur(${getFilter(music.getPlaySongLyricIndex, index)}px)`
|
? `blur(${getFilter(music.getPlaySongLyricIndex, index)}px)`
|
||||||
: null,
|
: 'none',
|
||||||
}"
|
}"
|
||||||
|
:key="item"
|
||||||
|
:id="'lrc' + index"
|
||||||
|
@click="lrcTextClick(item.time)"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="lyric"
|
class="lyric"
|
||||||
@@ -46,72 +51,94 @@
|
|||||||
{{ item.content }}
|
{{ item.content }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-show="
|
v-if="
|
||||||
music.getPlaySongLyric.hasTran &&
|
music.getPlaySongLyric.hasLrcTran && setting.showTransl && item.tran
|
||||||
setting.getShowTransl &&
|
|
||||||
item.tran
|
|
||||||
"
|
"
|
||||||
:style="{ fontSize: setting.lyricsFontSize - 1 + 'vh' }"
|
:style="{ fontSize: setting.lyricsFontSize - 1 + 'vh' }"
|
||||||
class="lyric-fy"
|
class="lyric-fy"
|
||||||
>
|
>
|
||||||
{{ item.tran }}</span
|
{{ item.tran }}</span
|
||||||
>
|
>
|
||||||
</div>
|
<span
|
||||||
|
v-if="
|
||||||
|
music.getPlaySongLyric.hasLrcRoma && setting.showRoma && item.roma
|
||||||
|
"
|
||||||
|
:style="{ fontSize: setting.lyricsFontSize - 1.5 + 'vh' }"
|
||||||
|
class="lyric-roma"
|
||||||
|
>
|
||||||
|
{{ item.roma }}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- 逐字歌词 -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div
|
<div
|
||||||
|
class="yrc"
|
||||||
v-for="(item, index) in music.getPlaySongLyric.yrc"
|
v-for="(item, index) in music.getPlaySongLyric.yrc"
|
||||||
:class="music.getPlaySongLyricIndex == index ? 'yrc on' : 'yrc'"
|
:class="{
|
||||||
:key="item"
|
on: music.getPlaySongLyricIndex === index,
|
||||||
:id="'yrc' + index"
|
blur: setting.lyricsBlur,
|
||||||
@click="lrcTextClick(item.time)"
|
}"
|
||||||
>
|
|
||||||
<div
|
|
||||||
:class="setting.lyricsBlur ? 'yrc-text blur' : 'yrc-text'"
|
|
||||||
:style="{
|
:style="{
|
||||||
|
marginBottom: setting.lyricsFontSize - 1.6 + 'vh',
|
||||||
transformOrigin:
|
transformOrigin:
|
||||||
setting.lyricsPosition === 'center' ? 'center' : null,
|
setting.lyricsPosition === 'center' ? 'center' : null,
|
||||||
filter: setting.lyricsBlur
|
filter: setting.lyricsBlur
|
||||||
? `blur(${getFilter(music.getPlaySongLyricIndex, index)}px)`
|
? `blur(${getFilter(music.getPlaySongLyricIndex, index)}px)`
|
||||||
: null,
|
: 'none',
|
||||||
}"
|
}"
|
||||||
|
:key="item"
|
||||||
|
:id="'yrc' + index"
|
||||||
|
@click="lrcTextClick(item.time)"
|
||||||
>
|
>
|
||||||
|
<div class="lyric" :style="{ fontSize: setting.lyricsFontSize + 'vh' }">
|
||||||
<div
|
<div
|
||||||
class="lyric"
|
class="text"
|
||||||
:style="{ fontSize: setting.lyricsFontSize + 'vh' }"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-for="(v, i) in item.content"
|
v-for="(v, i) in item.content"
|
||||||
|
:class="{
|
||||||
|
fill:
|
||||||
|
music.getPlaySongLyricIndex === index &&
|
||||||
|
music.getPlaySongTime.currentTime + 0.2 >= v.time,
|
||||||
|
transform: setting.showYrcTransform,
|
||||||
|
}"
|
||||||
:key="i"
|
:key="i"
|
||||||
:style="{
|
:style="{
|
||||||
'--dur': v.duration - 0.15 + 's',
|
'--dur': `${Math.max(v.duration - 0.2, 0.1)}s`,
|
||||||
}"
|
}"
|
||||||
:class="
|
|
||||||
music.getPlaySongLyricIndex == index &&
|
|
||||||
music.getPlaySongTime.currentTime + 0.15 >= v.time
|
|
||||||
? 'text fill'
|
|
||||||
: 'text'
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
{{ v.content }}
|
<span class="word" v-html="v.content.replace(/ /g, ' ')" />
|
||||||
</span>
|
<span
|
||||||
|
class="filler"
|
||||||
|
:class="{
|
||||||
|
long: i === item.content.length - 1 && v.duration > 1,
|
||||||
|
animation: setting.showYrcAnimation,
|
||||||
|
paused: !music.playState,
|
||||||
|
}"
|
||||||
|
v-html="v.content.replace(/ /g, ' ')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
v-show="
|
v-if="
|
||||||
music.getPlaySongLyric.hasTran &&
|
music.getPlaySongLyric.hasYrcTran && setting.showTransl && item.tran
|
||||||
setting.getShowTransl &&
|
|
||||||
item.tran
|
|
||||||
"
|
"
|
||||||
:style="{ fontSize: setting.lyricsFontSize - 1 + 'vh' }"
|
:style="{ fontSize: setting.lyricsFontSize - 1 + 'vh' }"
|
||||||
class="lyric-fy"
|
class="lyric-fy"
|
||||||
>
|
>
|
||||||
{{ item.tran }}</span
|
{{ item.tran }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="
|
||||||
|
music.getPlaySongLyric.hasYrcRoma && setting.showRoma && item.roma
|
||||||
|
"
|
||||||
|
:style="{ fontSize: setting.lyricsFontSize - 1.5 + 'vh' }"
|
||||||
|
class="lyric-roma"
|
||||||
>
|
>
|
||||||
</div>
|
{{ item.roma }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="placeholder"></div>
|
<div class="placeholder" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -142,11 +169,133 @@ const lrcTextClick = (time) => {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.lrc-all {
|
.lrc-all {
|
||||||
margin-right: 20%;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
// margin-right: 20%;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
// max-width: 460px;
|
|
||||||
max-width: 52vh;
|
max-width: 52vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
padding: 0 10px;
|
||||||
|
.placeholder {
|
||||||
|
width: 100%;
|
||||||
|
&:nth-of-type(1) {
|
||||||
|
min-height: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
padding: 0 0 0.8vh 3vh;
|
||||||
|
}
|
||||||
|
&:nth-last-of-type(1) {
|
||||||
|
min-height: 80%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.lrc,
|
||||||
|
.yrc {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
padding: 1.8vh 4vh 1.8vh 3vh;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 8px;
|
||||||
|
opacity: 0.3;
|
||||||
|
transform: scale(0.9);
|
||||||
|
transform-origin: left bottom;
|
||||||
|
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
.lyric {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-weight: bold;
|
||||||
|
.text {
|
||||||
|
position: relative;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
.filler {
|
||||||
|
transition: color 0.3s ease, opacity 0.3s ease;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
opacity: 0;
|
||||||
|
&.animation {
|
||||||
|
color: white;
|
||||||
|
background: linear-gradient(to right, white, white) no-repeat 0 0;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-size: 0 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.fill {
|
||||||
|
&.transform {
|
||||||
|
transform: translateY(-1.5px);
|
||||||
|
}
|
||||||
|
.word {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
.filler {
|
||||||
|
opacity: 1 !important;
|
||||||
|
&.long {
|
||||||
|
animation: shine calc var(--dur) ease-in-out;
|
||||||
|
}
|
||||||
|
&.animation {
|
||||||
|
background-size: 100% 100%;
|
||||||
|
animation: progress var(--dur) linear forwards;
|
||||||
|
}
|
||||||
|
&.paused {
|
||||||
|
animation-play-state: paused;
|
||||||
|
-webkit-animation-play-state: paused;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.lyric-fy,
|
||||||
|
.lyric-roma {
|
||||||
|
margin-top: 4px;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
&.on {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
.lyric {
|
||||||
|
.text {
|
||||||
|
.word,
|
||||||
|
.filler {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&::before {
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: #ffffff20;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 0;
|
||||||
|
transform: scale(1.05);
|
||||||
|
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
&::before {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
&::before {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -154,7 +303,7 @@ const lrcTextClick = (time) => {
|
|||||||
height: 80vh;
|
height: 80vh;
|
||||||
}
|
}
|
||||||
&.record {
|
&.record {
|
||||||
height: 60vh;
|
height: 70vh;
|
||||||
}
|
}
|
||||||
&.center {
|
&.center {
|
||||||
mask: linear-gradient(
|
mask: linear-gradient(
|
||||||
@@ -197,106 +346,39 @@ const lrcTextClick = (time) => {
|
|||||||
);
|
);
|
||||||
.placeholder {
|
.placeholder {
|
||||||
&:nth-of-type(1) {
|
&:nth-of-type(1) {
|
||||||
height: 16%;
|
min-height: 16%;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
.lrc-text {
|
|
||||||
&.blur {
|
|
||||||
filter: blur(0) !important;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
height: 70vh;
|
height: 70vh;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
.placeholder {
|
}
|
||||||
width: 100%;
|
@keyframes progress {
|
||||||
&:nth-of-type(1) {
|
0% {
|
||||||
height: 50%;
|
background-size: 0 100%;
|
||||||
display: flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
padding: 0 0 0.8vh 3vh;
|
|
||||||
}
|
}
|
||||||
&:nth-last-of-type(1) {
|
100% {
|
||||||
height: 80%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.lrc,
|
|
||||||
.yrc {
|
|
||||||
opacity: 0.2;
|
|
||||||
transition: all 0.3s;
|
|
||||||
margin-bottom: 0.8vh;
|
|
||||||
padding: 1.8vh 4vh 1.8vh 3vh;
|
|
||||||
border-radius: 8px;
|
|
||||||
transition: all 0.3s;
|
|
||||||
transform-origin: left bottom;
|
|
||||||
cursor: pointer;
|
|
||||||
.lrc-text,
|
|
||||||
.yrc-text {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
transition: all 0.35s ease-in-out;
|
|
||||||
transform: scale(0.95);
|
|
||||||
transform-origin: left bottom;
|
|
||||||
.lyric {
|
|
||||||
font-weight: bold;
|
|
||||||
transition: all 0.3s;
|
|
||||||
.text {
|
|
||||||
transition: all var(--dur);
|
|
||||||
color: #ffffff66;
|
|
||||||
&.fill {
|
|
||||||
text-shadow: 0px 0px 30px #ffffff40;
|
|
||||||
background-image: linear-gradient(to right, #fff 0%, #fff 0%);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 0% 100%;
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
color: #ffffff66;
|
|
||||||
animation: toRight var(--dur) forwards linear;
|
|
||||||
}
|
|
||||||
@keyframes toRight {
|
|
||||||
to {
|
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@keyframes shine {
|
||||||
|
0% {
|
||||||
|
text-shadow: 0 0 0.1em rgba(255, 255, 255, 0);
|
||||||
}
|
}
|
||||||
|
25% {
|
||||||
|
text-shadow: 0 0 0.3em rgba(255, 255, 255, 0.5);
|
||||||
}
|
}
|
||||||
|
50% {
|
||||||
|
text-shadow: 0 0 0.5em rgba(255, 255, 255, 0.7);
|
||||||
}
|
}
|
||||||
.lyric-fy {
|
75% {
|
||||||
margin-top: 4px;
|
text-shadow: 0 0 0.3em rgba(255, 255, 255, 0.5);
|
||||||
transition: all 0.3s;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
}
|
||||||
}
|
100% {
|
||||||
&.on {
|
text-shadow: 0 0 0.1em rgba(255, 255, 255, 0);
|
||||||
opacity: 1;
|
|
||||||
.lrc-text {
|
|
||||||
transform: scale(1.05);
|
|
||||||
.lyric {
|
|
||||||
text-shadow: 0px 0px 30px #ffffff40;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.yrc-text {
|
|
||||||
transform: scale(1.05);
|
|
||||||
.lyric {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
background-color: #ffffff20;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.yrc {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
40
src/components/Player/Spectrum.vue
Normal file
40
src/components/Player/Spectrum.vue
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<div class="spectrum">
|
||||||
|
<canvas class="avBars" ref="canvasRef" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { musicStore } from "@/store";
|
||||||
|
|
||||||
|
const music = musicStore();
|
||||||
|
const canvasRef = ref(null);
|
||||||
|
|
||||||
|
const drawSpectrum = (data) => {
|
||||||
|
canvasRef.value.width =
|
||||||
|
document.body.clientWidth >= 1600 ? 1600 : document.body.clientWidth;
|
||||||
|
canvasRef.value.height = 80;
|
||||||
|
const ctx = canvasRef.value.getContext("2d");
|
||||||
|
const barWidth = 2;
|
||||||
|
// 清除画布
|
||||||
|
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height);
|
||||||
|
for (let i = 0; i < 360; i++) {
|
||||||
|
const barHeight = (data[i] / 255) * canvasRef.value.height;
|
||||||
|
const x = i * (barWidth * 2);
|
||||||
|
const y = canvasRef.value.height - barHeight;
|
||||||
|
ctx.fillStyle = "#ffffff";
|
||||||
|
ctx.fillRect(x, y, barWidth, barHeight);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => music.spectrumsData.data,
|
||||||
|
(val) => drawSpectrum(val)
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.spectrum {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,20 +1,33 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<Transition name="show">
|
||||||
<n-card
|
<n-card
|
||||||
:class="
|
v-show="music.getPlaylists[0] && music.showPlayBar"
|
||||||
music.getPlaylists[0] && music.showPlayBar ? 'player show' : 'player'
|
class="player"
|
||||||
"
|
|
||||||
content-style="padding: 0"
|
content-style="padding: 0"
|
||||||
>
|
>
|
||||||
<div class="slider">
|
<div class="slider">
|
||||||
<span>{{ music.getPlaySongTime.songTimePlayed }}</span>
|
<span>{{ music.getPlaySongTime.songTimePlayed }}</span>
|
||||||
<n-slider
|
<vue-slider
|
||||||
v-model:value="music.getPlaySongTime.barMoveDistance"
|
v-model="music.getPlaySongTime.barMoveDistance"
|
||||||
class="progress"
|
@drag-start="music.setPlayState(false)"
|
||||||
:step="0.01"
|
@drag-end="sliderDragEnd"
|
||||||
:tooltip="false"
|
@click.stop="
|
||||||
@update:value="songTimeSliderUpdate"
|
songTimeSliderUpdate(music.getPlaySongTime.barMoveDistance)
|
||||||
@click.stop
|
"
|
||||||
/>
|
:tooltip="'active'"
|
||||||
|
:use-keyboard="false"
|
||||||
|
>
|
||||||
|
<template v-slot:tooltip>
|
||||||
|
<div class="slider-tooltip">
|
||||||
|
{{
|
||||||
|
getSongPlayingTime(
|
||||||
|
(music.getPlaySongTime.duration / 100) *
|
||||||
|
music.getPlaySongTime.barMoveDistance
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</vue-slider>
|
||||||
<span>{{ music.getPlaySongTime.songTimeDuration }}</span>
|
<span>{{ music.getPlaySongTime.songTimeDuration }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="all">
|
<div class="all">
|
||||||
@@ -39,14 +52,20 @@
|
|||||||
@click.stop="router.push(`/song?id=${music.getPlaySongData.id}`)"
|
@click.stop="router.push(`/song?id=${music.getPlaySongData.id}`)"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
music.getPlaySongData ? music.getPlaySongData.name : "暂无歌曲"
|
music.getPlaySongData
|
||||||
|
? music.getPlaySongData.name
|
||||||
|
: $t("other.noSong")
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 显示歌手或歌词 -->
|
||||||
<div class="artisrOrLrc" v-if="music.getPlaySongData">
|
<div class="artisrOrLrc" v-if="music.getPlaySongData">
|
||||||
|
<Transition name="fade" mode="out-in">
|
||||||
<template v-if="setting.bottomLyricShow">
|
<template v-if="setting.bottomLyricShow">
|
||||||
<Transition mode="out-in">
|
<Transition name="fade" mode="out-in">
|
||||||
<AllArtists
|
<AllArtists
|
||||||
v-if="!music.getPlayState || !music.getPlaySongLyric.lrc[0]"
|
v-if="
|
||||||
|
!music.getPlayState || !music.getPlaySongLyric.lrc[0]
|
||||||
|
"
|
||||||
class="text-hidden"
|
class="text-hidden"
|
||||||
:artistsData="music.getPlaySongData.artist"
|
:artistsData="music.getPlaySongData.artist"
|
||||||
/>
|
/>
|
||||||
@@ -93,13 +112,13 @@
|
|||||||
:artistsData="music.getPlaySongData.artist"
|
:artistsData="music.getPlaySongData.artist"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<n-icon
|
<n-icon
|
||||||
v-if="!music.getPersonalFmMode"
|
v-if="!music.getPersonalFmMode"
|
||||||
title="上一曲"
|
|
||||||
class="prev"
|
class="prev"
|
||||||
size="30"
|
size="30"
|
||||||
:component="SkipPreviousRound"
|
:component="SkipPreviousRound"
|
||||||
@@ -115,7 +134,6 @@
|
|||||||
<div class="play-state">
|
<div class="play-state">
|
||||||
<n-icon
|
<n-icon
|
||||||
size="46"
|
size="46"
|
||||||
:title="music.getPlayState ? '暂停' : '播放'"
|
|
||||||
:component="
|
:component="
|
||||||
music.getPlayState ? PauseCircleFilled : PlayCircleFilled
|
music.getPlayState ? PauseCircleFilled : PlayCircleFilled
|
||||||
"
|
"
|
||||||
@@ -130,6 +148,8 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div :class="music.getPersonalFmMode ? 'menu fm' : 'menu'">
|
<div :class="music.getPersonalFmMode ? 'menu fm' : 'menu'">
|
||||||
|
<n-popover trigger="hover" :keep-alive-on-hover="false">
|
||||||
|
<template #trigger>
|
||||||
<div class="like" v-if="music.getPlaySongData">
|
<div class="like" v-if="music.getPlaySongData">
|
||||||
<n-icon
|
<n-icon
|
||||||
class="like-icon"
|
class="like-icon"
|
||||||
@@ -146,6 +166,15 @@
|
|||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
{{
|
||||||
|
music.getSongIsLike(music.getPlaySongData.id)
|
||||||
|
? $t("menu.cancelCollection")
|
||||||
|
: $t("menu.collection")
|
||||||
|
}}
|
||||||
|
</n-popover>
|
||||||
|
<n-popover trigger="hover" :keep-alive-on-hover="false">
|
||||||
|
<template #trigger>
|
||||||
<div class="add-playlist">
|
<div class="add-playlist">
|
||||||
<n-icon
|
<n-icon
|
||||||
class="add-icon"
|
class="add-icon"
|
||||||
@@ -156,6 +185,9 @@
|
|||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
{{ $t("menu.add") }}
|
||||||
|
</n-popover>
|
||||||
<div class="pattern">
|
<div class="pattern">
|
||||||
<n-icon
|
<n-icon
|
||||||
:component="
|
:component="
|
||||||
@@ -168,6 +200,8 @@
|
|||||||
@click="music.setPlaySongMode()"
|
@click="music.setPlaySongMode()"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<n-popover trigger="hover" :keep-alive-on-hover="false">
|
||||||
|
<template #trigger>
|
||||||
<div :class="music.showPlayList ? 'playlist open' : 'playlist'">
|
<div :class="music.showPlayList ? 'playlist open' : 'playlist'">
|
||||||
<n-icon
|
<n-icon
|
||||||
size="30"
|
size="30"
|
||||||
@@ -175,6 +209,9 @@
|
|||||||
@click.stop="music.showPlayList = !music.showPlayList"
|
@click.stop="music.showPlayList = !music.showPlayList"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
{{ $t("general.name.playlists") }}
|
||||||
|
</n-popover>
|
||||||
<div class="volume">
|
<div class="volume">
|
||||||
<n-icon
|
<n-icon
|
||||||
size="28"
|
size="28"
|
||||||
@@ -201,18 +238,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<audio
|
|
||||||
ref="player"
|
|
||||||
:autoplay="music.getPlayState"
|
|
||||||
@timeupdate="songUpdate"
|
|
||||||
@play="songPlay"
|
|
||||||
@pause="songPause"
|
|
||||||
@canplay="songCanplay"
|
|
||||||
@error="songError"
|
|
||||||
@ended="music.setPlaySongIndex('next')"
|
|
||||||
:src="music.getPlaySongLink"
|
|
||||||
></audio>
|
|
||||||
</n-card>
|
</n-card>
|
||||||
|
</Transition>
|
||||||
<!-- 播放列表 -->
|
<!-- 播放列表 -->
|
||||||
<PlayListDrawer ref="PlayListDrawerRef" />
|
<PlayListDrawer ref="PlayListDrawerRef" />
|
||||||
<!-- 添加到歌单 -->
|
<!-- 添加到歌单 -->
|
||||||
@@ -231,7 +258,6 @@ import {
|
|||||||
import { NIcon } from "naive-ui";
|
import { NIcon } from "naive-ui";
|
||||||
import {
|
import {
|
||||||
KeyboardArrowUpFilled,
|
KeyboardArrowUpFilled,
|
||||||
MusicNoteFilled,
|
|
||||||
PlayCircleFilled,
|
PlayCircleFilled,
|
||||||
PauseCircleFilled,
|
PauseCircleFilled,
|
||||||
SkipNextRound,
|
SkipNextRound,
|
||||||
@@ -248,24 +274,34 @@ import {
|
|||||||
} from "@vicons/material";
|
} from "@vicons/material";
|
||||||
import { PlayCycle, PlayOnce, ShuffleOne } from "@icon-park/vue-next";
|
import { PlayCycle, PlayOnce, ShuffleOne } from "@icon-park/vue-next";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { musicStore, settingStore } from "@/store";
|
import { musicStore, settingStore, siteStore } from "@/store";
|
||||||
|
import {
|
||||||
|
createSound,
|
||||||
|
setVolume,
|
||||||
|
setSeek,
|
||||||
|
fadePlayOrPause,
|
||||||
|
} from "@/utils/Player";
|
||||||
|
import { getSongPlayingTime } from "@/utils/timeTools";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import AddPlaylist from "@/components/DataModel/AddPlaylist.vue";
|
import { debounce } from "throttle-debounce";
|
||||||
import PlayListDrawer from "@/components/DataModel/PlayListDrawer.vue";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import VueSlider from "vue-slider-component";
|
||||||
|
import AddPlaylist from "@/components/DataModal/AddPlaylist.vue";
|
||||||
|
import PlayListDrawer from "@/components/DataModal/PlayListDrawer.vue";
|
||||||
import AllArtists from "@/components/DataList/AllArtists.vue";
|
import AllArtists from "@/components/DataList/AllArtists.vue";
|
||||||
|
import ColorThief from "colorthief";
|
||||||
import BigPlayer from "./BigPlayer.vue";
|
import BigPlayer from "./BigPlayer.vue";
|
||||||
import debounce from "@/utils/debounce";
|
import "vue-slider-component/theme/default.css";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const setting = settingStore();
|
const setting = settingStore();
|
||||||
const music = musicStore();
|
const music = musicStore();
|
||||||
|
const site = siteStore();
|
||||||
const { persistData } = storeToRefs(music);
|
const { persistData } = storeToRefs(music);
|
||||||
const addPlayListRef = ref(null);
|
const addPlayListRef = ref(null);
|
||||||
const PlayListDrawerRef = ref(null);
|
const PlayListDrawerRef = ref(null);
|
||||||
|
|
||||||
// 重试次数
|
|
||||||
const testNumber = ref(0);
|
|
||||||
|
|
||||||
// UNM 是否存在
|
// UNM 是否存在
|
||||||
const useUnmServerHas = import.meta.env.VITE_UNM_API ? true : false;
|
const useUnmServerHas = import.meta.env.VITE_UNM_API ? true : false;
|
||||||
|
|
||||||
@@ -292,16 +328,18 @@ const getPlaySongData = (data, level = setting.songLevel) => {
|
|||||||
if (res.success) {
|
if (res.success) {
|
||||||
console.log("当前歌曲可用");
|
console.log("当前歌曲可用");
|
||||||
if (!pc && (fee === 1 || fee === 4))
|
if (!pc && (fee === 1 || fee === 4))
|
||||||
$message.info("当前歌曲为 VIP 专享,仅可试听");
|
$message.info(t("general.message.vipTip"));
|
||||||
// 获取音乐地址
|
// 获取音乐地址
|
||||||
getMusicUrl(id, level).then((res) => {
|
getMusicUrl(id, level).then((res) => {
|
||||||
music.setPlaySongLink(res.data[0].url.replace(/^http:/, "https:"));
|
player.value = createSound(
|
||||||
|
res.data[0].url.replace(/^http:/, "https:")
|
||||||
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (useUnmServerHas && setting.useUnmServer) {
|
if (useUnmServerHas && setting.useUnmServer) {
|
||||||
getMusicNumUrlData(id);
|
getMusicNumUrlData(id);
|
||||||
} else {
|
} else {
|
||||||
$message.warning("当前歌曲播放失败,跳至下一首");
|
$message.warning(t("general.message.playError"));
|
||||||
music.setPlaySongIndex("next");
|
music.setPlaySongIndex("next");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,9 +350,9 @@ const getPlaySongData = (data, level = setting.songLevel) => {
|
|||||||
music.setPlaySongLyric(res);
|
music.setPlaySongLyric(res);
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (music.getPlaylists[0] && music.getPlayState) {
|
||||||
console.log("当前歌曲所有音源匹配失败:" + err);
|
console.log("当前歌曲所有音源匹配失败:" + err);
|
||||||
if (music.getPlayState && $player) {
|
$message.warning(t("general.message.playError"));
|
||||||
$message.warning("当前歌曲所有音源匹配失败,跳至下一首");
|
|
||||||
music.setPlaySongIndex("next");
|
music.setPlaySongIndex("next");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -326,167 +364,26 @@ const getMusicNumUrlData = (id) => {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
console.log("替换成功:" + res.data.url.replace(/^http:/, ""));
|
console.log("替换成功:" + res.data.url.replace(/^http:/, ""));
|
||||||
music.setPlaySongLink(res.data.url.replace(/^http:/, ""));
|
player.value = createSound(res.data.url.replace(/^http:/, ""));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.log("解灰失败:" + err);
|
console.log("解灰失败:" + err);
|
||||||
$message.warning("当前歌曲解灰失败,跳至下一首");
|
$message.warning(t("general.message.playError"));
|
||||||
music.setPlaySongIndex("next");
|
music.setPlaySongIndex("next");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 歌曲进度更新事件
|
|
||||||
const songUpdate = (e) => {
|
|
||||||
const currentTime = e.target.currentTime;
|
|
||||||
const duration = e.target.duration;
|
|
||||||
music.setPlaySongTime({ currentTime, duration });
|
|
||||||
};
|
|
||||||
|
|
||||||
// 歌曲缓冲完毕
|
|
||||||
const songCanplay = () => {
|
|
||||||
console.log("缓冲完毕", music.getPlayState);
|
|
||||||
if (music.getPlayState && $player) {
|
|
||||||
music.setPlayState(true);
|
|
||||||
songInOrOut("play");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 歌曲开始播放
|
|
||||||
const songPlay = () => {
|
|
||||||
testNumber.value = 0;
|
|
||||||
if (!music.getPlaySongData) {
|
|
||||||
$message.error("音乐数据获取失败");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
music.setPlayState(true);
|
|
||||||
// 兼容 mediaSession
|
|
||||||
if ("mediaSession" in navigator) {
|
|
||||||
navigator.mediaSession.metadata = new MediaMetadata({
|
|
||||||
title: music.getPlaySongData.name,
|
|
||||||
artist: music.getPlaySongData.artist[0].name,
|
|
||||||
album: music.getPlaySongData.album.name,
|
|
||||||
artwork: [
|
|
||||||
{
|
|
||||||
src:
|
|
||||||
music.getPlaySongData.album.picUrl.replace(/^http:/, "https:") +
|
|
||||||
"?param=96y96",
|
|
||||||
sizes: "96x96",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src:
|
|
||||||
music.getPlaySongData.album.picUrl.replace(/^http:/, "https:") +
|
|
||||||
"?param=128y128",
|
|
||||||
sizes: "128x128",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src:
|
|
||||||
music.getPlaySongData.album.picUrl.replace(/^http:/, "https:") +
|
|
||||||
"?param=192y192",
|
|
||||||
sizes: "192x192",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
navigator.mediaSession.setActionHandler("nexttrack", () => {
|
|
||||||
music.setPlaySongIndex("next");
|
|
||||||
});
|
|
||||||
navigator.mediaSession.setActionHandler("previoustrack", () => {
|
|
||||||
music.setPlaySongIndex("prev");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$message.info(
|
|
||||||
music.getPlaySongData.name + " - " + music.getPlaySongData.artist[0].name,
|
|
||||||
{
|
|
||||||
icon: () =>
|
|
||||||
h(NIcon, null, {
|
|
||||||
default: () => h(MusicNoteFilled),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// 写入播放历史
|
|
||||||
music.setPlayHistory(music.getPlaySongData);
|
|
||||||
// 更改页面标题
|
|
||||||
// $setSiteTitle(
|
|
||||||
// music.getPlaySongData.name + " - " + music.getPlaySongData.artist[0].name
|
|
||||||
// );
|
|
||||||
window.document.title =
|
|
||||||
music.getPlaySongData.name +
|
|
||||||
" - " +
|
|
||||||
music.getPlaySongData.artist[0].name +
|
|
||||||
" - SPlayer";
|
|
||||||
};
|
|
||||||
|
|
||||||
// 音乐渐入渐出
|
|
||||||
const isFading = ref(false);
|
|
||||||
const songInOrOut = (type) => {
|
|
||||||
if (isFading.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
isFading.value = true;
|
|
||||||
|
|
||||||
if (type === "play") {
|
|
||||||
let volume = 0;
|
|
||||||
$player.play();
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
// 如果音量已经到达当前音量,则停止渐入
|
|
||||||
if (volume >= persistData.value.playVolume) {
|
|
||||||
clearInterval(interval);
|
|
||||||
isFading.value = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 增加音量
|
|
||||||
volume += 0.1;
|
|
||||||
if (volume > persistData.value.playVolume) {
|
|
||||||
volume = persistData.value.playVolume;
|
|
||||||
}
|
|
||||||
$player.volume = volume;
|
|
||||||
}, 30);
|
|
||||||
} else if (type === "pause") {
|
|
||||||
let volume = persistData.value.playVolume;
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
// 如果音量已经到达最小值,则停止渐出
|
|
||||||
if (volume <= 0) {
|
|
||||||
clearInterval(interval);
|
|
||||||
$player.pause();
|
|
||||||
isFading.value = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 减小音量
|
|
||||||
volume -= 0.1;
|
|
||||||
if (volume < 0) {
|
|
||||||
volume = 0;
|
|
||||||
}
|
|
||||||
$player.volume = volume;
|
|
||||||
}, 30);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 歌曲暂停
|
|
||||||
const songPause = () => {
|
|
||||||
console.log("音乐暂停");
|
|
||||||
if (!$player.ended) music.setPlayState(false);
|
|
||||||
// 更改页面标题
|
|
||||||
// window.document.title = "SPlayer";
|
|
||||||
$setSiteTitle();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 歌曲进度条更新
|
// 歌曲进度条更新
|
||||||
const songTimeSliderUpdate = (val) => {
|
const sliderDragEnd = () => {
|
||||||
if ($player && music.getPlaySongTime && music.getPlaySongTime.duration)
|
songTimeSliderUpdate(music.getPlaySongTime.barMoveDistance);
|
||||||
$player.currentTime = (music.getPlaySongTime.duration / 100) * val;
|
music.setPlayState(true);
|
||||||
};
|
};
|
||||||
|
const songTimeSliderUpdate = (val) => {
|
||||||
// 歌曲播放失败事件
|
if (player.value && music.getPlaySongTime?.duration) {
|
||||||
const songError = () => {
|
const currentTime = (music.getPlaySongTime.duration / 100) * val;
|
||||||
console.error("歌曲播放失败");
|
setSeek(player.value, currentTime);
|
||||||
$message.error("歌曲播放失败");
|
|
||||||
if (testNumber.value < 4) {
|
|
||||||
if (music.getPlaylists[0]) getPlaySongData(music.getPlaySongData);
|
|
||||||
testNumber.value++;
|
|
||||||
} else {
|
|
||||||
$message.error("歌曲重试次数过多,请刷新后重试");
|
|
||||||
}
|
}
|
||||||
if (music.getPlayState) songInOrOut("play");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 静音事件
|
// 静音事件
|
||||||
@@ -499,27 +396,55 @@ const volumeMute = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
// 歌曲更换事件
|
||||||
// 获取音乐数据
|
const songChange = debounce(500, (val) => {
|
||||||
if (music.getPlaylists[0] && music.getPlaySongData)
|
if (val === undefined) {
|
||||||
getPlaySongData(music.getPlaySongData);
|
window.document.title =
|
||||||
// 挂载播放器
|
sessionStorage.getItem("siteTitle") ?? import.meta.env.VITE_SITE_TITLE;
|
||||||
window.$player = player.value;
|
}
|
||||||
// 恢复上次播放进度
|
// 加载数据
|
||||||
if (music.getPlaySongTime && music.getPlaySongTime.currentTime) {
|
getPlaySongData(val);
|
||||||
$player.currentTime = music.getPlaySongTime.currentTime;
|
getPicColor(val?.album.picUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取封面图主色
|
||||||
|
const getPicColor = (url) => {
|
||||||
|
if (!url) return false;
|
||||||
|
const imgUrl = url.replace(/^http:/, "https:") + "?param=50y50";
|
||||||
|
const img = new Image();
|
||||||
|
fetch(imgUrl)
|
||||||
|
.then((res) => res.blob())
|
||||||
|
.then((blob) => {
|
||||||
|
img.src = URL.createObjectURL(blob);
|
||||||
|
img.addEventListener("load", async () => {
|
||||||
|
const colorThief = new ColorThief();
|
||||||
|
const color = await colorThief.getColor(img);
|
||||||
|
console.log(`当前封面主色:rgb(${color.join(",")})`);
|
||||||
|
site.songPicColor = `rgb(${color.join(",")})`;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("图像处理出错:" + err);
|
||||||
|
site.songPicColor = "rgb(128,128,128)";
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 挂载方法
|
||||||
|
window.$getPlaySongData = getPlaySongData;
|
||||||
|
// 获取音乐数据
|
||||||
|
if (music.getPlaylists[0] && music.getPlaySongData) {
|
||||||
|
getPlaySongData(music.getPlaySongData);
|
||||||
|
getPicColor(music.getPlaySongData.album.picUrl);
|
||||||
}
|
}
|
||||||
// 设置音量
|
|
||||||
if ($player) $player.volume = persistData.value.playVolume;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听当前音乐数据变化
|
// 监听当前音乐数据变化
|
||||||
watch(
|
watch(
|
||||||
() => music.getPlaySongData,
|
() => music.getPlaySongData,
|
||||||
(val) => {
|
(val) => {
|
||||||
debounce(() => {
|
music.setPlaySongTime({ currentTime: 0, duration: 0 });
|
||||||
getPlaySongData(val);
|
songChange(val);
|
||||||
}, 500);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -527,7 +452,7 @@ watch(
|
|||||||
watch(
|
watch(
|
||||||
() => persistData.value.playVolume,
|
() => persistData.value.playVolume,
|
||||||
(val) => {
|
(val) => {
|
||||||
if ($player) $player.volume = val;
|
if (player.value) setVolume(player.value, val);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -535,40 +460,44 @@ watch(
|
|||||||
watch(
|
watch(
|
||||||
() => music.getPlayState,
|
() => music.getPlayState,
|
||||||
(val) => {
|
(val) => {
|
||||||
nextTick(() => {
|
nextTick().then(() => {
|
||||||
// $player ? (val ? $player.play() : $player.pause()) : null;
|
if (player.value && !music.isLoadingSong) {
|
||||||
if ($player) {
|
fadePlayOrPause(
|
||||||
// val ? $player.play() : $player.pause();
|
player.value,
|
||||||
val ? songInOrOut("play") : songInOrOut("pause");
|
val ? "play" : "pause",
|
||||||
} else {
|
persistData.value.playVolume
|
||||||
$message.error("播放器初始化失败,请重试");
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// 监听歌曲进度更新
|
|
||||||
// watch(
|
|
||||||
// () => music.getPlaySongTime,
|
|
||||||
// (val) => {
|
|
||||||
// if (val.barMoveDistance) {
|
|
||||||
// songTimeVal.value = val.barMoveDistance;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.show-enter-active,
|
||||||
|
.show-leave-active {
|
||||||
|
transform: translateY(0);
|
||||||
|
transition: all 0.3s cubic-bezier(0.65, 0.05, 0.36, 1);
|
||||||
|
}
|
||||||
|
.show-enter-from,
|
||||||
|
.show-leave-to {
|
||||||
|
transform: translateY(80px);
|
||||||
|
}
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
.player {
|
.player {
|
||||||
height: 70px;
|
height: 70px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: -90px;
|
|
||||||
left: 0;
|
|
||||||
transition: all 0.3s;
|
|
||||||
z-index: 2004;
|
|
||||||
&.show {
|
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
left: 0;
|
||||||
|
z-index: 2;
|
||||||
.slider {
|
.slider {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -12px;
|
top: -12px;
|
||||||
@@ -576,19 +505,15 @@ watch(
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
top: -6px;
|
top: -8px;
|
||||||
> {
|
> {
|
||||||
span {
|
span {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress {
|
|
||||||
--n-handle-size: 12px;
|
|
||||||
--n-rail-height: 3px;
|
|
||||||
}
|
|
||||||
> {
|
> {
|
||||||
span {
|
span {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -600,6 +525,33 @@ watch(
|
|||||||
margin: 0 2px;
|
margin: 0 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.vue-slider {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 3px !important;
|
||||||
|
cursor: pointer;
|
||||||
|
.slider-tooltip {
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
background-color: var(--n-color);
|
||||||
|
outline: 1px solid var(--n-border-color);
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 25px;
|
||||||
|
}
|
||||||
|
:deep(.vue-slider-rail) {
|
||||||
|
background-color: var(--n-border-color);
|
||||||
|
border-radius: 25px;
|
||||||
|
.vue-slider-process {
|
||||||
|
background-color: var(--main-color);
|
||||||
|
}
|
||||||
|
.vue-slider-dot {
|
||||||
|
width: 12px !important;
|
||||||
|
height: 12px !important;
|
||||||
|
}
|
||||||
|
.vue-slider-dot-handle-focus {
|
||||||
|
box-shadow: 0px 0px 1px 2px var(--main-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.all {
|
.all {
|
||||||
@@ -659,21 +611,12 @@ watch(
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.artisrOrLrc {
|
.artisrOrLrc {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
.v-enter-active,
|
|
||||||
.v-leave-active {
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.v-enter-from,
|
|
||||||
.v-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -685,7 +628,7 @@ watch(
|
|||||||
.next,
|
.next,
|
||||||
.prev,
|
.prev,
|
||||||
.dislike {
|
.dislike {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
@@ -693,7 +636,7 @@ watch(
|
|||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--n-color-embedded);
|
color: var(--n-color-embedded);
|
||||||
background-color: $mainColor;
|
background-color: var(--main-color);
|
||||||
}
|
}
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.9);
|
transform: scale(0.9);
|
||||||
@@ -705,7 +648,7 @@ watch(
|
|||||||
.play-state {
|
.play-state {
|
||||||
width: 46px;
|
width: 46px;
|
||||||
height: 46px;
|
height: 46px;
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
margin: 0 12px;
|
margin: 0 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
@@ -725,7 +668,7 @@ watch(
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.volume,
|
.volume,
|
||||||
.like,
|
.like,
|
||||||
@@ -745,10 +688,12 @@ watch(
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
@media (min-width: 640px) {
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $mainColor;
|
background-color: var(--main-color);
|
||||||
color: var(--n-color-embedded);
|
color: var(--n-color-embedded);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
@@ -782,7 +727,7 @@ watch(
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
&.open {
|
&.open {
|
||||||
.n-icon {
|
.n-icon {
|
||||||
background-color: $mainColor;
|
background-color: var(--main-color);
|
||||||
color: var(--n-color-embedded);
|
color: var(--n-color-embedded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,51 +43,52 @@ import {
|
|||||||
useNotification,
|
useNotification,
|
||||||
} from "naive-ui";
|
} from "naive-ui";
|
||||||
import { settingStore } from "@/store";
|
import { settingStore } from "@/store";
|
||||||
|
import themeColorData from "./themeColor.json";
|
||||||
|
|
||||||
const setting = settingStore();
|
const setting = settingStore();
|
||||||
const osThemeRef = useOsTheme();
|
const osThemeRef = useOsTheme();
|
||||||
|
const themeOverrides = ref(null);
|
||||||
|
|
||||||
// 明暗切换
|
// 明暗切换
|
||||||
const theme = ref(null);
|
const theme = ref(null);
|
||||||
|
const themeColorMeta = document.querySelector('meta[name="theme-color"]');
|
||||||
const changeTheme = () => {
|
const changeTheme = () => {
|
||||||
if (setting.getSiteTheme == "light") {
|
if (setting.getSiteTheme == "light") {
|
||||||
theme.value = null;
|
theme.value = null;
|
||||||
|
themeColorMeta.setAttribute("content", "#ffffff");
|
||||||
} else if (setting.getSiteTheme == "dark") {
|
} else if (setting.getSiteTheme == "dark") {
|
||||||
theme.value = darkTheme;
|
theme.value = darkTheme;
|
||||||
|
themeColorMeta.setAttribute("content", "#18181c");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
// 根据系统决定明暗切换
|
||||||
changeTheme();
|
const osThemeChange = (val) => {
|
||||||
});
|
|
||||||
|
|
||||||
// 监听明暗变化
|
|
||||||
watch(
|
|
||||||
() => setting.getSiteTheme,
|
|
||||||
() => {
|
|
||||||
changeTheme();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// 监听系统明暗变化
|
|
||||||
watch(
|
|
||||||
() => osThemeRef.value,
|
|
||||||
(value) => {
|
|
||||||
if (setting.themeAuto) {
|
if (setting.themeAuto) {
|
||||||
value == "dark"
|
val == "dark"
|
||||||
? setting.setSiteTheme("dark")
|
? setting.setSiteTheme("dark")
|
||||||
: setting.setSiteTheme("light");
|
: setting.setSiteTheme("light");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
);
|
|
||||||
|
|
||||||
// 配置主题色
|
// 配置主题色
|
||||||
const themeOverrides = {
|
const changeThemeColor = (val) => {
|
||||||
common: {
|
const color = themeColorData[val];
|
||||||
primaryColor: "#f55e55",
|
console.log("当前主题色:" + val, color);
|
||||||
primaryColorHover: "#F57B74",
|
themeOverrides.value = {
|
||||||
primaryColorSuppl: "#F57B74",
|
common: color,
|
||||||
primaryColorPressed: "#F64B41",
|
};
|
||||||
},
|
setting.themeData = color;
|
||||||
|
setCssVariable("--main-color", color.primaryColor);
|
||||||
|
setCssVariable("--main-second-color", color.primaryColor + "1f");
|
||||||
|
setCssVariable("--main-boxshadow-color", color.primaryColor + "26");
|
||||||
|
setCssVariable("--main-boxshadow-hover-color", color.primaryColor + "05");
|
||||||
|
};
|
||||||
|
|
||||||
|
// 修改全局颜色
|
||||||
|
const setCssVariable = (name, value) => {
|
||||||
|
document.documentElement.style.setProperty(name, value);
|
||||||
|
// document.body.style.setProperty(name, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 挂载 naive 组件的方法
|
// 挂载 naive 组件的方法
|
||||||
@@ -102,12 +103,34 @@ const NaiveProviderContent = defineComponent({
|
|||||||
setup() {
|
setup() {
|
||||||
setupNaiveTools();
|
setupNaiveTools();
|
||||||
},
|
},
|
||||||
render() {
|
render() {},
|
||||||
return h("div", {
|
});
|
||||||
class: {
|
|
||||||
tools: true,
|
// 监听明暗变化
|
||||||
},
|
watch(
|
||||||
});
|
() => setting.getSiteTheme,
|
||||||
},
|
() => {
|
||||||
|
changeTheme();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听系统明暗变化
|
||||||
|
watch(
|
||||||
|
() => osThemeRef.value,
|
||||||
|
(val) => {
|
||||||
|
osThemeChange(val);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听主题色变化
|
||||||
|
watch(
|
||||||
|
() => setting.themeType,
|
||||||
|
(val) => changeThemeColor(val)
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
changeTheme();
|
||||||
|
changeThemeColor(setting.themeType);
|
||||||
|
osThemeChange(osThemeRef.value);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
82
src/components/Provider/themeColor.json
Normal file
82
src/components/Provider/themeColor.json
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"red": {
|
||||||
|
"name": "欢快派对",
|
||||||
|
"label": "red",
|
||||||
|
"primaryColor": "#f55e55",
|
||||||
|
"primaryColorHover": "#F57B74",
|
||||||
|
"primaryColorSuppl": "#F57B74",
|
||||||
|
"primaryColorPressed": "#F64B41"
|
||||||
|
},
|
||||||
|
"orange": {
|
||||||
|
"name": "柑橘桔梦",
|
||||||
|
"label": "orange",
|
||||||
|
"primaryColor": "#ff8c00",
|
||||||
|
"primaryColorHover": "#ffa033",
|
||||||
|
"primaryColorSuppl": "#ffa033",
|
||||||
|
"primaryColorPressed": "#ff6b00"
|
||||||
|
},
|
||||||
|
"blue": {
|
||||||
|
"name": "深海蓝梦",
|
||||||
|
"label": "blue",
|
||||||
|
"primaryColor": "#3b5998",
|
||||||
|
"primaryColorHover": "#5475a3",
|
||||||
|
"primaryColorSuppl": "#8b9dc3",
|
||||||
|
"primaryColorPressed": "#2d4373"
|
||||||
|
},
|
||||||
|
"pink": {
|
||||||
|
"name": "粉色梦幻",
|
||||||
|
"label": "pink",
|
||||||
|
"primaryColor": "#e91e63",
|
||||||
|
"primaryColorHover": "#f06292",
|
||||||
|
"primaryColorSuppl": "#f06292",
|
||||||
|
"primaryColorPressed": "#c2185b"
|
||||||
|
},
|
||||||
|
"brown": {
|
||||||
|
"name": "深棕林荫",
|
||||||
|
"label": "brown",
|
||||||
|
"primaryColor": "#795548",
|
||||||
|
"primaryColorHover": "#8d6e63",
|
||||||
|
"primaryColorSuppl": "#8d6e63",
|
||||||
|
"primaryColorPressed": "#5d4037"
|
||||||
|
},
|
||||||
|
"indigo": {
|
||||||
|
"name": "星空靛蓝",
|
||||||
|
"label": "indigo",
|
||||||
|
"primaryColor": "#3f51b5",
|
||||||
|
"primaryColorHover": "#5c6bc0",
|
||||||
|
"primaryColorSuppl": "#5c6bc0",
|
||||||
|
"primaryColorPressed": "#3949ab"
|
||||||
|
},
|
||||||
|
"green": {
|
||||||
|
"name": "生命绿洲",
|
||||||
|
"label": "green",
|
||||||
|
"primaryColor": "#2ecc71",
|
||||||
|
"primaryColorHover": "#3ddc88",
|
||||||
|
"primaryColorSuppl": "#3ddc88",
|
||||||
|
"primaryColorPressed": "#27ae60"
|
||||||
|
},
|
||||||
|
"purple": {
|
||||||
|
"name": "皇室紫梦",
|
||||||
|
"label": "purple",
|
||||||
|
"primaryColor": "#9c27b0",
|
||||||
|
"primaryColorHover": "#ba68c8",
|
||||||
|
"primaryColorSuppl": "#ba68c8",
|
||||||
|
"primaryColorPressed": "#7b1fa2"
|
||||||
|
},
|
||||||
|
"yellow": {
|
||||||
|
"name": "金色阳光",
|
||||||
|
"label": "yellow",
|
||||||
|
"primaryColor": "#FBC02D",
|
||||||
|
"primaryColorHover": "#FFD54F",
|
||||||
|
"primaryColorSuppl": "#FFD54F",
|
||||||
|
"primaryColorPressed": "#FFC107"
|
||||||
|
},
|
||||||
|
"teal": {
|
||||||
|
"name": "海洋碧绿",
|
||||||
|
"label": "teal",
|
||||||
|
"primaryColor": "#009688",
|
||||||
|
"primaryColorHover": "#26a69a",
|
||||||
|
"primaryColorSuppl": "#26a69a",
|
||||||
|
"primaryColorPressed": "#00796b"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="searchInp">
|
<div class="searchInp">
|
||||||
<n-input
|
<n-input
|
||||||
:class="inputActive ? 'input focus' : 'input'"
|
:class="site.searchInputActive ? 'input focus' : 'input'"
|
||||||
:input-props="{ autoComplete: false }"
|
:input-props="{ autoComplete: false }"
|
||||||
|
:placeholder="$t('nav.search.placeholder')"
|
||||||
|
ref="searchInpRef"
|
||||||
round
|
round
|
||||||
clearable
|
clearable
|
||||||
placeholder="搜索音乐/视频"
|
|
||||||
v-model:value="inputValue"
|
v-model:value="inputValue"
|
||||||
@focus="inputFocus"
|
@focus="inputFocus"
|
||||||
@keydown="inputkeydown($event)"
|
@keydown="inputkeydown($event)"
|
||||||
@@ -14,7 +15,7 @@
|
|||||||
<template #prefix>
|
<template #prefix>
|
||||||
<n-icon
|
<n-icon
|
||||||
size="16"
|
size="16"
|
||||||
:color="inputActive ? '#f55e55' : ''"
|
:class="site.searchInputActive ? 'active' : ''"
|
||||||
:component="Search"
|
:component="Search"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -23,7 +24,7 @@
|
|||||||
<n-card
|
<n-card
|
||||||
class="list"
|
class="list"
|
||||||
v-show="
|
v-show="
|
||||||
inputActive &&
|
site.searchInputActive &&
|
||||||
!inputValue &&
|
!inputValue &&
|
||||||
(music.getSearchHistory[0] || searchData.hot[0])
|
(music.getSearchHistory[0] || searchData.hot[0])
|
||||||
"
|
"
|
||||||
@@ -36,7 +37,7 @@
|
|||||||
>
|
>
|
||||||
<div class="list-title">
|
<div class="list-title">
|
||||||
<n-icon size="16" :component="History" />
|
<n-icon size="16" :component="History" />
|
||||||
<n-text>搜索历史</n-text>
|
<n-text>{{ $t("nav.search.history") }}</n-text>
|
||||||
</div>
|
</div>
|
||||||
<n-space>
|
<n-space>
|
||||||
<n-tag
|
<n-tag
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
<n-icon size="16" :depth="3">
|
<n-icon size="16" :depth="3">
|
||||||
<DeleteFour theme="filled" />
|
<DeleteFour theme="filled" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<n-text :depth="3">删除搜索历史</n-text>
|
<n-text :depth="3">{{ $t("nav.search.delHistory") }}</n-text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hot-list" v-if="searchData.hot[0]">
|
<div class="hot-list" v-if="searchData.hot[0]">
|
||||||
@@ -60,7 +61,7 @@
|
|||||||
<n-icon size="16">
|
<n-icon size="16">
|
||||||
<Fire theme="filled" />
|
<Fire theme="filled" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<n-text>热搜榜</n-text>
|
<n-text>{{ $t("nav.search.hotList") }}</n-text>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="hot-item"
|
class="hot-item"
|
||||||
@@ -93,7 +94,7 @@
|
|||||||
<CollapseTransition easing="ease-in-out">
|
<CollapseTransition easing="ease-in-out">
|
||||||
<n-card
|
<n-card
|
||||||
class="list"
|
class="list"
|
||||||
v-show="inputActive && inputValue && searchData.suggest"
|
v-show="site.searchInputActive && inputValue && searchData.suggest"
|
||||||
content-style="padding: 0"
|
content-style="padding: 0"
|
||||||
>
|
>
|
||||||
<n-scrollbar>
|
<n-scrollbar>
|
||||||
@@ -102,19 +103,19 @@
|
|||||||
v-if="Object.keys(searchData.suggest).length === 0"
|
v-if="Object.keys(searchData.suggest).length === 0"
|
||||||
>
|
>
|
||||||
<n-icon size="16" :component="Find" />
|
<n-icon size="16" :component="Find" />
|
||||||
<span>暂无搜索结果</span>
|
<span>{{ $t("nav.search.noSuggestions") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="suggest-all" v-else>
|
<div class="suggest-all" v-else>
|
||||||
<div class="loading" v-show="!searchData.suggest.order">
|
<div class="loading" v-show="!searchData.suggest.order">
|
||||||
<n-icon size="16" :component="Find" />
|
<n-icon size="16" :component="Find" />
|
||||||
<span>努力搜索中</span>
|
<span>{{ $t("nav.search.searchTip") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="suggest-item" v-if="searchData.suggest.songs">
|
<div class="suggest-item" v-if="searchData.suggest.songs">
|
||||||
<div class="type">
|
<div class="type">
|
||||||
<n-icon size="18">
|
<n-icon size="18">
|
||||||
<MusicOne theme="filled" />
|
<MusicOne theme="filled" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<span class="name">单曲</span>
|
<span class="name">{{ $t("nav.search.songs") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
class="names"
|
class="names"
|
||||||
@@ -130,7 +131,7 @@
|
|||||||
<n-icon size="18">
|
<n-icon size="18">
|
||||||
<Voice theme="filled" />
|
<Voice theme="filled" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<span class="name">歌手</span>
|
<span class="name">{{ $t("nav.search.artists") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
class="names"
|
class="names"
|
||||||
@@ -145,7 +146,7 @@
|
|||||||
<n-icon size="18">
|
<n-icon size="18">
|
||||||
<RecordDisc theme="filled" />
|
<RecordDisc theme="filled" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<span class="name">专辑</span>
|
<span class="name">{{ $t("nav.search.albums") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
class="names"
|
class="names"
|
||||||
@@ -161,7 +162,7 @@
|
|||||||
<n-icon size="18">
|
<n-icon size="18">
|
||||||
<Record theme="filled" />
|
<Record theme="filled" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<span class="name">歌单</span>
|
<span class="name">{{ $t("nav.search.playlists") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
class="names"
|
class="names"
|
||||||
@@ -192,23 +193,26 @@ import {
|
|||||||
History,
|
History,
|
||||||
DeleteFour,
|
DeleteFour,
|
||||||
} from "@icon-park/vue-next";
|
} from "@icon-park/vue-next";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { musicStore, settingStore, siteStore } from "@/store";
|
||||||
import CollapseTransition from "@ivanv/vue-collapse-transition/src/CollapseTransition.vue";
|
import CollapseTransition from "@ivanv/vue-collapse-transition/src/CollapseTransition.vue";
|
||||||
import debounce from "@/utils/debounce";
|
import debounce from "@/utils/debounce";
|
||||||
import { useRouter } from "vue-router";
|
|
||||||
import { musicStore, settingStore } from "@/store";
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const music = musicStore();
|
const music = musicStore();
|
||||||
const setting = settingStore();
|
const setting = settingStore();
|
||||||
|
const site = siteStore();
|
||||||
|
|
||||||
// 输入框内容
|
// 输入框内容
|
||||||
const inputValue = ref(null);
|
const inputValue = ref(null);
|
||||||
|
const searchInpRef = ref(null);
|
||||||
// 输入框激活状态
|
|
||||||
const inputActive = ref(false);
|
|
||||||
|
|
||||||
// 输入框激活事件
|
// 输入框激活事件
|
||||||
const inputFocus = () => {
|
const inputFocus = () => {
|
||||||
inputActive.value = true;
|
searchInpRef.value?.focus();
|
||||||
|
site.searchInputActive = true;
|
||||||
music.showPlayList = false;
|
music.showPlayList = false;
|
||||||
getSearchHotData();
|
getSearchHotData();
|
||||||
};
|
};
|
||||||
@@ -274,7 +278,8 @@ const toSearch = (val, type) => {
|
|||||||
const inputkeydown = (e) => {
|
const inputkeydown = (e) => {
|
||||||
if (e.key === "Enter" && inputValue.value != null) {
|
if (e.key === "Enter" && inputValue.value != null) {
|
||||||
console.log("执行搜索" + inputValue.value.trim());
|
console.log("执行搜索" + inputValue.value.trim());
|
||||||
inputActive.value = false;
|
searchInpRef.value?.blur();
|
||||||
|
site.searchInputActive = false;
|
||||||
// 写入搜索历史
|
// 写入搜索历史
|
||||||
music.setSearchHistory(inputValue.value.trim());
|
music.setSearchHistory(inputValue.value.trim());
|
||||||
router.push({
|
router.push({
|
||||||
@@ -290,13 +295,13 @@ const inputkeydown = (e) => {
|
|||||||
const delHistory = () => {
|
const delHistory = () => {
|
||||||
$dialog.warning({
|
$dialog.warning({
|
||||||
class: "s-dialog",
|
class: "s-dialog",
|
||||||
title: "删除历史",
|
title: t("general.dialog.delete"),
|
||||||
content: "确认删除全部的搜索历史记录?",
|
content: t("nav.search.tip"),
|
||||||
positiveText: "删除",
|
positiveText: t("general.dialog.delete"),
|
||||||
negativeText: "取消",
|
negativeText: t("general.dialog.cancel"),
|
||||||
onPositiveClick: () => {
|
onPositiveClick: () => {
|
||||||
music.setSearchHistory(null, true);
|
music.setSearchHistory(null, true);
|
||||||
$message.success("删除成功");
|
$message.success(t("general.message.deleteSuccess"));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -306,13 +311,15 @@ onMounted(() => {
|
|||||||
getSearchHotData();
|
getSearchHotData();
|
||||||
// 搜索框失焦
|
// 搜索框失焦
|
||||||
document.addEventListener("click", () => {
|
document.addEventListener("click", () => {
|
||||||
inputActive.value = false;
|
searchInpRef.value?.blur();
|
||||||
|
site.searchInputActive = false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.removeEventListener("click", () => {
|
document.removeEventListener("click", () => {
|
||||||
inputActive.value = false;
|
searchInpRef.value?.blur();
|
||||||
|
site.searchInputActive = false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -333,7 +340,10 @@ watch(
|
|||||||
watch(
|
watch(
|
||||||
() => music.showPlayList,
|
() => music.showPlayList,
|
||||||
(val) => {
|
(val) => {
|
||||||
if (val) inputActive.value = false;
|
if (val) {
|
||||||
|
searchInpRef.value?.blur();
|
||||||
|
site.searchInputActive = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
@@ -353,11 +363,25 @@ watch(
|
|||||||
&.focus {
|
&.focus {
|
||||||
width: 280px;
|
width: 280px;
|
||||||
:deep(input) {
|
:deep(input) {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
@media (max-width: 450px) {
|
@media (max-width: 450px) {
|
||||||
width: 60vw;
|
width: 60vw;
|
||||||
}
|
}
|
||||||
|
@media (max-width: 380px) {
|
||||||
|
width: 54vw;
|
||||||
|
}
|
||||||
|
@media (max-width: 320px) {
|
||||||
|
width: 50vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.n-input__prefix) {
|
||||||
|
.n-icon {
|
||||||
|
transition: color 0.3s;
|
||||||
|
&.active {
|
||||||
|
color: var(--main-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.list {
|
.list {
|
||||||
@@ -369,44 +393,49 @@ watch(
|
|||||||
z-index: 3;
|
z-index: 3;
|
||||||
|
|
||||||
@media (max-width: 450px) {
|
@media (max-width: 450px) {
|
||||||
padding-top: 12px;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
top: 58px;
|
top: 58px;
|
||||||
right: 0;
|
right: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
border-radius: 0 0 8px 8px;
|
border-radius: 0 0 8px 8px;
|
||||||
|
z-index: 2006;
|
||||||
&::after {
|
&::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
content: "收起";
|
content: "×";
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
background-color: #efefef;
|
background-color: var(--n-action-color);
|
||||||
border-radius: 0 0 0 14px;
|
border-radius: 0 0 0 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.n-scrollbar) {
|
:deep(.n-scrollbar) {
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
@media (max-width: 450px) {
|
@media (max-width: 450px) {
|
||||||
max-height: calc(100vh - 130px);
|
max-height: calc(100vh - 60px);
|
||||||
min-height: calc(100vh - 130px);
|
min-height: calc(100vh - 60px);
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.n-scrollbar-rail {
|
.n-scrollbar-rail {
|
||||||
width: 4px;
|
width: 4px;
|
||||||
}
|
}
|
||||||
|
.n-scrollbar-container {
|
||||||
|
@media (max-width: 450px) {
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
.n-scrollbar-content {
|
.n-scrollbar-content {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
.list-title {
|
.list-title {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
.n-text {
|
.n-text {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -419,8 +448,8 @@ watch(
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $mainSecondaryColor;
|
background-color: var(--main-second-color);
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
@@ -467,7 +496,7 @@ watch(
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
&.hot {
|
&.hot {
|
||||||
color: #ff5656;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.title {
|
.title {
|
||||||
@@ -488,9 +517,9 @@ watch(
|
|||||||
transform: scale(0.9);
|
transform: scale(0.9);
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
background-color: $mainSecondaryColor;
|
background-color: var(--main-second-color);
|
||||||
border-color: $mainColor;
|
border-color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.tip {
|
.tip {
|
||||||
@@ -524,7 +553,7 @@ watch(
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
.type {
|
.type {
|
||||||
color: #ff5656;
|
color: var(--main-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -551,5 +580,6 @@ watch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
24
src/locale/index.js
Normal file
24
src/locale/index.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { createI18n } from "vue-i18n";
|
||||||
|
import { settingStore } from "@/store";
|
||||||
|
|
||||||
|
// 引入语言文件
|
||||||
|
import en from "./lang/en.js";
|
||||||
|
import zhCN from "./lang/zh-CN.js";
|
||||||
|
|
||||||
|
// 注册 i8n 实例
|
||||||
|
export const useI18n = (app) => {
|
||||||
|
const setting = settingStore();
|
||||||
|
const i18n = createI18n({
|
||||||
|
legacy: false,
|
||||||
|
globalInjection: true,
|
||||||
|
locale: setting.language,
|
||||||
|
fallbackLocale: "zh-CN",
|
||||||
|
messages: {
|
||||||
|
en,
|
||||||
|
"zh-CN": zhCN,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
app.config.globalProperties.$i18n = i18n;
|
||||||
|
app.use(i18n);
|
||||||
|
return i18n;
|
||||||
|
};
|
||||||
367
src/locale/lang/en.js
Normal file
367
src/locale/lang/en.js
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
export default {
|
||||||
|
// Navigation
|
||||||
|
nav: {
|
||||||
|
home: "Home",
|
||||||
|
discover: "Discover",
|
||||||
|
discoverChildren: {
|
||||||
|
playlists: "Playlists",
|
||||||
|
toplists: "Toplists",
|
||||||
|
artists: "Artists",
|
||||||
|
},
|
||||||
|
user: "Library",
|
||||||
|
userChildren: {
|
||||||
|
playlist: "My Playlists",
|
||||||
|
like: "Liked Playlists",
|
||||||
|
album: "Liked Albums",
|
||||||
|
artist: "Liked Artists",
|
||||||
|
cloud: "Music Cloud",
|
||||||
|
login: "Login",
|
||||||
|
results: "Music Library",
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
dark: "Dark Mode",
|
||||||
|
light: "Light Mode",
|
||||||
|
login: "Login",
|
||||||
|
logout: "Logout",
|
||||||
|
notLogin: "Not logged in",
|
||||||
|
notLoginSubtitle: "Log in for full functionality",
|
||||||
|
loginError: "Level information retrieval failed",
|
||||||
|
history: "Playback History",
|
||||||
|
setting: "Global Setting",
|
||||||
|
about: "About",
|
||||||
|
tip: "Confirm that you are logged out of the current user login ?",
|
||||||
|
success: "Log out successfully",
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
placeholder: "Search music/videos",
|
||||||
|
history: "Search History",
|
||||||
|
delHistory: "Delete Search History",
|
||||||
|
hotList: "Hot Searches",
|
||||||
|
searchTip: "Searching...",
|
||||||
|
noSuggestions: "No search results",
|
||||||
|
songs: "Songs",
|
||||||
|
artists: "Artists",
|
||||||
|
albums: "Albums",
|
||||||
|
playlists: "Playlists",
|
||||||
|
tip: "Confirm to delete all search history ?",
|
||||||
|
results: "search results",
|
||||||
|
},
|
||||||
|
officialList: "Official List",
|
||||||
|
globalList: "Global List",
|
||||||
|
},
|
||||||
|
// Home
|
||||||
|
home: {
|
||||||
|
title: {
|
||||||
|
exclusive: "Exclusive Recommend",
|
||||||
|
playlists: "Recommended Playlists",
|
||||||
|
artists: "Artist Recommend",
|
||||||
|
newAlbum: "New Albums",
|
||||||
|
more: "More",
|
||||||
|
},
|
||||||
|
modules: {
|
||||||
|
dailySongs: {
|
||||||
|
title: "Daily Recommend",
|
||||||
|
subtitle: "Updated at 6:00 am based on your music preference.",
|
||||||
|
},
|
||||||
|
radar: {
|
||||||
|
title: "Personal Radar",
|
||||||
|
subtitle: "Created for you based on your listening history.",
|
||||||
|
},
|
||||||
|
likeSong: {
|
||||||
|
title: "Liked Songs",
|
||||||
|
subtitle: "Discover your unique music taste.",
|
||||||
|
},
|
||||||
|
papersonalfm: {
|
||||||
|
title: "Personal FM",
|
||||||
|
subtitle: "Offline mode",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Login
|
||||||
|
login: {
|
||||||
|
login: "Login {name}",
|
||||||
|
qr: "QR",
|
||||||
|
phone: "Captcha",
|
||||||
|
email: "Email",
|
||||||
|
canNotUse: "This login method is temporarily unavailable",
|
||||||
|
loggedIn: "Already logged in, please don't log in again",
|
||||||
|
qrText1: "Please open APP and scan the code to login",
|
||||||
|
qrText2: "The current QR code is invalid, please scan it again",
|
||||||
|
qrText3: "Scan successfully, please confirm login in the client",
|
||||||
|
qrText4: "Login successfully",
|
||||||
|
qrText5: "Login error, please try again",
|
||||||
|
qrText6: "Login QR code generation failed",
|
||||||
|
},
|
||||||
|
// Menu
|
||||||
|
menu: {
|
||||||
|
play: "Play now",
|
||||||
|
nextPlay: "Play next",
|
||||||
|
add: "Add to playlist",
|
||||||
|
create: "Create new playlist",
|
||||||
|
download: "Download",
|
||||||
|
comment: "Comment",
|
||||||
|
mv: "Watch MV",
|
||||||
|
delete: "Remove from cloud drive",
|
||||||
|
deleteQuestion:
|
||||||
|
"Confirm to delete song {name} from Cloud Drive? This action cannot be undone!",
|
||||||
|
match: "Song Information Match",
|
||||||
|
search: "Search for same name",
|
||||||
|
copy: "Copy {name} {other}",
|
||||||
|
update: "Edit Playlist",
|
||||||
|
del: "Delete Playlist",
|
||||||
|
delQuestion:
|
||||||
|
"Are you sure you want to delete the playlist {name}? This action cannot be undone!",
|
||||||
|
unableToDelete: "Default playlist cannot be deleted",
|
||||||
|
collection: "Add {name} to Collection",
|
||||||
|
cancelCollection: "Remove {name} from Collection",
|
||||||
|
},
|
||||||
|
// General
|
||||||
|
general: {
|
||||||
|
type: {
|
||||||
|
hot: "Hot",
|
||||||
|
all: "All",
|
||||||
|
china: "China",
|
||||||
|
chinaMale: "China Male",
|
||||||
|
chinaFemale: "China Female",
|
||||||
|
chinaGroup: "China Group",
|
||||||
|
western: "Western",
|
||||||
|
westernMale: "Western Male",
|
||||||
|
westernFemale: "Western Female",
|
||||||
|
westernGroup: "Western Group",
|
||||||
|
japan: "Japan",
|
||||||
|
japanMale: "Japan Male",
|
||||||
|
japanFemale: "Japan Female",
|
||||||
|
japanGroup: "Japan Group",
|
||||||
|
korea: "Korea",
|
||||||
|
koreaMale: "Korea Male",
|
||||||
|
koreaFemale: "Korea Female",
|
||||||
|
koreaGroup: "Korea Group",
|
||||||
|
other: "Other",
|
||||||
|
quality: {
|
||||||
|
l: "Standard quality",
|
||||||
|
m: "Higher quality",
|
||||||
|
h: "Very high quality",
|
||||||
|
sq: "Lossless",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
song: "Song",
|
||||||
|
hotSong: "Top Songs",
|
||||||
|
playlist: "Playlist",
|
||||||
|
playlists: "Playlists",
|
||||||
|
videos: "Videos",
|
||||||
|
toplists: "Toplists",
|
||||||
|
artists: "Artists",
|
||||||
|
album: "Album",
|
||||||
|
link: "Link",
|
||||||
|
cloud: "Cloud",
|
||||||
|
songSize: "{size} songs",
|
||||||
|
albumSize: "{size} albums",
|
||||||
|
mvSize: "{size} MVs",
|
||||||
|
unknownSong: "Unknown Songs",
|
||||||
|
unknownArtist: "Unknown Artist",
|
||||||
|
itemCount: "Total {size} items",
|
||||||
|
goto: "Goto",
|
||||||
|
pageSizes: "{num} items/page",
|
||||||
|
desc: "{name} Introduction",
|
||||||
|
allDesc: "All Introduction",
|
||||||
|
allSong: "All Songs",
|
||||||
|
allPLaylist: "All Playlists",
|
||||||
|
artistDesc: "Singer Introduction",
|
||||||
|
play: "Play",
|
||||||
|
add: "Add",
|
||||||
|
comment: "Comment",
|
||||||
|
noKeywords: "Incomplete parameters",
|
||||||
|
goBack: "Back to previous page",
|
||||||
|
reload: "Reload",
|
||||||
|
allComments: "All Comments",
|
||||||
|
hotComments: "Hot Comments",
|
||||||
|
toCurrentlySong: "Go to currently playing song",
|
||||||
|
loadMore: "Load More",
|
||||||
|
playlistType: "Playlist Category",
|
||||||
|
bestPlaylist: "Best Playlist",
|
||||||
|
upCloud: "Cloud Upload",
|
||||||
|
cloudUsed: "Used {used}%, Remaining {remaining} G",
|
||||||
|
simiVideo: "Similar Videos",
|
||||||
|
restore: "Restore",
|
||||||
|
},
|
||||||
|
dialog: {
|
||||||
|
check: "Check",
|
||||||
|
cancel: "Cancel",
|
||||||
|
success: "Successfully",
|
||||||
|
failed: "Failed",
|
||||||
|
delete: "Confirm deletion",
|
||||||
|
match: "Match",
|
||||||
|
create: "Create",
|
||||||
|
download: "Downloading",
|
||||||
|
downloadingNow: "Downloading now",
|
||||||
|
editor: "Editor",
|
||||||
|
resetUp: "Re-Upload",
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
copySuccess: "Copied successfully",
|
||||||
|
copyFailure: "Copying failed",
|
||||||
|
addSuccess: "Add songs to playlist successfully",
|
||||||
|
addFailure: "Failed to add, please try again",
|
||||||
|
createSuccess: "Playlist creation success",
|
||||||
|
createFailed: "Playlist creation failed, please try again",
|
||||||
|
deleteSuccess: "Delete successfully",
|
||||||
|
deleteFailure: "Delete failed",
|
||||||
|
downloadSuccess: "{name} Download completed",
|
||||||
|
downloadFailure: "Download failed, please try another sound quality",
|
||||||
|
downloadError: "There was an error downloading, please try again",
|
||||||
|
upCloudSuccess: "{name} Upload successful",
|
||||||
|
upCloudFailure: "There was an error uploading the song",
|
||||||
|
upCloudError: "There was an error uploading the song, please try again",
|
||||||
|
upCloudNotHas: "Upload song details failed to get, try song match",
|
||||||
|
editorSuccess: "Edit successful",
|
||||||
|
editorFailed: "Edit failed, Please try again",
|
||||||
|
operationFailed: "Operation failed, please try again",
|
||||||
|
acquisitionFailed: "Failed to get",
|
||||||
|
notSupported: "Your browser does not support this operation",
|
||||||
|
jumpOut: "About to jump to off-site links",
|
||||||
|
needLogin: "Please login to your account to use",
|
||||||
|
needVip: "This operation requires a member account",
|
||||||
|
needCheck: "Please check your input",
|
||||||
|
isLoading: "Data loading",
|
||||||
|
vipTip:
|
||||||
|
"The current song is exclusive for VIP and can be listened to only",
|
||||||
|
playError: "Current song failed to play, skip to next song",
|
||||||
|
signInSuccess: "Sign In Success",
|
||||||
|
signInSuccessDesc: "Daily sign-in and Yunbei sign-in success",
|
||||||
|
signInFailed: "Failed to sign in",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// State
|
||||||
|
state: {
|
||||||
|
prohibition: "Prohibit access",
|
||||||
|
prohibitionDesc: "There are always doors that are closed to you",
|
||||||
|
notFound: "Resources do not exist",
|
||||||
|
notFoundDesc: "Why did you come here?",
|
||||||
|
error: "Server error",
|
||||||
|
errorDesc: "The server is broken, try again later",
|
||||||
|
},
|
||||||
|
// Other
|
||||||
|
other: {
|
||||||
|
sData: "Original song information",
|
||||||
|
asId: "Matching ID",
|
||||||
|
asIdDes: "Please enter the song ID to be matched",
|
||||||
|
noNeedMatch: "Consistent with original song ID, no correction required",
|
||||||
|
plaseCheck: "Please click to check first",
|
||||||
|
matchSuccess: "Song match successfully",
|
||||||
|
matchFailed: "Song match failed, please try again",
|
||||||
|
matchError: "Unusual song ID, can't match",
|
||||||
|
newPlaylistName: "Please enter a new playlist title",
|
||||||
|
setPrivacy: "Set as private playlist",
|
||||||
|
cloudTip:
|
||||||
|
"Currently for the cloud disk songs, downloaded files are the highest sound quality",
|
||||||
|
playlistEmpty: "There is no song, please go to the playlist to add",
|
||||||
|
plName: "Playlist Name",
|
||||||
|
plNameTip: "Please enter the playlist name",
|
||||||
|
plDes: "Playlist Description",
|
||||||
|
plDesTip: "Please enter the playlist description",
|
||||||
|
plTag: "Playlist Tags",
|
||||||
|
plTagTip: "Please enter the playlist tags",
|
||||||
|
lrcClicks: "Click on the selected lyrics to adjust the playback progress",
|
||||||
|
noSong: "No Song",
|
||||||
|
noHistory: "No play history",
|
||||||
|
justShow: "Show only the last {num} songs",
|
||||||
|
noDesc: "Too lazy, do not even write the introduction",
|
||||||
|
containing: "Song list containing this song",
|
||||||
|
loginExpired: "Login is disabled, please login again",
|
||||||
|
cleanAll: "Reset successful",
|
||||||
|
},
|
||||||
|
// Setting
|
||||||
|
setting: {
|
||||||
|
dev: "WIP",
|
||||||
|
main: "Basic",
|
||||||
|
player: "Player",
|
||||||
|
themeChange: "Theme color changed to {name}",
|
||||||
|
themeType: "Theme Color Selection",
|
||||||
|
themeTypeTip: "Change the site theme color, taking effect immediately",
|
||||||
|
themeTypeDialog: "Confirm to restore the full site theme color as default?",
|
||||||
|
language: "Language",
|
||||||
|
changeLanguage: "Language has been switched to {name}",
|
||||||
|
theme: "Light/Dark Mode",
|
||||||
|
themeAuto: "Follow System Light/Dark Mode",
|
||||||
|
autoSignIn: "Daily Check-in",
|
||||||
|
autoSignInTip: "Automatically perform daily check-in",
|
||||||
|
bannerShow: "Show Banner Image",
|
||||||
|
listClickMode: "List Click Mode",
|
||||||
|
listClickModeTip:
|
||||||
|
"This setting is ineffective on mobile, both click modes will be in effect",
|
||||||
|
dblclick: "Double-click to play",
|
||||||
|
click: "Single-click to play",
|
||||||
|
searchHistory: "Display Search History",
|
||||||
|
bottomLyricShow: "Display Bottom Lyrics",
|
||||||
|
bottomLyricShowTip:
|
||||||
|
"Whether to display lyrics at the bottom of the screen while playing",
|
||||||
|
songVolumeFade: "Song Volume Fade",
|
||||||
|
songVolumeFadeTip:
|
||||||
|
"Gradually fade in/out volume when stopping/starting playback",
|
||||||
|
memoryLastPlaybackPosition: "Remember Playback Position",
|
||||||
|
memoryLastPlaybackPositionTip:
|
||||||
|
"Resume last playback progress after refreshing the page",
|
||||||
|
songLevel: "Song Quality",
|
||||||
|
songLevelTip:
|
||||||
|
"Lossless quality and above require a Black Vinyl Club membership",
|
||||||
|
standard: "Standard",
|
||||||
|
higher: "Higher",
|
||||||
|
exhigh: "Extreme",
|
||||||
|
lossless: "Lossless",
|
||||||
|
hires: "Hi-Res",
|
||||||
|
jyeffect: "Whale Cloud Hi-Fi",
|
||||||
|
jymaster: "Whale Cloud Master",
|
||||||
|
useUnmServerShow: "Use UNM to play blocked songs",
|
||||||
|
useUnmServerShowTip1: "Whether to use UNM to replace blocked song links",
|
||||||
|
useUnmServerShowTip2:
|
||||||
|
"Please configure UNM-Server before using unblocking feature",
|
||||||
|
showLyricSetting: "Play Page Shortcut Settings",
|
||||||
|
showLyricSettingTip: "Show shortcut settings on the play page",
|
||||||
|
resetApp: "Reset the program",
|
||||||
|
resetAppTip:
|
||||||
|
"Try this if the program displays abnormally or if there is a problem",
|
||||||
|
resetAppWarning:
|
||||||
|
"Confirming reset to default? Your login status and custom settings will be lost!",
|
||||||
|
playerStyle: "Player Style",
|
||||||
|
playerStyleTip: "Style of the player's left-hand function area",
|
||||||
|
cover: "Cover Mode",
|
||||||
|
record: "Record Mode",
|
||||||
|
backgroundImageShow: "Play Background Style",
|
||||||
|
solid: "Solid Cover",
|
||||||
|
blur: "Blurry Cover",
|
||||||
|
backgroundImageShowTip1: "Display album cover in blurred mode",
|
||||||
|
backgroundImageShowTip2: "Extract album's main color as background color",
|
||||||
|
showTransl: "Show Lyric Translation",
|
||||||
|
showTranslTip: "Whether to display lyric translation when available",
|
||||||
|
showRoma: "Show Lyric Transliteration",
|
||||||
|
showRomaTip: "Whether to display lyric transliteration when available",
|
||||||
|
countDownShow: "Show Countdown Before Playing",
|
||||||
|
countDownShowTip: "Some songs may have incorrect countdown display",
|
||||||
|
showYrc: "Show Word-by-Word Lyrics",
|
||||||
|
showYrcTip: "Whether to display word-by-word lyrics when available",
|
||||||
|
showYrcAnimation: "Word by word lyric step animation",
|
||||||
|
showYrcAnimationTip:
|
||||||
|
"Whether to display verbatim lyrics step - by - step animation, more cost performance",
|
||||||
|
showYrcTransform: "Word for word lyrics come up",
|
||||||
|
showYrcTransformTip:
|
||||||
|
"Whether to display verbatim lyrics text floating animation",
|
||||||
|
lrcMousePause: "Intelligent Scroll Pause",
|
||||||
|
lrcMousePauseTip:
|
||||||
|
"Whether to pause scrolling when the mouse is over the lyrics area",
|
||||||
|
lyricsBlock: "Lyric Scrolling Position",
|
||||||
|
lyricsBlockTip: "The position where the lyrics are highlighted",
|
||||||
|
blockStart: "Near the Top",
|
||||||
|
blockCenter: "Horizontally Centered",
|
||||||
|
lyricsFontSize: "Lyric Text Size",
|
||||||
|
lyrics1: "Smallest",
|
||||||
|
lyrics2: "Default",
|
||||||
|
lyrics3: "Largest",
|
||||||
|
lyricsPosition: "Default Lyric Position",
|
||||||
|
positionLeft: "Left",
|
||||||
|
positionCenter: "Centered",
|
||||||
|
lyricsBlur: "Lyric Blur",
|
||||||
|
lyricsBlurTip:
|
||||||
|
"Blur lyrics other than the currently playing ones, experimental feature",
|
||||||
|
},
|
||||||
|
};
|
||||||
350
src/locale/lang/zh-CN.js
Normal file
350
src/locale/lang/zh-CN.js
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
export default {
|
||||||
|
// 导航栏
|
||||||
|
nav: {
|
||||||
|
home: "首页",
|
||||||
|
discover: "发现",
|
||||||
|
discoverChildren: {
|
||||||
|
playlists: "歌单",
|
||||||
|
toplists: "排行榜",
|
||||||
|
artists: "歌手",
|
||||||
|
},
|
||||||
|
user: "音乐库",
|
||||||
|
userChildren: {
|
||||||
|
login: "登录账号",
|
||||||
|
playlist: "我的歌单",
|
||||||
|
like: "收藏的歌单",
|
||||||
|
album: "收藏的专辑",
|
||||||
|
artist: "收藏的歌手",
|
||||||
|
cloud: "音乐云盘",
|
||||||
|
results: "的音乐库",
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
dark: "深色模式",
|
||||||
|
light: "浅色模式",
|
||||||
|
login: "登录账号",
|
||||||
|
logout: "退出登录",
|
||||||
|
notLogin: "未登录",
|
||||||
|
notLoginSubtitle: "登录后享受完整功能",
|
||||||
|
loginError: "等级信息获取失败",
|
||||||
|
history: "播放历史",
|
||||||
|
setting: "全局设置",
|
||||||
|
about: "关于本站",
|
||||||
|
tip: "确认退出当前用户登录?",
|
||||||
|
success: "成功退出登录",
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
placeholder: "搜索音乐/视频",
|
||||||
|
history: "搜索历史",
|
||||||
|
delHistory: "删除搜索历史",
|
||||||
|
hotList: "热搜榜",
|
||||||
|
searchTip: "努力搜索中",
|
||||||
|
noSuggestions: "暂无搜索结果",
|
||||||
|
songs: "单曲",
|
||||||
|
artists: "歌手",
|
||||||
|
albums: "专辑",
|
||||||
|
playlists: "歌单",
|
||||||
|
tip: "确认删除全部的搜索历史记录?",
|
||||||
|
results: "的搜索结果",
|
||||||
|
},
|
||||||
|
officialList: "官方榜",
|
||||||
|
globalList: "全球榜",
|
||||||
|
},
|
||||||
|
// 首页
|
||||||
|
home: {
|
||||||
|
title: {
|
||||||
|
exclusive: "专属推荐",
|
||||||
|
playlists: "推荐歌单",
|
||||||
|
artists: "歌手推荐",
|
||||||
|
newAlbum: "新碟上架",
|
||||||
|
more: "更多",
|
||||||
|
},
|
||||||
|
modules: {
|
||||||
|
dailySongs: {
|
||||||
|
title: "每日推荐",
|
||||||
|
subtitle: "根据你的音乐口味 · 每日 6:00 更新",
|
||||||
|
},
|
||||||
|
radar: {
|
||||||
|
title: "私人雷达",
|
||||||
|
subtitle: "根据听歌记录为你打造",
|
||||||
|
},
|
||||||
|
likeSong: {
|
||||||
|
title: "喜欢的音乐",
|
||||||
|
subtitle: "发现你独特的音乐品味",
|
||||||
|
},
|
||||||
|
papersonalfm: {
|
||||||
|
title: "私人FM",
|
||||||
|
subtitle: "未登录模式",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Login
|
||||||
|
login: {
|
||||||
|
login: "登录 {name}",
|
||||||
|
qr: "扫码登录",
|
||||||
|
phone: "验证码登录",
|
||||||
|
email: "邮箱登录",
|
||||||
|
canNotUse: "该登录方式暂时无法使用",
|
||||||
|
loggedIn: "已登录,请勿重复登录",
|
||||||
|
qrText1: "请打开云音乐 APP 扫码登录",
|
||||||
|
qrText2: "当前二维码已失效,请重新扫码",
|
||||||
|
qrText3: "扫描成功,请在客户端确认登录",
|
||||||
|
qrText4: "登录成功",
|
||||||
|
qrText5: "登录出错,请重试",
|
||||||
|
qrText6: "登录二维码生成失败",
|
||||||
|
},
|
||||||
|
// 菜单
|
||||||
|
menu: {
|
||||||
|
play: "立即播放",
|
||||||
|
nextPlay: "下一首播放",
|
||||||
|
add: "添加到歌单",
|
||||||
|
create: "新建歌单",
|
||||||
|
download: "歌曲下载",
|
||||||
|
comment: "前往评论区",
|
||||||
|
mv: "观看 MV",
|
||||||
|
delete: "从云盘中删除",
|
||||||
|
deleteQuestion: "确认从云盘中删除歌曲 {name} ?删除后将不可恢复!",
|
||||||
|
match: "歌曲信息匹配",
|
||||||
|
search: "同名搜索",
|
||||||
|
copy: "复制{name}{other}",
|
||||||
|
update: "编辑歌单",
|
||||||
|
del: "删除歌单",
|
||||||
|
delQuestion: "确认删除歌单 {name} ?删除后将不可恢复!",
|
||||||
|
unableToDelete: "默认歌单无法删除",
|
||||||
|
collection: "收藏{name}",
|
||||||
|
cancelCollection: "取消收藏{name}",
|
||||||
|
},
|
||||||
|
// 通用
|
||||||
|
general: {
|
||||||
|
type: {
|
||||||
|
hot: "热门",
|
||||||
|
all: "全部",
|
||||||
|
china: "华语",
|
||||||
|
chinaMale: "华语男",
|
||||||
|
chinaFemale: "华语女",
|
||||||
|
chinaGroup: "华语组合",
|
||||||
|
western: "欧美",
|
||||||
|
westernMale: "欧美男",
|
||||||
|
westernFemale: "欧美女",
|
||||||
|
westernGroup: "欧美组合",
|
||||||
|
japan: "日本",
|
||||||
|
japanMale: "日本男",
|
||||||
|
japanFemale: "日本女",
|
||||||
|
japanGroup: "日本组合",
|
||||||
|
korea: "韩国",
|
||||||
|
koreaMale: "韩国男",
|
||||||
|
koreaFemale: "韩国女",
|
||||||
|
koreaGroup: "韩国组合",
|
||||||
|
other: "其他",
|
||||||
|
quality: {
|
||||||
|
l: "标准音质",
|
||||||
|
m: "较高音质",
|
||||||
|
h: "极高音质",
|
||||||
|
sq: "无损音质",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
song: "歌曲",
|
||||||
|
hotSong: "热门歌曲",
|
||||||
|
playlist: "歌单",
|
||||||
|
videos: "视频",
|
||||||
|
playlists: "播放列表",
|
||||||
|
toplists: "排行榜",
|
||||||
|
artists: "歌手",
|
||||||
|
album: "专辑",
|
||||||
|
link: "链接",
|
||||||
|
cloud: "云盘",
|
||||||
|
songSize: "{size} 首",
|
||||||
|
albumSize: "{size} 张专辑",
|
||||||
|
mvSize: "{size} 个 MV",
|
||||||
|
unknownSong: "未知歌曲",
|
||||||
|
unknownArtist: "未知歌手",
|
||||||
|
itemCount: "共{size}项",
|
||||||
|
goto: "前往",
|
||||||
|
pageSizes: "{num}条/页",
|
||||||
|
desc: "{name}简介",
|
||||||
|
allDesc: "全部简介",
|
||||||
|
allSong: "全部歌曲",
|
||||||
|
allPLaylist: "全部歌单",
|
||||||
|
artistDesc: "歌手介绍",
|
||||||
|
play: "播放",
|
||||||
|
add: "添加",
|
||||||
|
comment: "评论",
|
||||||
|
noKeywords: "参数不完整",
|
||||||
|
goBack: "返回上一级",
|
||||||
|
reload: "重新载入",
|
||||||
|
allComments: "全部评论",
|
||||||
|
hotComments: "热门评论",
|
||||||
|
toCurrentlySong: "前往当前播放歌曲",
|
||||||
|
loadMore: "加载更多",
|
||||||
|
playlistType: "歌单分类",
|
||||||
|
bestPlaylist: "精品歌单",
|
||||||
|
upCloud: "云盘上传",
|
||||||
|
cloudUsed: "已用 {used}%,剩余 {remaining} G",
|
||||||
|
simiVideo: "相似视频",
|
||||||
|
restore: "恢复默认",
|
||||||
|
},
|
||||||
|
dialog: {
|
||||||
|
check: "检查",
|
||||||
|
cancel: "取消",
|
||||||
|
success: "成功",
|
||||||
|
failed: "失败",
|
||||||
|
delete: "确认删除",
|
||||||
|
match: "匹配歌曲",
|
||||||
|
create: "新建",
|
||||||
|
download: "下载",
|
||||||
|
downloadingNow: "正在下载",
|
||||||
|
editor: "编辑",
|
||||||
|
resetUp: "重新上传",
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
copySuccess: "复制成功",
|
||||||
|
copyFailure: "复制失败",
|
||||||
|
addSuccess: "添加歌曲至歌单成功",
|
||||||
|
addFailure: "添加失败,请重试",
|
||||||
|
createSuccess: "歌单新建成功",
|
||||||
|
createFailed: "歌单新建失败,请重试",
|
||||||
|
deleteSuccess: "删除成功",
|
||||||
|
deleteFailure: "删除失败",
|
||||||
|
downloadSuccess: "{name}下载完成",
|
||||||
|
downloadFailure: "下载失败,请尝试其他音质",
|
||||||
|
downloadError: "下载出现错误,请重试",
|
||||||
|
upCloudSuccess: "{name} 上传成功",
|
||||||
|
upCloudFailure: "歌曲上传出现错误",
|
||||||
|
upCloudError: "歌曲上传出错,请重试",
|
||||||
|
upCloudNotHas: "上传歌曲详细信息获取失败,可尝试歌曲匹配",
|
||||||
|
editorSuccess: "编辑成功",
|
||||||
|
editorFailed: "编辑失败,请重试",
|
||||||
|
operationFailed: "操作失败,请重试",
|
||||||
|
acquisitionFailed: "获取失败",
|
||||||
|
notSupported: "您的浏览器暂不支持该操作",
|
||||||
|
jumpOut: "即将跳转至站外链接",
|
||||||
|
needLogin: "请登录账号后使用",
|
||||||
|
needVip: "该操作需要账号为黑胶会员",
|
||||||
|
needCheck: "请检查您的输入",
|
||||||
|
isLoading: "数据加载中",
|
||||||
|
vipTip: "当前歌曲为 VIP 专享,仅可试听",
|
||||||
|
playError: "当前歌曲播放失败,跳至下一首",
|
||||||
|
signInSuccess: "签到成功",
|
||||||
|
signInSuccessDesc: "每日签到及云贝签到成功",
|
||||||
|
signInFailed: "签到失败",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// State
|
||||||
|
state: {
|
||||||
|
prohibition: "禁止访问",
|
||||||
|
prohibitionDesc: "总有些门是对你关闭的",
|
||||||
|
notFound: "资源不存在",
|
||||||
|
notFoundDesc: "怎么跑到这来了?",
|
||||||
|
error: "服务器错误",
|
||||||
|
errorDesc: "服务器寄了,等会再试吧",
|
||||||
|
},
|
||||||
|
// Other
|
||||||
|
other: {
|
||||||
|
sData: "原歌曲信息",
|
||||||
|
asId: "匹配的 ID",
|
||||||
|
asIdDes: "请输入要匹配的歌曲 ID",
|
||||||
|
noNeedMatch: "与原歌曲 ID 一致,无需匹配",
|
||||||
|
plaseCheck: "请先点击检查",
|
||||||
|
matchSuccess: "歌曲匹配成功",
|
||||||
|
matchFailed: "歌曲匹配失败,请重试",
|
||||||
|
matchError: "非正常歌曲 ID,无法匹配",
|
||||||
|
newPlaylistName: "请输入新歌单标题",
|
||||||
|
setPrivacy: "设置为隐私歌单",
|
||||||
|
cloudTip: "当前为云盘歌曲,下载的文件均为最高音质",
|
||||||
|
playlistEmpty: "暂无歌曲,请前往列表添加",
|
||||||
|
plName: "歌单名称",
|
||||||
|
plNameTip: "请输入歌单名称",
|
||||||
|
plDes: "歌单描述",
|
||||||
|
plDesTip: "请输入歌单描述",
|
||||||
|
plTag: "歌单标签",
|
||||||
|
plTagTip: "请输入歌单标签",
|
||||||
|
lrcClicks: "点击选中的歌词以调整播放进度",
|
||||||
|
noSong: "暂无歌曲",
|
||||||
|
noHistory: "暂无播放历史",
|
||||||
|
justShow: "仅显示最近 {num} 首",
|
||||||
|
noDesc: "太懒了吧,连简介都不写",
|
||||||
|
containing: "包含这首歌的歌单",
|
||||||
|
loginExpired: "登录已失效,请重新登录",
|
||||||
|
cleanAll: "重置成功",
|
||||||
|
},
|
||||||
|
setting: {
|
||||||
|
dev: "开发中功能",
|
||||||
|
main: "基础",
|
||||||
|
player: "播放器",
|
||||||
|
themeChange: "主题色更换为 {name}",
|
||||||
|
themeType: "主题色选择",
|
||||||
|
themeTypeTip: "更换全站主题色,即时生效",
|
||||||
|
themeTypeDialog: "确认恢复全站主题色为默认?",
|
||||||
|
language: "语言",
|
||||||
|
changeLanguage: "语言已切换为 {name}",
|
||||||
|
theme: "明暗模式",
|
||||||
|
themeAuto: "明暗模式跟随系统",
|
||||||
|
autoSignIn: "每日签到",
|
||||||
|
autoSignInTip: "是否自动进行每日签到",
|
||||||
|
bannerShow: "显示轮播图",
|
||||||
|
listClickMode: "列表点击方式",
|
||||||
|
listClickModeTip: "移动端该设置项无效,单击同时生效",
|
||||||
|
dblclick: "双击播放",
|
||||||
|
click: "单击播放",
|
||||||
|
searchHistory: "显示搜索历史",
|
||||||
|
bottomLyricShow: "显示底栏歌词",
|
||||||
|
bottomLyricShowTip: "是否在播放时显示歌词",
|
||||||
|
songVolumeFade: "歌曲渐入渐出",
|
||||||
|
songVolumeFadeTip: "是否在歌曲暂停 / 播放时渐入渐出",
|
||||||
|
memoryLastPlaybackPosition: "记忆播放位置",
|
||||||
|
memoryLastPlaybackPositionTip: "是否在刷新后恢复上次播放进度",
|
||||||
|
songLevel: "歌曲音质",
|
||||||
|
songLevelTip: "无损音质及以上需要您为黑胶会员",
|
||||||
|
standard: "标准",
|
||||||
|
higher: "较高",
|
||||||
|
exhigh: "极高",
|
||||||
|
lossless: "无损",
|
||||||
|
hires: "Hi-Res",
|
||||||
|
jyeffect: "鲸云臻音",
|
||||||
|
jymaster: "鲸云母带",
|
||||||
|
useUnmServerShow: "尝试替换无法播放的歌曲",
|
||||||
|
useUnmServerShowTip1: "是否使用 UNM 替换无法播放的歌曲链接",
|
||||||
|
useUnmServerShowTip2: "请配置 UNM-Server 后使用解灰功能",
|
||||||
|
showLyricSetting: "播放页快捷设置",
|
||||||
|
showLyricSettingTip: "是否在播放页面显示快捷设置",
|
||||||
|
resetApp: "程序重置",
|
||||||
|
resetAppTip: "若程序显示异常或出现问题时可尝试此操作",
|
||||||
|
resetAppWarning: "确认重置为默认状态?你的登录状态以及自定义设置都将丢失!",
|
||||||
|
playerStyle: "播放器样式",
|
||||||
|
playerStyleTip: "播放器左侧功能区样式",
|
||||||
|
cover: "封面模式",
|
||||||
|
record: "唱片模式",
|
||||||
|
backgroundImageShow: "播放背景样式",
|
||||||
|
solid: "封面主色",
|
||||||
|
blur: "封面模糊",
|
||||||
|
backgroundImageShowTip1: "将专辑封面模糊显示",
|
||||||
|
backgroundImageShowTip2: "提取专辑主色作为背景颜色",
|
||||||
|
showTransl: "显示歌词翻译",
|
||||||
|
showTranslTip: "是否在具有翻译歌词时显示",
|
||||||
|
showRoma: "显示歌词音译",
|
||||||
|
showRomaTip: "是否在具有音译歌词时显示",
|
||||||
|
countDownShow: "显示前奏等待",
|
||||||
|
countDownShowTip: "部分歌曲前奏可能存在显示错误",
|
||||||
|
showYrc: "显示逐字歌词",
|
||||||
|
showYrcTip: "是否在歌曲具有逐字歌词时显示",
|
||||||
|
showYrcAnimation: "逐字歌词步进动画",
|
||||||
|
showYrcAnimationTip: "是否显示逐字歌词步进动画,较耗费性能",
|
||||||
|
showYrcTransform: "逐字歌词上浮",
|
||||||
|
showYrcTransformTip: "是否显示逐字歌词文字上浮动画",
|
||||||
|
lrcMousePause: "智能暂停滚动",
|
||||||
|
lrcMousePauseTip: "鼠标移入歌词区域是否暂停滚动",
|
||||||
|
lyricsBlock: "歌词滚动位置",
|
||||||
|
lyricsBlockTip: "歌词高亮时所处的位置",
|
||||||
|
blockStart: "靠近顶部",
|
||||||
|
blockCenter: "水平居中",
|
||||||
|
lyricsFontSize: "歌词文本大小",
|
||||||
|
lyrics1: "最小",
|
||||||
|
lyrics2: "默认",
|
||||||
|
lyrics3: "最大",
|
||||||
|
lyricsPosition: "默认歌词位置",
|
||||||
|
positionLeft: "居左",
|
||||||
|
positionCenter: "居中",
|
||||||
|
lyricsBlur: "歌词模糊",
|
||||||
|
lyricsBlurTip: "除当前播放歌词外模糊显示,实验性功能",
|
||||||
|
},
|
||||||
|
};
|
||||||
33
src/main.js
33
src/main.js
@@ -1,9 +1,10 @@
|
|||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
import { createPinia } from "pinia";
|
import { createPinia } from "pinia";
|
||||||
|
import { useI18n } from "@/locale";
|
||||||
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
|
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
|
||||||
|
|
||||||
import App from "./App.vue";
|
import App from "@/App.vue";
|
||||||
import router from "./router";
|
import router from "@/router";
|
||||||
|
|
||||||
// 全局样式
|
// 全局样式
|
||||||
import "@/style/global.scss";
|
import "@/style/global.scss";
|
||||||
@@ -12,18 +13,34 @@ const app = createApp(App);
|
|||||||
|
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
pinia.use(piniaPluginPersistedstate);
|
pinia.use(piniaPluginPersistedstate);
|
||||||
app.use(pinia);
|
|
||||||
|
|
||||||
|
app.use(pinia);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
|
// 国际化
|
||||||
|
useI18n(app);
|
||||||
|
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
|
|
||||||
// PWA
|
if ("serviceWorker" in navigator) {
|
||||||
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
let pwaMessage = null;
|
||||||
// 弹出更新提醒
|
|
||||||
console.log("站点已更新,刷新后生效");
|
// 检测到更新提醒
|
||||||
|
navigator.serviceWorker.addEventListener("onupdatefound", () => {
|
||||||
|
console.info("发现站点更新,正在下载新版本");
|
||||||
|
pwaMessage = $message.loading("发现站点更新,正在下载新版本", {
|
||||||
|
closable: true,
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新完成提醒
|
||||||
|
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
||||||
|
console.info("站点已更新,刷新后生效");
|
||||||
|
if (pwaMessage) pwaMessage?.destroy();
|
||||||
$message.info("站点已更新,刷新后生效", {
|
$message.info("站点已更新,刷新后生效", {
|
||||||
closable: true,
|
closable: true,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const router = createRouter({
|
|||||||
// 路由守卫
|
// 路由守卫
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
const user = userStore();
|
const user = userStore();
|
||||||
$loadingBar.start();
|
if (typeof $loadingBar !== "undefined") $loadingBar.start();
|
||||||
// 判断是否需要登录
|
// 判断是否需要登录
|
||||||
if (to.meta.needLogin) {
|
if (to.meta.needLogin) {
|
||||||
getLoginState()
|
getLoginState()
|
||||||
@@ -45,7 +45,7 @@ router.beforeEach((to, from, next) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.afterEach(() => {
|
router.afterEach(() => {
|
||||||
$loadingBar.finish();
|
if (typeof $loadingBar !== "undefined") $loadingBar.finish();
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import useSettingDataStore from "./settingData";
|
import useSettingDataStore from "./settingData";
|
||||||
import useMusicDataStore from "./musicData";
|
import useMusicDataStore from "./musicData";
|
||||||
import useUserDataStore from "./userData";
|
import useUserDataStore from "./userData";
|
||||||
|
import useSiteDataStore from "./siteData";
|
||||||
|
|
||||||
export const settingStore = () => useSettingDataStore();
|
export const settingStore = () => useSettingDataStore();
|
||||||
export const musicStore = () => useMusicDataStore();
|
export const musicStore = () => useMusicDataStore();
|
||||||
export const userStore = () => useUserDataStore();
|
export const userStore = () => useUserDataStore();
|
||||||
|
export const siteStore = () => useSiteDataStore();
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { getSongTime, getSongPlayingTime } from "@/utils/timeTools.js";
|
import { nextTick } from "vue";
|
||||||
|
import { getSongTime, getSongPlayingTime } from "@/utils/timeTools";
|
||||||
import { getPersonalFm, setFmTrash } from "@/api/home";
|
import { getPersonalFm, setFmTrash } from "@/api/home";
|
||||||
import { getLikelist, setLikeSong } from "@/api/user";
|
import { getLikelist, setLikeSong } from "@/api/user";
|
||||||
import { getPlayListCatlist } from "@/api/playlist";
|
import { getPlayListCatlist } from "@/api/playlist";
|
||||||
import { userStore, settingStore } from "@/store";
|
import { userStore, settingStore } from "@/store";
|
||||||
import { NIcon } from "naive-ui";
|
import { NIcon } from "naive-ui";
|
||||||
import { PlayCycle, PlayOnce, ShuffleOne } from "@icon-park/vue-next";
|
import { PlayCycle, PlayOnce, ShuffleOne } from "@icon-park/vue-next";
|
||||||
|
import { soundStop, fadePlayOrPause } from "@/utils/Player";
|
||||||
import parseLyric from "@/utils/parseLyric";
|
import parseLyric from "@/utils/parseLyric";
|
||||||
|
import getLanguageData from "@/utils/getLanguageData";
|
||||||
|
|
||||||
const useMusicDataStore = defineStore("musicData", {
|
const useMusicDataStore = defineStore("musicData", {
|
||||||
state: () => {
|
state: () => {
|
||||||
@@ -20,7 +23,7 @@ const useMusicDataStore = defineStore("musicData", {
|
|||||||
// 播放状态
|
// 播放状态
|
||||||
playState: false,
|
playState: false,
|
||||||
// 当前歌曲播放链接
|
// 当前歌曲播放链接
|
||||||
playSongLink: null,
|
// playSongLink: null,
|
||||||
// 当前歌曲歌词数据
|
// 当前歌曲歌词数据
|
||||||
playSongLyric: {
|
playSongLyric: {
|
||||||
lrc: [],
|
lrc: [],
|
||||||
@@ -37,6 +40,15 @@ const useMusicDataStore = defineStore("musicData", {
|
|||||||
catList: {},
|
catList: {},
|
||||||
// 精品歌单分类
|
// 精品歌单分类
|
||||||
highqualityCatList: [],
|
highqualityCatList: [],
|
||||||
|
// 音乐频谱数据
|
||||||
|
spectrumsData: {
|
||||||
|
data: [],
|
||||||
|
audio: null,
|
||||||
|
analyser: null,
|
||||||
|
audioCtx: null,
|
||||||
|
},
|
||||||
|
// 是否正在加载数据
|
||||||
|
isLoadingSong: false,
|
||||||
// 持久化数据
|
// 持久化数据
|
||||||
persistData: {
|
persistData: {
|
||||||
// 搜索历史
|
// 搜索历史
|
||||||
@@ -116,10 +128,6 @@ const useMusicDataStore = defineStore("musicData", {
|
|||||||
getPlayState(state) {
|
getPlayState(state) {
|
||||||
return state.playState;
|
return state.playState;
|
||||||
},
|
},
|
||||||
// 获取播放链接
|
|
||||||
getPlaySongLink(state) {
|
|
||||||
return state.playSongLink;
|
|
||||||
},
|
|
||||||
// 获取喜欢音乐列表
|
// 获取喜欢音乐列表
|
||||||
getLikeList(state) {
|
getLikeList(state) {
|
||||||
return state.persistData.likeList;
|
return state.persistData.likeList;
|
||||||
@@ -142,8 +150,8 @@ const useMusicDataStore = defineStore("musicData", {
|
|||||||
setPersonalFmMode(value) {
|
setPersonalFmMode(value) {
|
||||||
this.persistData.personalFmMode = value;
|
this.persistData.personalFmMode = value;
|
||||||
if (value) {
|
if (value) {
|
||||||
this.playSongLink = null;
|
if (typeof $player !== "undefined") soundStop($player);
|
||||||
if (this.persistData.personalFmData.id) {
|
if (this.persistData.personalFmData?.id) {
|
||||||
this.persistData.playlists = [];
|
this.persistData.playlists = [];
|
||||||
this.persistData.playlists.push(this.persistData.personalFmData);
|
this.persistData.playlists.push(this.persistData.personalFmData);
|
||||||
this.persistData.playSongIndex = 0;
|
this.persistData.playSongIndex = 0;
|
||||||
@@ -175,7 +183,7 @@ const useMusicDataStore = defineStore("musicData", {
|
|||||||
} else {
|
} else {
|
||||||
this.persistData.personalFmData = fmData;
|
this.persistData.personalFmData = fmData;
|
||||||
if (this.persistData.personalFmMode) {
|
if (this.persistData.personalFmMode) {
|
||||||
this.playSongLink = null;
|
if (typeof $player !== "undefined") soundStop($player);
|
||||||
this.persistData.playlists = [];
|
this.persistData.playlists = [];
|
||||||
this.persistData.playlists.push(fmData);
|
this.persistData.playlists.push(fmData);
|
||||||
this.persistData.playSongIndex = 0;
|
this.persistData.playSongIndex = 0;
|
||||||
@@ -183,29 +191,28 @@ const useMusicDataStore = defineStore("musicData", {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$message.error("获取私人 FM 失败");
|
$message.error(getLanguageData("personalFmError"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("获取私人 FM 失败:" + err);
|
console.error(getLanguageData("personalFmError"), err);
|
||||||
$message.error("获取私人 FM 失败");
|
$message.error(getLanguageData("personalFmError"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 私人fm垃圾桶
|
// 私人fm垃圾桶
|
||||||
setFmDislike(id, tip = true) {
|
setFmDislike(id) {
|
||||||
const user = userStore();
|
const user = userStore();
|
||||||
if (user.userLogin) {
|
if (user.userLogin) {
|
||||||
setFmTrash(id).then((res) => {
|
setFmTrash(id).then((res) => {
|
||||||
if (res.code == 200) {
|
if (res.code == 200) {
|
||||||
if (tip) $message.success("已将该歌曲移除至垃圾桶");
|
|
||||||
this.persistData.personalFmMode = true;
|
this.persistData.personalFmMode = true;
|
||||||
this.setPlaySongIndex("next");
|
this.setPlaySongIndex("next");
|
||||||
} else {
|
} else {
|
||||||
$message.error("歌曲移除至垃圾桶失败");
|
$message.error(getLanguageData("fmTrashError"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$message.error("请登录账号后使用");
|
$message.error(getLanguageData("needLogin"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 更改喜欢列表
|
// 更改喜欢列表
|
||||||
@@ -232,30 +239,30 @@ const useMusicDataStore = defineStore("musicData", {
|
|||||||
setLikeSong(id, like).then((res) => {
|
setLikeSong(id, like).then((res) => {
|
||||||
if (res.code == 200) {
|
if (res.code == 200) {
|
||||||
list.push(id);
|
list.push(id);
|
||||||
$message.info("成功喜欢歌曲");
|
$message.info(getLanguageData("loveSong"));
|
||||||
} else {
|
} else {
|
||||||
$message.error("喜欢歌曲时发生错误");
|
$message.error(getLanguageData("loveSongError"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$message.info("我喜欢的列表中已存在该歌曲");
|
$message.info(getLanguageData("loveSongRepeat"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (exists) {
|
if (exists) {
|
||||||
setLikeSong(id, like).then((res) => {
|
setLikeSong(id, like).then((res) => {
|
||||||
if (res.code == 200) {
|
if (res.code == 200) {
|
||||||
list.splice(list.indexOf(id), 1);
|
list.splice(list.indexOf(id), 1);
|
||||||
$message.info("成功取消喜欢歌曲");
|
$message.info(getLanguageData("loveSongRemove"));
|
||||||
} else {
|
} else {
|
||||||
$message.error("取消喜欢歌曲时发生错误");
|
$message.error(getLanguageData("loveSongRemoveError"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$message.error("我喜欢的列表中未找到该歌曲");
|
$message.error(getLanguageData("loveSongNoFound"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$message.error("请登录账号后使用");
|
$message.error(getLanguageData("needLogin"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 更改音乐播放状态
|
// 更改音乐播放状态
|
||||||
@@ -270,19 +277,13 @@ const useMusicDataStore = defineStore("musicData", {
|
|||||||
setPlayBarState(value) {
|
setPlayBarState(value) {
|
||||||
this.showPlayBar = value;
|
this.showPlayBar = value;
|
||||||
},
|
},
|
||||||
// 更改歌曲播放链接
|
|
||||||
setPlaySongLink(value) {
|
|
||||||
this.playSongLink = value;
|
|
||||||
},
|
|
||||||
// 更改播放列表模式
|
// 更改播放列表模式
|
||||||
setPlayListMode(value) {
|
setPlayListMode(value) {
|
||||||
console.log(value);
|
|
||||||
this.persistData.playListMode = value;
|
this.persistData.playListMode = value;
|
||||||
},
|
},
|
||||||
// 添加歌单至播放列表
|
// 添加歌单至播放列表
|
||||||
setPlaylists(value) {
|
setPlaylists(value) {
|
||||||
this.persistData.playlists = value;
|
this.persistData.playlists = value.slice();
|
||||||
console.log(`已添加${value.length}首歌曲至播放列表`);
|
|
||||||
},
|
},
|
||||||
// 更改每日推荐数据
|
// 更改每日推荐数据
|
||||||
setDailySongs(value) {
|
setDailySongs(value) {
|
||||||
@@ -301,8 +302,6 @@ const useMusicDataStore = defineStore("musicData", {
|
|||||||
mv: v.mv ? v.mv : null,
|
mv: v.mv ? v.mv : null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
$message.error("处理每日推荐发生错误");
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 歌词处理
|
// 歌词处理
|
||||||
@@ -311,8 +310,8 @@ const useMusicDataStore = defineStore("musicData", {
|
|||||||
try {
|
try {
|
||||||
this.playSongLyric = parseLyric(value);
|
this.playSongLyric = parseLyric(value);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$message.error("歌词处理出错");
|
$message.error(getLanguageData("getLrcError"));
|
||||||
console.error("歌词处理出错:" + err);
|
console.error(getLanguageData("getLrcError"), err);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("该歌曲暂无歌词");
|
console.log("该歌曲暂无歌词");
|
||||||
@@ -324,9 +323,13 @@ const useMusicDataStore = defineStore("musicData", {
|
|||||||
this.persistData.playSongTime.currentTime = value.currentTime;
|
this.persistData.playSongTime.currentTime = value.currentTime;
|
||||||
this.persistData.playSongTime.duration = value.duration;
|
this.persistData.playSongTime.duration = value.duration;
|
||||||
// 计算进度条应该移动的距离
|
// 计算进度条应该移动的距离
|
||||||
|
if (value.duration === 0) {
|
||||||
|
this.persistData.playSongTime.barMoveDistance = 0;
|
||||||
|
} else {
|
||||||
this.persistData.playSongTime.barMoveDistance = Number(
|
this.persistData.playSongTime.barMoveDistance = Number(
|
||||||
(value.currentTime / (value.duration / 100)).toFixed(2)
|
(value.currentTime / (value.duration / 100)).toFixed(2)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
if (this.persistData.playSongTime.barMoveDistance) {
|
if (this.persistData.playSongTime.barMoveDistance) {
|
||||||
// 歌曲播放进度转换
|
// 歌曲播放进度转换
|
||||||
this.persistData.playSongTime.songTimePlayed = getSongPlayingTime(
|
this.persistData.playSongTime.songTimePlayed = getSongPlayingTime(
|
||||||
@@ -347,24 +350,26 @@ const useMusicDataStore = defineStore("musicData", {
|
|||||||
setPlaySongMode() {
|
setPlaySongMode() {
|
||||||
if (this.persistData.playSongMode === "normal") {
|
if (this.persistData.playSongMode === "normal") {
|
||||||
this.persistData.playSongMode = "random";
|
this.persistData.playSongMode = "random";
|
||||||
$message.info("随机播放", {
|
$message.info(getLanguageData("random"), {
|
||||||
icon: () => h(NIcon, null, { default: () => h(ShuffleOne) }),
|
icon: () => h(NIcon, null, { default: () => h(ShuffleOne) }),
|
||||||
});
|
});
|
||||||
} else if (this.persistData.playSongMode === "random") {
|
} else if (this.persistData.playSongMode === "random") {
|
||||||
this.persistData.playSongMode = "single";
|
this.persistData.playSongMode = "single";
|
||||||
$message.info("单曲循环", {
|
$message.info(getLanguageData("single"), {
|
||||||
icon: () => h(NIcon, null, { default: () => h(PlayOnce) }),
|
icon: () => h(NIcon, null, { default: () => h(PlayOnce) }),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.persistData.playSongMode = "normal";
|
this.persistData.playSongMode = "normal";
|
||||||
$message.info("列表循环", {
|
$message.info(getLanguageData("normal"), {
|
||||||
icon: () => h(NIcon, null, { default: () => h(PlayCycle) }),
|
icon: () => h(NIcon, null, { default: () => h(PlayCycle) }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 上下曲调整
|
// 上下曲调整
|
||||||
setPlaySongIndex(type) {
|
setPlaySongIndex(type) {
|
||||||
this.playState = false;
|
if (typeof $player === "undefined") return false;
|
||||||
|
soundStop($player);
|
||||||
|
this.isLoadingSong = true;
|
||||||
if (this.persistData.personalFmMode) {
|
if (this.persistData.personalFmMode) {
|
||||||
this.setPersonalFmData();
|
this.setPersonalFmData();
|
||||||
} else {
|
} else {
|
||||||
@@ -377,21 +382,26 @@ const useMusicDataStore = defineStore("musicData", {
|
|||||||
this.persistData.playSongIndex = Math.floor(
|
this.persistData.playSongIndex = Math.floor(
|
||||||
Math.random() * listLength
|
Math.random() * listLength
|
||||||
);
|
);
|
||||||
} else if (listMode === "single" && $player) {
|
} else if (listMode === "single" && typeof $player !== "undefined") {
|
||||||
$player.currentTime = 0;
|
soundStop($player);
|
||||||
|
fadePlayOrPause($player, "play", this.persistData.playVolume);
|
||||||
} else {
|
} else {
|
||||||
$message.error("播放出错,请刷新后重试");
|
$message.error(getLanguageData("playError"));
|
||||||
}
|
}
|
||||||
// 判断是否处于最后/第一首
|
// 判断是否处于最后/第一首
|
||||||
if (this.persistData.playSongIndex < 0) {
|
if (this.persistData.playSongIndex < 0) {
|
||||||
this.persistData.playSongIndex = listLength - 1;
|
this.persistData.playSongIndex = listLength - 1;
|
||||||
} else if (this.persistData.playSongIndex >= listLength) {
|
} else if (this.persistData.playSongIndex >= listLength) {
|
||||||
this.persistData.playSongIndex = 0;
|
this.persistData.playSongIndex = 0;
|
||||||
|
soundStop($player);
|
||||||
|
fadePlayOrPause($player, "play", this.persistData.playVolume);
|
||||||
}
|
}
|
||||||
if (listMode !== "single") {
|
if (listMode !== "single" && listLength > 1) {
|
||||||
this.playSongLink = null;
|
soundStop($player);
|
||||||
}
|
}
|
||||||
this.playState = true;
|
nextTick().then(() => {
|
||||||
|
this.setPlayState(true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 添加歌曲至播放列表
|
// 添加歌曲至播放列表
|
||||||
@@ -405,11 +415,12 @@ const useMusicDataStore = defineStore("musicData", {
|
|||||||
value.id !==
|
value.id !==
|
||||||
this.persistData.playlists[this.persistData.playSongIndex]?.id
|
this.persistData.playlists[this.persistData.playSongIndex]?.id
|
||||||
) {
|
) {
|
||||||
console.log("播放歌曲与上一次不一致");
|
console.log("Play a song that is not the same as the last one");
|
||||||
this.playSongLink = null;
|
if (typeof $player !== "undefined") soundStop($player);
|
||||||
|
this.isLoadingSong = true;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("出现错误:" + error);
|
console.error("Error:" + error);
|
||||||
}
|
}
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
this.persistData.playSongIndex = index;
|
this.persistData.playSongIndex = index;
|
||||||
@@ -417,10 +428,13 @@ const useMusicDataStore = defineStore("musicData", {
|
|||||||
this.persistData.playlists.push(value);
|
this.persistData.playlists.push(value);
|
||||||
this.persistData.playSongIndex = this.persistData.playlists.length - 1;
|
this.persistData.playSongIndex = this.persistData.playlists.length - 1;
|
||||||
}
|
}
|
||||||
play ? (this.playState = true) : null;
|
play ? this.setPlayState(true) : null;
|
||||||
},
|
},
|
||||||
// 在当前播放歌曲后添加
|
// 在当前播放歌曲后添加
|
||||||
addSongToNext(value) {
|
addSongToNext(value) {
|
||||||
|
// 更改播放模式为列表循环
|
||||||
|
this.persistData.playSongMode = "normal";
|
||||||
|
// 查找是否存在于播放列表
|
||||||
const index = this.persistData.playlists.findIndex(
|
const index = this.persistData.playlists.findIndex(
|
||||||
(o) => o.id === value.id
|
(o) => o.id === value.id
|
||||||
);
|
);
|
||||||
@@ -442,16 +456,25 @@ const useMusicDataStore = defineStore("musicData", {
|
|||||||
value
|
value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$message.success(value.name + " 已添加至下一曲播放");
|
$message.success(value.name + " " + getLanguageData("addSongToNext"));
|
||||||
},
|
},
|
||||||
// 播放列表移除歌曲
|
// 播放列表移除歌曲
|
||||||
removeSong(index) {
|
removeSong(index) {
|
||||||
|
if (typeof $player === "undefined") return false;
|
||||||
const name = this.persistData.playlists[index].name;
|
const name = this.persistData.playlists[index].name;
|
||||||
if (index < this.persistData.playSongIndex) {
|
if (index < this.persistData.playSongIndex) {
|
||||||
this.persistData.playSongIndex--;
|
this.persistData.playSongIndex--;
|
||||||
|
} else if (index === this.persistData.playSongIndex) {
|
||||||
|
// 如果删除的是当前播放歌曲,则重置播放器
|
||||||
|
soundStop($player);
|
||||||
}
|
}
|
||||||
$message.success(name + " 已从播放列表中移除");
|
$message.success(name + " " + getLanguageData("removeSong"));
|
||||||
this.persistData.playlists.splice(index, 1);
|
this.persistData.playlists.splice(index, 1);
|
||||||
|
// 检查当前播放歌曲的索引是否超出了列表范围
|
||||||
|
if (this.persistData.playSongIndex >= this.persistData.playlists.length) {
|
||||||
|
this.persistData.playSongIndex = 0;
|
||||||
|
soundStop($player);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// 获取歌单分类
|
// 获取歌单分类
|
||||||
setCatList(highquality = false) {
|
setCatList(highquality = false) {
|
||||||
@@ -459,7 +482,7 @@ const useMusicDataStore = defineStore("musicData", {
|
|||||||
if (res.code == 200) {
|
if (res.code == 200) {
|
||||||
this.catList = res;
|
this.catList = res;
|
||||||
} else {
|
} else {
|
||||||
$message.error("歌单分类获取失败");
|
$message.error(getLanguageData("getDataError"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (highquality) {
|
if (highquality) {
|
||||||
@@ -467,7 +490,7 @@ const useMusicDataStore = defineStore("musicData", {
|
|||||||
if (res.code == 200) {
|
if (res.code == 200) {
|
||||||
this.highqualityCatList = res.tags;
|
this.highqualityCatList = res.tags;
|
||||||
} else {
|
} else {
|
||||||
$message.error("精品歌单分类获取失败");
|
$message.error(getLanguageData("getDataError"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { NIcon } from "naive-ui";
|
import { NIcon } from "naive-ui";
|
||||||
import { WbSunnyFilled, DarkModeFilled } from "@vicons/material";
|
import { WbSunnyFilled, DarkModeFilled } from "@vicons/material";
|
||||||
|
import getLanguageData from "@/utils/getLanguageData";
|
||||||
|
|
||||||
const useSettingDataStore = defineStore("settingData", {
|
const useSettingDataStore = defineStore("settingData", {
|
||||||
state: () => {
|
state: () => {
|
||||||
@@ -8,6 +9,8 @@ const useSettingDataStore = defineStore("settingData", {
|
|||||||
// 全局主题
|
// 全局主题
|
||||||
theme: "light",
|
theme: "light",
|
||||||
themeAuto: true,
|
themeAuto: true,
|
||||||
|
themeType: "red",
|
||||||
|
themeData: {},
|
||||||
// 搜索历史
|
// 搜索历史
|
||||||
searchHistory: true,
|
searchHistory: true,
|
||||||
// 轮播图显示
|
// 轮播图显示
|
||||||
@@ -22,8 +25,14 @@ const useSettingDataStore = defineStore("settingData", {
|
|||||||
bottomLyricShow: true,
|
bottomLyricShow: true,
|
||||||
// 是否显示逐字歌词
|
// 是否显示逐字歌词
|
||||||
showYrc: true,
|
showYrc: true,
|
||||||
|
// 是否显示逐字歌词动画
|
||||||
|
showYrcAnimation: true,
|
||||||
|
// 是否显示逐字歌词上浮
|
||||||
|
showYrcTransform: false,
|
||||||
// 是否显示歌词翻译
|
// 是否显示歌词翻译
|
||||||
showTransl: true,
|
showTransl: true,
|
||||||
|
// 是否显示歌词音译
|
||||||
|
showRoma: true,
|
||||||
// 歌曲音质
|
// 歌曲音质
|
||||||
songLevel: "exhigh",
|
songLevel: "exhigh",
|
||||||
// 歌词位置
|
// 歌词位置
|
||||||
@@ -37,9 +46,23 @@ const useSettingDataStore = defineStore("settingData", {
|
|||||||
// 音乐频谱
|
// 音乐频谱
|
||||||
musicFrequency: false,
|
musicFrequency: false,
|
||||||
// 鼠标移入歌词区域暂停滚动
|
// 鼠标移入歌词区域暂停滚动
|
||||||
lrcMousePause: true,
|
lrcMousePause: false,
|
||||||
// 是否使用网易云解灰
|
// 是否使用网易云解灰
|
||||||
useUnmServer: true,
|
useUnmServer: true,
|
||||||
|
// 播放背景是否显示图片
|
||||||
|
backgroundImageShow: "blur",
|
||||||
|
// 是否显示前奏等待
|
||||||
|
countDownShow: true,
|
||||||
|
// 是否显示歌词设置
|
||||||
|
showLyricSetting: false,
|
||||||
|
// 歌曲渐入渐出
|
||||||
|
songVolumeFade: true,
|
||||||
|
// 列表默认数量
|
||||||
|
listNumber: 30,
|
||||||
|
// 记忆上次播放位置
|
||||||
|
memoryLastPlaybackPosition: true,
|
||||||
|
// 语言
|
||||||
|
language: "zh-CN",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
@@ -47,16 +70,14 @@ const useSettingDataStore = defineStore("settingData", {
|
|||||||
getSiteTheme(state) {
|
getSiteTheme(state) {
|
||||||
return state.theme;
|
return state.theme;
|
||||||
},
|
},
|
||||||
// 获取是否开启翻译
|
|
||||||
getShowTransl(state) {
|
|
||||||
return state.showTransl;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
// 切换明暗模式
|
// 切换明暗模式
|
||||||
setSiteTheme(value) {
|
setSiteTheme(value) {
|
||||||
const isLightMode = value === "light";
|
const isLightMode = value === "light";
|
||||||
const message = isLightMode ? "已切换至浅色模式" : "已切换至深色模式";
|
const message = isLightMode
|
||||||
|
? getLanguageData("lightMode")
|
||||||
|
: getLanguageData("darkMode");
|
||||||
const icon = isLightMode ? WbSunnyFilled : DarkModeFilled;
|
const icon = isLightMode ? WbSunnyFilled : DarkModeFilled;
|
||||||
this.theme = value;
|
this.theme = value;
|
||||||
$message.info(message, {
|
$message.info(message, {
|
||||||
|
|||||||
25
src/store/siteData.js
Normal file
25
src/store/siteData.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
const useSiteDataStore = defineStore("siteData", {
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
// 站点标题
|
||||||
|
siteTitle: import.meta.env.VITE_SITE_TITLE,
|
||||||
|
// 封面主题色
|
||||||
|
songPicColor: "rgb(128,128,128)",
|
||||||
|
// 搜索框激活状态
|
||||||
|
searchInputActive: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getters: {},
|
||||||
|
actions: {},
|
||||||
|
// 开启数据持久化
|
||||||
|
persist: [
|
||||||
|
{
|
||||||
|
storage: localStorage,
|
||||||
|
paths: [""],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export default useSiteDataStore;
|
||||||
@@ -7,13 +7,12 @@ import {
|
|||||||
getUserArtistlist,
|
getUserArtistlist,
|
||||||
getUserAlbum,
|
getUserAlbum,
|
||||||
} from "@/api/user";
|
} from "@/api/user";
|
||||||
import { formatNumber, getLongTime } from "@/utils/timeTools.js";
|
import { formatNumber, getLongTime } from "@/utils/timeTools";
|
||||||
|
import getLanguageData from "@/utils/getLanguageData";
|
||||||
|
|
||||||
const useUserDataStore = defineStore("userData", {
|
const useUserDataStore = defineStore("userData", {
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
// 站点标题
|
|
||||||
siteTitle: "SPlayer",
|
|
||||||
// 用户登录状态
|
// 用户登录状态
|
||||||
userLogin: false,
|
userLogin: false,
|
||||||
// 用户 cookie
|
// 用户 cookie
|
||||||
@@ -70,10 +69,6 @@ const useUserDataStore = defineStore("userData", {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
// 更改站点标题
|
|
||||||
setSiteTitle(value) {
|
|
||||||
this.siteTitle = value;
|
|
||||||
},
|
|
||||||
// 更改 cookie
|
// 更改 cookie
|
||||||
setCookie(value) {
|
setCookie(value) {
|
||||||
window.localStorage.setItem("cookie", value);
|
window.localStorage.setItem("cookie", value);
|
||||||
@@ -92,11 +87,11 @@ const useUserDataStore = defineStore("userData", {
|
|||||||
console.log(res);
|
console.log(res);
|
||||||
this.userOtherData.level = res[0].data;
|
this.userOtherData.level = res[0].data;
|
||||||
this.userOtherData.subcount = res[1];
|
this.userOtherData.subcount = res[1];
|
||||||
this.setUserPlayLists();
|
// this.setUserPlayLists();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error("获取用户详情失败:" + err);
|
console.error(getLanguageData("getDataError"), err);
|
||||||
$message.error("获取用户详情失败,请刷新后重试");
|
$message.error(getLanguageData("getDataError"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -107,20 +102,19 @@ const useUserDataStore = defineStore("userData", {
|
|||||||
this.userData = {};
|
this.userData = {};
|
||||||
this.userOtherData = {};
|
this.userOtherData = {};
|
||||||
localStorage.removeItem("cookie");
|
localStorage.removeItem("cookie");
|
||||||
|
// 调用退出登录接口
|
||||||
userLogOut();
|
userLogOut();
|
||||||
},
|
},
|
||||||
// 更改用户歌单
|
// 更改用户歌单
|
||||||
async setUserPlayLists() {
|
async setUserPlayLists(callback) {
|
||||||
if (this.userLogin) {
|
if (this.userLogin) {
|
||||||
try {
|
try {
|
||||||
if (!Object.keys(this.userOtherData).length) {
|
|
||||||
this.setUserOtherData();
|
|
||||||
} else {
|
|
||||||
this.userPlayLists.isLoading = true;
|
this.userPlayLists.isLoading = true;
|
||||||
const { userId } = this.userData;
|
const { userId } = this.userData;
|
||||||
const { subcount } = this.userOtherData;
|
// const { subcount } = this.userOtherData;
|
||||||
const number =
|
const { createdPlaylistCount, subPlaylistCount } =
|
||||||
subcount.createdPlaylistCount + subcount.subPlaylistCount;
|
await getUserSubcount();
|
||||||
|
const number = createdPlaylistCount + subPlaylistCount ?? 30;
|
||||||
const res = await getUserPlaylist(userId, number);
|
const res = await getUserPlaylist(userId, number);
|
||||||
if (res.playlist) {
|
if (res.playlist) {
|
||||||
this.userPlayLists = {
|
this.userPlayLists = {
|
||||||
@@ -150,19 +144,23 @@ const useUserDataStore = defineStore("userData", {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (callback && typeof callback === "function") {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
this.userPlayLists.isLoading = false;
|
this.userPlayLists.isLoading = false;
|
||||||
} else {
|
} else {
|
||||||
this.userPlayLists.isLoading = false;
|
this.userPlayLists.isLoading = false;
|
||||||
$message.error("用户歌单为空");
|
$message.info(getLanguageData("getDaraEmpty"));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.userPlayLists.isLoading = false;
|
this.userPlayLists.isLoading = false;
|
||||||
console.error("获取用户歌单时出现错误:" + err);
|
if (this.userLogin) {
|
||||||
$message.error("获取用户歌单时出现错误,请刷新后重试");
|
console.error(getLanguageData("getDataError"), err);
|
||||||
|
$message.error(getLanguageData("getDataError"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$message.error("请登录账号后使用");
|
$message.error(getLanguageData("needLogin"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 更改用户收藏歌手
|
// 更改用户收藏歌手
|
||||||
@@ -182,25 +180,27 @@ const useUserDataStore = defineStore("userData", {
|
|||||||
size: v.musicSize,
|
size: v.musicSize,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (typeof callback === "function") {
|
if (callback && typeof callback === "function") {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
this.userArtistLists.isLoading = false;
|
this.userArtistLists.isLoading = false;
|
||||||
} else {
|
} else {
|
||||||
this.userArtistLists.isLoading = false;
|
this.userArtistLists.isLoading = false;
|
||||||
$message.error("用户收藏歌手为空");
|
$message.info(getLanguageData("getDaraEmpty"));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.userArtistLists.isLoading = false;
|
this.userArtistLists.isLoading = false;
|
||||||
console.error("用户收藏歌手获取失败:" + err);
|
if (this.userLogin) {
|
||||||
$message.error("用户收藏歌手获取失败,请刷新后重试");
|
console.error(getLanguageData("getDataError"), err);
|
||||||
|
$message.error(getLanguageData("getDataError"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$message.error("请登录账号后使用");
|
$message.error(getLanguageData("needLogin"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 更改用户收藏专辑
|
// 更改用户收藏专辑
|
||||||
async setUserAlbumLists() {
|
async setUserAlbumLists(callback) {
|
||||||
if (this.userLogin) {
|
if (this.userLogin) {
|
||||||
try {
|
try {
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
@@ -222,15 +222,20 @@ const useUserDataStore = defineStore("userData", {
|
|||||||
offset += 30;
|
offset += 30;
|
||||||
console.log(totalCount, offset, this.userAlbum.list);
|
console.log(totalCount, offset, this.userAlbum.list);
|
||||||
}
|
}
|
||||||
|
if (callback && typeof callback === "function") {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
this.userAlbum.isLoading = false;
|
this.userAlbum.isLoading = false;
|
||||||
this.userAlbum.has = true;
|
this.userAlbum.has = true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.userAlbum.isLoading = false;
|
this.userAlbum.isLoading = false;
|
||||||
console.error("用户收藏专辑获取失败:" + err);
|
if (this.userLogin) {
|
||||||
$message.error("用户收藏专辑获取失败,请刷新后重试");
|
console.error(getLanguageData("getDataError"), err);
|
||||||
|
$message.error(getLanguageData("getDataError"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$message.error("请登录账号后使用");
|
$message.error(getLanguageData("needLogin"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ body,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.n-modal-container {
|
.n-modal-container {
|
||||||
z-index: 2006 !important;
|
// z-index: 2006 !important;
|
||||||
.n-modal-body-wrapper {
|
.n-modal-body-wrapper {
|
||||||
.n-modal-mask {
|
.n-modal-mask {
|
||||||
-webkit-backdrop-filter: blur(16px);
|
-webkit-backdrop-filter: blur(16px);
|
||||||
@@ -121,3 +121,26 @@ body,
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 响应式布局
|
||||||
|
@mixin changeWidth($padding: 10vw) {
|
||||||
|
.n-layout-header {
|
||||||
|
padding: 0 $padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-layout-content {
|
||||||
|
.main {
|
||||||
|
padding: 24px $padding 54px $padding;
|
||||||
|
.player {
|
||||||
|
padding: 0 $padding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include changeWidth;
|
||||||
|
|
||||||
|
/* 小于1200px时 */
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
@include changeWidth($padding: 5vw);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
// 主题色
|
|
||||||
$mainColor: #f55e55;
|
|
||||||
$mainSecondaryColor: #f55e551f;
|
|
||||||
|
|
||||||
// 响应式布局
|
|
||||||
@mixin changeWidth($padding:10vw) {
|
|
||||||
.n-layout-header {
|
|
||||||
padding: 0 $padding;
|
|
||||||
}
|
|
||||||
|
|
||||||
.n-layout-content {
|
|
||||||
.main {
|
|
||||||
padding: 24px $padding 54px $padding;
|
|
||||||
|
|
||||||
.player {
|
|
||||||
padding: 0 $padding;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include changeWidth;
|
|
||||||
|
|
||||||
/* 小于1200px时 */
|
|
||||||
@media (max-width: 1200px) {
|
|
||||||
@include changeWidth($padding:5vw);
|
|
||||||
}
|
|
||||||
@@ -77,7 +77,15 @@ class MusicFrequency {
|
|||||||
this.source.connect(this.analyser);
|
this.source.connect(this.analyser);
|
||||||
this.analyser.connect(this.context.destination);
|
this.analyser.connect(this.context.destination);
|
||||||
}
|
}
|
||||||
|
// 断开音频元素和分析器之间的连接,释放音频上下文
|
||||||
|
disconnect() {
|
||||||
|
// 断开连接
|
||||||
|
this.source.disconnect();
|
||||||
|
this.analyser.disconnect();
|
||||||
|
// 关闭音频上下文
|
||||||
|
this.context.close();
|
||||||
|
}
|
||||||
|
// 绘制频谱
|
||||||
drawSpectrum() {
|
drawSpectrum() {
|
||||||
// 获取频域数据
|
// 获取频域数据
|
||||||
this.analyser.getByteFrequencyData(this.output);
|
this.analyser.getByteFrequencyData(this.output);
|
||||||
|
|||||||
317
src/utils/Player.js
Normal file
317
src/utils/Player.js
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
import { Howl, Howler } from "howler";
|
||||||
|
import { songScrobble } from "@/api/song";
|
||||||
|
import { musicStore } from "@/store";
|
||||||
|
import { NIcon } from "naive-ui";
|
||||||
|
import { MusicNoteFilled } from "@vicons/material";
|
||||||
|
import getLanguageData from "./getLanguageData";
|
||||||
|
|
||||||
|
// 歌曲信息更新定时器
|
||||||
|
let timeupdateInterval = null;
|
||||||
|
// 听歌打卡延时器
|
||||||
|
let scrobbleTimeout = null;
|
||||||
|
// 重试次数
|
||||||
|
let testNumber = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建音频对象
|
||||||
|
* @param {string} src - 音频文件地址
|
||||||
|
* @param {number} volume - 音量(默认为0.7)
|
||||||
|
* @param {number} seek - 初始播放进度(默认为0)
|
||||||
|
* @return {Howl} - 音频对象
|
||||||
|
*/
|
||||||
|
export const createSound = (src, autoPlay = true) => {
|
||||||
|
try {
|
||||||
|
Howler.unload();
|
||||||
|
const music = musicStore();
|
||||||
|
const sound = new Howl({
|
||||||
|
src: [src],
|
||||||
|
format: ["mp3", "flac"],
|
||||||
|
html5: true,
|
||||||
|
preload: true,
|
||||||
|
volume: music.persistData.playVolume,
|
||||||
|
});
|
||||||
|
if (autoPlay && music.getPlayState) {
|
||||||
|
fadePlayOrPause(sound, "play", music.persistData.playVolume);
|
||||||
|
}
|
||||||
|
// 首次加载事件
|
||||||
|
sound?.once("load", () => {
|
||||||
|
const songId = music.getPlaySongData?.id;
|
||||||
|
const sourceId = music.getPlaySongData?.sourceId
|
||||||
|
? music.getPlaySongData.sourceId
|
||||||
|
: 0;
|
||||||
|
const user = JSON.parse(localStorage.getItem("userData"));
|
||||||
|
const settings = JSON.parse(localStorage.getItem("settingData"));
|
||||||
|
const isLogin = user.userLogin;
|
||||||
|
const isMemory = settings.memoryLastPlaybackPosition;
|
||||||
|
console.log("首次缓冲完成:" + songId + " / 来源:" + sourceId);
|
||||||
|
if (isMemory) {
|
||||||
|
sound?.seek(music.persistData.playSongTime.currentTime);
|
||||||
|
} else {
|
||||||
|
music.persistData.playSongTime = {
|
||||||
|
currentTime: 0,
|
||||||
|
duration: 0,
|
||||||
|
barMoveDistance: 0,
|
||||||
|
songTimePlayed: "00:00",
|
||||||
|
songTimeDuration: "00:00",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 取消加载状态
|
||||||
|
music.isLoadingSong = false;
|
||||||
|
// 听歌打卡
|
||||||
|
if (isLogin) {
|
||||||
|
clearTimeout(scrobbleTimeout);
|
||||||
|
scrobbleTimeout = setTimeout(() => {
|
||||||
|
songScrobble(songId, sourceId)
|
||||||
|
.then((res) => {
|
||||||
|
console.log("歌曲打卡完成", res);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("歌曲打卡失败:" + err);
|
||||||
|
});
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 播放事件
|
||||||
|
sound?.on("play", () => {
|
||||||
|
if (!Object.keys(music.getPlaySongData).length) {
|
||||||
|
$message.error(getLanguageData("songLoadError"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
testNumber = 0;
|
||||||
|
music.setPlayState(true);
|
||||||
|
const songName = music.getPlaySongData.name;
|
||||||
|
const songArtist = music.getPlaySongData.artist[0].name;
|
||||||
|
$message.info(songName + " - " + songArtist, {
|
||||||
|
icon: () =>
|
||||||
|
h(NIcon, null, {
|
||||||
|
default: () => h(MusicNoteFilled),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
console.log("开始播放:" + songName + " - " + songArtist);
|
||||||
|
// 获取播放器信息
|
||||||
|
timeupdateInterval = setInterval(() => checkAudioTime(sound, music), 250);
|
||||||
|
setMediaSession(music);
|
||||||
|
// 写入播放历史
|
||||||
|
music.setPlayHistory(music.getPlaySongData);
|
||||||
|
// 播放时页面标题
|
||||||
|
window.document.title =
|
||||||
|
music.getPlaySongData.name +
|
||||||
|
" - " +
|
||||||
|
music.getPlaySongData.artist[0].name +
|
||||||
|
" - " +
|
||||||
|
import.meta.env.VITE_SITE_TITLE;
|
||||||
|
});
|
||||||
|
// 暂停事件
|
||||||
|
sound?.on("pause", () => {
|
||||||
|
clearInterval(timeupdateInterval);
|
||||||
|
console.log("音乐暂停");
|
||||||
|
music.setPlayState(false);
|
||||||
|
// 更改页面标题
|
||||||
|
$setSiteTitle();
|
||||||
|
});
|
||||||
|
// 结束事件
|
||||||
|
sound?.on("end", () => {
|
||||||
|
console.log("歌曲播放结束");
|
||||||
|
music.setPlaySongIndex("next");
|
||||||
|
});
|
||||||
|
// 错误事件
|
||||||
|
sound?.on("loaderror", () => {
|
||||||
|
if (testNumber > 2) {
|
||||||
|
$message.error(getLanguageData("songPlayError"));
|
||||||
|
console.error(getLanguageData("songPlayError"));
|
||||||
|
music.setPlayState(false);
|
||||||
|
}
|
||||||
|
if (testNumber < 4) {
|
||||||
|
if (music.getPlaylists[0]) $getPlaySongData(music.getPlaySongData);
|
||||||
|
testNumber++;
|
||||||
|
} else {
|
||||||
|
$message.error(getLanguageData("songLoadTest"), {
|
||||||
|
closable: true,
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sound?.on("playerror", () => {
|
||||||
|
$message.error(getLanguageData("songPlayError"));
|
||||||
|
console.error(getLanguageData("songPlayError"));
|
||||||
|
music.setPlayState(false);
|
||||||
|
});
|
||||||
|
// 生成频谱
|
||||||
|
// createSpectrums(sound, music);
|
||||||
|
// 返回音频对象
|
||||||
|
return (window.$player = sound);
|
||||||
|
} catch (err) {
|
||||||
|
$message.error(getLanguageData("songLoadError"));
|
||||||
|
console.error(getLanguageData("songLoadError"), err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置音量
|
||||||
|
* @param {number} volume - 设置的音量值,0-1之间的浮点数
|
||||||
|
*/
|
||||||
|
export const setVolume = (sound, volume) => {
|
||||||
|
sound?.volume(volume);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置进度
|
||||||
|
* @param {number} seek - 设置的进度值,0-1之间的浮点数
|
||||||
|
*/
|
||||||
|
export const setSeek = (sound, seek) => {
|
||||||
|
const music = musicStore();
|
||||||
|
music.persistData.playSongTime.currentTime = seek;
|
||||||
|
sound?.seek(seek);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音频渐入渐出
|
||||||
|
* @param {Howl} sound - 音频对象
|
||||||
|
* @param {String} type - 渐入还是渐出
|
||||||
|
* @param {number} volume - 渐出音量的大小,0-1之间的浮点数
|
||||||
|
* @param {number} duration - 渐出音量的时长,单位为毫秒
|
||||||
|
*/
|
||||||
|
export const fadePlayOrPause = (sound, type, volume, duration = 300) => {
|
||||||
|
const isFade =
|
||||||
|
JSON.parse(localStorage.getItem("settingData")).songVolumeFade ?? true;
|
||||||
|
if (isFade) {
|
||||||
|
if (type === "play") {
|
||||||
|
if (sound?.playing()) return;
|
||||||
|
sound?.play();
|
||||||
|
sound?.once("play", () => {
|
||||||
|
sound?.fade(0, volume, duration);
|
||||||
|
});
|
||||||
|
} else if (type === "pause") {
|
||||||
|
sound?.fade(volume, 0, duration);
|
||||||
|
sound?.once("fade", () => {
|
||||||
|
sound?.pause();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
type === "play" ? sound?.play() : sound?.pause();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止播放器
|
||||||
|
* @param {Howl} sound - 音频对象
|
||||||
|
*/
|
||||||
|
export const soundStop = (sound) => {
|
||||||
|
sound?.stop();
|
||||||
|
setSeek(sound, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取播放进度
|
||||||
|
* @param {Howl} sound - 音频对象
|
||||||
|
* @param {music} music - pinia
|
||||||
|
*/
|
||||||
|
const checkAudioTime = (sound, music) => {
|
||||||
|
if (sound.playing()) {
|
||||||
|
const currentTime = sound.seek();
|
||||||
|
const duration = sound._duration;
|
||||||
|
music.setPlaySongTime({ currentTime, duration });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成 MediaSession
|
||||||
|
* @param {music} music - pinia
|
||||||
|
*/
|
||||||
|
const setMediaSession = (music) => {
|
||||||
|
if (
|
||||||
|
"mediaSession" in navigator &&
|
||||||
|
Object.keys(music.getPlaySongData).length
|
||||||
|
) {
|
||||||
|
const artists = music.getPlaySongData.artist.map((a) => a.name);
|
||||||
|
navigator.mediaSession.metadata = new MediaMetadata({
|
||||||
|
title: music.getPlaySongData.name,
|
||||||
|
artist: artists.join(" & "),
|
||||||
|
album: music.getPlaySongData.album.name,
|
||||||
|
artwork: [
|
||||||
|
{
|
||||||
|
src:
|
||||||
|
music.getPlaySongData.album.picUrl.replace(/^http:/, "https:") +
|
||||||
|
"?param=96y96",
|
||||||
|
sizes: "96x96",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src:
|
||||||
|
music.getPlaySongData.album.picUrl.replace(/^http:/, "https:") +
|
||||||
|
"?param=128y128",
|
||||||
|
sizes: "128x128",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src:
|
||||||
|
music.getPlaySongData.album.picUrl.replace(/^http:/, "https:") +
|
||||||
|
"?param=512x512",
|
||||||
|
sizes: "512x512",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
length: music.getPlaySongTime?.duration,
|
||||||
|
});
|
||||||
|
navigator.mediaSession.setActionHandler("nexttrack", () => {
|
||||||
|
music.setPlaySongIndex("next");
|
||||||
|
});
|
||||||
|
navigator.mediaSession.setActionHandler("previoustrack", () => {
|
||||||
|
music.setPlaySongIndex("prev");
|
||||||
|
});
|
||||||
|
navigator.mediaSession.setActionHandler("play", () => {
|
||||||
|
music.setPlayState(true);
|
||||||
|
});
|
||||||
|
navigator.mediaSession.setActionHandler("pause", () => {
|
||||||
|
music.setPlayState(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成频谱数据 - 快速傅里叶变换(FFT)
|
||||||
|
* @param {Howl} sound - 音频对象
|
||||||
|
* @param {music} music - pinia
|
||||||
|
*/
|
||||||
|
const createSpectrums = (sound, music) => {
|
||||||
|
try {
|
||||||
|
if (!music.spectrumsData.audioCtx) {
|
||||||
|
// 断开之前的连接
|
||||||
|
music.spectrumsData.audio?.disconnect();
|
||||||
|
music.spectrumsData.analyser?.disconnect();
|
||||||
|
music.spectrumsData.audioCtx?.close();
|
||||||
|
// 创建新的连接
|
||||||
|
music.spectrumsData.audioCtx = new (window.AudioContext ||
|
||||||
|
window.webkitAudioContext)();
|
||||||
|
const audioDom = sound._sounds[0]._node;
|
||||||
|
audioDom.crossOrigin = "anonymous";
|
||||||
|
const source =
|
||||||
|
music.spectrumsData.audioCtx.createMediaElementSource(audioDom);
|
||||||
|
const analyser = music.spectrumsData.audioCtx.createAnalyser();
|
||||||
|
analyser.fftSize = 256;
|
||||||
|
source.connect(analyser);
|
||||||
|
analyser.connect(music.spectrumsData.audioCtx.destination);
|
||||||
|
// 更新频谱数据
|
||||||
|
const dataArray = new Uint8Array(analyser.frequencyBinCount);
|
||||||
|
updateSpectrums(analyser, dataArray, music);
|
||||||
|
// 保存当前链接
|
||||||
|
music.spectrumsData.audio = source;
|
||||||
|
music.spectrumsData.analyser = analyser;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("音乐频谱生成失败:" + err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新音乐频谱数据
|
||||||
|
*
|
||||||
|
* @param {Object} analyser - 音频分析器
|
||||||
|
* @param {Uint8Array} dataArray - 频谱数据数组
|
||||||
|
* @param {Object} music - pinia
|
||||||
|
*/
|
||||||
|
const updateSpectrums = (analyser, dataArray, music) => {
|
||||||
|
analyser.getByteFrequencyData(dataArray);
|
||||||
|
music.spectrumsData.data = [...dataArray];
|
||||||
|
// 递归调用,持续更新频谱数据
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
updateSpectrums(analyser, dataArray, music);
|
||||||
|
});
|
||||||
|
};
|
||||||
17
src/utils/getCoverUrl.js
Normal file
17
src/utils/getCoverUrl.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* 获取图片的 url
|
||||||
|
* @param {string} url - 必选参数,输入的原始图片url
|
||||||
|
* @param {number} size - 可选参数,需要生成的图片尺寸,默认为null
|
||||||
|
* @return {string} 返回根据 url 和 size 参数生成的图片url
|
||||||
|
*/
|
||||||
|
const getCoverUrl = (url, size = null) => {
|
||||||
|
if (!url) return "/images/pic/default.png";
|
||||||
|
const sizeUrl = size ? `?param=${size}y${size}` : "";
|
||||||
|
const imageUrl = url.replace(/^http:/, "https:");
|
||||||
|
if (imageUrl.endsWith(".jpg")) {
|
||||||
|
return imageUrl + sizeUrl;
|
||||||
|
}
|
||||||
|
return imageUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getCoverUrl;
|
||||||
88
src/utils/getLanguageData.js
Normal file
88
src/utils/getLanguageData.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { settingStore } from "@/store";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译文本数据
|
||||||
|
*/
|
||||||
|
const languageData = {
|
||||||
|
"zh-CN": {
|
||||||
|
million: "万",
|
||||||
|
billion: "亿",
|
||||||
|
year: "年",
|
||||||
|
month: "月",
|
||||||
|
day: "日",
|
||||||
|
just: "刚刚发布",
|
||||||
|
minutesAgo: "分钟前",
|
||||||
|
yesterday: "昨天",
|
||||||
|
lightMode: "已切换至浅色模式",
|
||||||
|
darkMode: "已切换至深色模式",
|
||||||
|
personalFmError: "获取私人 FM 失败",
|
||||||
|
fmTrashError: "歌曲移除至垃圾桶失败",
|
||||||
|
needLogin: "请登录账号后使用",
|
||||||
|
loveSong: "已添加到我喜欢的音乐",
|
||||||
|
loveSongError: "喜欢音乐时发生错误",
|
||||||
|
loveSongRepeat: "我喜欢的音乐中已存在该歌曲",
|
||||||
|
loveSongRemove: "已从我喜欢的音乐中移除",
|
||||||
|
loveSongRemoveError: "取消喜欢音乐时发生错误",
|
||||||
|
loveSongNoFound: "我喜欢的列表中未找到该歌曲",
|
||||||
|
getDataError: "数据获取失败,请刷新后重试",
|
||||||
|
getDaraEmpty: "数据为空",
|
||||||
|
getLrcError: "歌词处理出错",
|
||||||
|
random: "随机播放",
|
||||||
|
single: "单曲循环",
|
||||||
|
normal: "列表循环",
|
||||||
|
playError: "播放出错,请刷新后重试",
|
||||||
|
addSongToNext: "已添加至下一曲播放",
|
||||||
|
removeSong: "已从播放列表中移除",
|
||||||
|
songLoadError: "音乐数据获取失败",
|
||||||
|
songPlayError: "歌曲播放失败",
|
||||||
|
songLoadTest: "歌曲重试次数过多,请刷新后重试",
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
million: "M",
|
||||||
|
billion: "B",
|
||||||
|
year: "-",
|
||||||
|
month: "-",
|
||||||
|
day: "",
|
||||||
|
just: "Just released",
|
||||||
|
minutesAgo: "Minutes ago",
|
||||||
|
yesterday: "Yesterday",
|
||||||
|
lightMode: "Switched to light mode",
|
||||||
|
darkMode: "Switched to dark mode",
|
||||||
|
personalFmError: "Get Private FM Failed",
|
||||||
|
fmTrashError: "Song removal to trash failed",
|
||||||
|
needLogin: "Please login to your account to use",
|
||||||
|
loveSong: "Added to my favorite music",
|
||||||
|
loveSongError: "An error occurred while liking music",
|
||||||
|
loveSongRepeat: "The song already exists in my favorite music",
|
||||||
|
loveSongRemove: "Removed from my favorite music",
|
||||||
|
loveSongError: "An error occurred while unliking music",
|
||||||
|
loveSongNoFound: "The song was not found in my favorite list",
|
||||||
|
getDataError: "Data acquisition failed, please refresh and try again",
|
||||||
|
getDaraEmpty: "Data is empty",
|
||||||
|
getLrcError: "Lyrics processing error",
|
||||||
|
random: "Random play",
|
||||||
|
single: "Single loop",
|
||||||
|
normal: "list loop",
|
||||||
|
playError: "Playback error, please refresh and try again",
|
||||||
|
addSongToNext: "has been added to the next song to play",
|
||||||
|
removeSong: "has been removed from the playlist",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回翻译文本
|
||||||
|
* @param {String} type 文本类别
|
||||||
|
* @returns {Object} 对应语种文本
|
||||||
|
*/
|
||||||
|
const getLanguageData = (type) => {
|
||||||
|
try {
|
||||||
|
const setting = settingStore();
|
||||||
|
const language = setting.language;
|
||||||
|
return languageData[language][type];
|
||||||
|
} catch (err) {
|
||||||
|
console.log("Failed to get translated:" + err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getLanguageData;
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/**
|
|
||||||
* 格式化歌词字符串为时间轴和歌词文本的数组
|
|
||||||
* @param {string} lrc 歌词字符串
|
|
||||||
* @returns {Array<{ time: number, lyric: string }>} 时间轴和歌词文本的数组
|
|
||||||
*/
|
|
||||||
const lyricFormat = (lrc) => {
|
|
||||||
// 匹配时间轴和歌词文本的正则表达式
|
|
||||||
const regex = /^\[([^\]]+)\]\s*(.+?)\s*$/;
|
|
||||||
// 将歌词字符串按行分割为数组
|
|
||||||
const lines = lrc.split("\n");
|
|
||||||
// 对每一行进行转换
|
|
||||||
const result = lines
|
|
||||||
// 筛选出包含时间轴和歌词文本的行
|
|
||||||
.filter((line) => regex.test(line))
|
|
||||||
// 转换时间轴和歌词文本为对象
|
|
||||||
.map((line) => {
|
|
||||||
const [, time, text] = line.match(regex);
|
|
||||||
const parts = time.split(":");
|
|
||||||
let seconds = parseInt(parts[0]) * 60 + parseFloat(parts[1]);
|
|
||||||
if (parts.length > 2) {
|
|
||||||
seconds += parseFloat(parts[2]) / 1000;
|
|
||||||
}
|
|
||||||
return { time: seconds, lyric: text.trim() };
|
|
||||||
}).filter((element) => element.lyric.trim() !== "");
|
|
||||||
// 检查是否为纯音乐,是则返回空数组
|
|
||||||
if (result.length && /纯音乐,请欣赏/.test(result[0].lyric)) {
|
|
||||||
console.log("该歌曲为纯音乐");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export default lyricFormat;
|
|
||||||
@@ -1,193 +1,192 @@
|
|||||||
/**
|
/**
|
||||||
* 将接口数据解析出对应数据
|
* 将歌词接口数据解析出对应数据
|
||||||
* @param {string} data 接口数据
|
* @param {string} data 接口数据
|
||||||
* @returns {Array} 对应数据
|
* @returns {Array} 对应数据
|
||||||
*/
|
*/
|
||||||
const parseLyric = (data) => {
|
const parseLyric = (data) => {
|
||||||
|
// 判断是否具有内容
|
||||||
|
const checkLyric = (lyric) => (lyric ? (lyric.lyric ? true : false) : false);
|
||||||
// 初始化数据
|
// 初始化数据
|
||||||
const { lrc, tlyric, romalrc, yrc, ytlrc } = data;
|
const { lrc, tlyric, romalrc, yrc, ytlrc, yromalrc } = data;
|
||||||
const lyrics = lrc ? lrc.lyric : null;
|
const lrcData = {
|
||||||
const otherLyrics = {
|
lrc: lrc?.lyric || null,
|
||||||
tran: tlyric ? tlyric.lyric : null,
|
tlyric: tlyric?.lyric || null,
|
||||||
roma: romalrc ? romalrc.lyric : null,
|
romalrc: romalrc?.lyric || null,
|
||||||
yrc: yrc ? yrc.lyric : null,
|
yrc: yrc?.lyric || null,
|
||||||
ytlrc: ytlrc ? ytlrc.lyric : null,
|
ytlrc: ytlrc?.lyric || null,
|
||||||
|
yromalrc: yromalrc?.lyric || null,
|
||||||
};
|
};
|
||||||
// 初始化输出结果
|
// 初始化输出结果
|
||||||
let result = {
|
const result = {
|
||||||
lrc: [], // 歌词数组 {time:时间,content:歌词}
|
// 是否具有普通翻译
|
||||||
yrc: [], // 逐字歌词数据
|
hasLrcTran: checkLyric(tlyric),
|
||||||
// 是否具有翻译
|
// 是否具有普通音译
|
||||||
hasTran: tlyric ? (tlyric.lyric ? true : false) : false,
|
hasLrcRoma: checkLyric(romalrc),
|
||||||
// 是否具有音译
|
|
||||||
hasRoma: romalrc ? (romalrc.lyric ? true : false) : false,
|
|
||||||
// 是否具有逐字歌词
|
// 是否具有逐字歌词
|
||||||
hasYrc: yrc ? (yrc.lyric ? true : false) : false,
|
hasYrc: checkLyric(yrc),
|
||||||
|
// 是否具有逐字翻译
|
||||||
|
hasYrcTran: checkLyric(ytlrc),
|
||||||
|
// 是否具有逐字音译
|
||||||
|
hasYrcRoma: checkLyric(yromalrc),
|
||||||
|
// 普通歌词数组
|
||||||
|
lrc: [],
|
||||||
|
// 逐字歌词数据
|
||||||
|
yrc: [],
|
||||||
};
|
};
|
||||||
// 普通歌词数据
|
// 普通歌词
|
||||||
let lrcData = Lrcsplit(lyrics);
|
if (lrcData.lrc) {
|
||||||
// 翻译歌词数据
|
result.lrc = parseLrc(lrcData.lrc);
|
||||||
let tranLrcData = null;
|
//判断是否有其他翻译
|
||||||
// 循环遍历 otherLyrics 参数对象
|
result.lrc = lrcData.tlyric
|
||||||
for (let i in otherLyrics) {
|
? parseOtherLrc(result.lrc, parseLrc(lrcData.tlyric), "tran")
|
||||||
const element = otherLyrics[i];
|
: result.lrc;
|
||||||
if (element !== null) {
|
result.lrc = lrcData.romalrc
|
||||||
// 若存在逐字歌词
|
? parseOtherLrc(result.lrc, parseLrc(lrcData.romalrc), "roma")
|
||||||
if (i == "yrc" && otherLyrics[i] != null) {
|
: result.lrc;
|
||||||
result[i] = parseYrc(otherLyrics[i]);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
// 若存在翻译
|
// 逐字歌词
|
||||||
if (i == "ytlrc" && element != null) {
|
if (lrcData.yrc) {
|
||||||
tranLrcData = Lrcsplit(element);
|
result.yrc = parseYrc(lrcData.yrc);
|
||||||
for (let num in tranLrcData) {
|
//判断是否有其他翻译
|
||||||
// 翻译文本对齐
|
result.yrc = lrcData.ytlrc
|
||||||
let objNum = result["yrc"].findIndex(
|
? parseOtherLrc(result.yrc, parseLrc(lrcData.ytlrc), "tran")
|
||||||
(o) => o.time == tranLrcData[num].time
|
: result.yrc;
|
||||||
);
|
result.yrc = lrcData.yromalrc
|
||||||
if (objNum != -1)
|
? parseOtherLrc(result.yrc, parseLrc(lrcData.yromalrc), "roma")
|
||||||
result["yrc"][objNum]["tran"] = tranLrcData[num].content;
|
: result.yrc;
|
||||||
}
|
}
|
||||||
}
|
console.log(result);
|
||||||
// 若存在其他翻译
|
|
||||||
tranLrcData = Lrcsplit(element);
|
|
||||||
if (tranLrcData[0]) {
|
|
||||||
console.log(`歌曲存在 ${i} 翻译`, tranLrcData);
|
|
||||||
for (let num in tranLrcData) {
|
|
||||||
// 翻译文本对齐
|
|
||||||
let objNum = lrcData.findIndex(
|
|
||||||
(o) => o.time == tranLrcData[num].time
|
|
||||||
);
|
|
||||||
if (objNum != -1) lrcData[objNum][i] = tranLrcData[num].content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 将歌词按时间排序
|
|
||||||
result.lrc = lrcData.sort((a, b) => {
|
|
||||||
return a.t - b.t;
|
|
||||||
});
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将歌词字符串解析为歌词对象数组
|
* 翻译文本对齐
|
||||||
* @param {string} lrc 歌词字符串
|
* @param {string} lrc 歌词字符串
|
||||||
|
* @param {string} tranLrc 翻译歌词字符串
|
||||||
|
* @returns {Array} 包含翻译的歌词对象数组
|
||||||
|
*/
|
||||||
|
const parseOtherLrc = (lrc, tranLrc, name) => {
|
||||||
|
const lyric = lrc;
|
||||||
|
const tranLyric = tranLrc;
|
||||||
|
if (lyric[0] && tranLyric[0]) {
|
||||||
|
lyric.forEach((v) => {
|
||||||
|
tranLyric.forEach((x) => {
|
||||||
|
if (
|
||||||
|
Number(v.time) === Number(x.time) ||
|
||||||
|
Math.abs(Number(v.time) - Number(x.time)) < 0.6
|
||||||
|
) {
|
||||||
|
v[name] = x.content;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return lyric;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普通歌词解析
|
||||||
|
* @param {string} lyrics 歌词字符串
|
||||||
* @returns {Array} 歌词对象数组
|
* @returns {Array} 歌词对象数组
|
||||||
*/
|
*/
|
||||||
const Lrcsplit = (lrc) => {
|
const parseLrc = (lyrics) => {
|
||||||
const lyrics = lrc.split("\n");
|
if (!lyrics) return [];
|
||||||
const lrcData = [];
|
try {
|
||||||
lyrics.forEach((lyric) => {
|
// 匹配时间轴和歌词文本的正则表达式
|
||||||
lyric = lyric.replace(/(^\s*)|(\s*$)/g, "");
|
const regex = /^\[([^\]]+)\]\s*(.+?)\s*$/;
|
||||||
const time = lyric.substring(lyric.indexOf("[") + 1, lyric.indexOf("]"));
|
// 将歌词字符串按行分割为数组
|
||||||
const timeArr = time.split(":");
|
const lines = lyrics.split("\n");
|
||||||
if (isNaN(parseInt(timeArr[0]))) {
|
// 对每一行进行转换
|
||||||
for (let i in lyrics) {
|
const parsedLyrics = lines
|
||||||
if (i != "lrc" && i == timeArr[0].toLowerCase()) {
|
// 筛选出包含时间轴和歌词文本的行
|
||||||
lyrics[i] = timeArr[1];
|
.filter((line) => regex.test(line))
|
||||||
|
// 转换时间轴和歌词文本为对象
|
||||||
|
.map((line) => {
|
||||||
|
const [, time, text] = line.match(regex);
|
||||||
|
const parts = time.split(":");
|
||||||
|
const seconds =
|
||||||
|
Number(parts[0]) * 60 +
|
||||||
|
Number(parts[1]) +
|
||||||
|
(parts.length > 2 ? Number(parts[2]) / 1000 : 0);
|
||||||
|
return { time: Number(seconds.toFixed(2)), content: text.trim() };
|
||||||
|
})
|
||||||
|
.filter((c) => c.content.trim() !== "");
|
||||||
|
// 检查是否为纯音乐,是则返回空数组
|
||||||
|
if (parsedLyrics.length && /纯音乐,请欣赏/.test(parsedLyrics[0].content)) {
|
||||||
|
console.log("该歌曲为纯音乐");
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
return parsedLyrics;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("普通歌词处理出错:" + err);
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
const lyricArr = lyric.match(/\[(\d+:.+?)\]/g);
|
|
||||||
let start = 0;
|
|
||||||
for (let k in lyricArr) {
|
|
||||||
start += lyricArr[k].length;
|
|
||||||
}
|
|
||||||
const content = lyric.substring(start);
|
|
||||||
if (content == "") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
lyricArr.forEach((t) => {
|
|
||||||
let time = t.substring(1, t.length - 1);
|
|
||||||
let second = time.split(":");
|
|
||||||
if (
|
|
||||||
(parseFloat(second[0]) * 60 + parseFloat(second[1])).toFixed(3) == 0
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lrcData.push({
|
|
||||||
time: (parseFloat(second[0]) * 60 + parseFloat(second[1])).toFixed(3),
|
|
||||||
content: content,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return lrcData;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 逐字歌词解析
|
* 逐字歌词解析
|
||||||
* @param {string} lyrics 歌词字符串
|
* @param {string} lyrics 逐字歌词字符串
|
||||||
* @returns {Array} 歌词对象数组
|
* @returns {Array} 歌词对象数组
|
||||||
*/
|
*/
|
||||||
const parseYrc = (lyrics) => {
|
const parseYrc = (lyrics) => {
|
||||||
// 若无内容,则返回空数组
|
if (!lyrics) return [];
|
||||||
if (lyrics == undefined) {
|
try {
|
||||||
|
// 遍历每一行逐字歌词
|
||||||
|
const parsedLyrics = lyrics
|
||||||
|
.split("\n")
|
||||||
|
.map((line) => {
|
||||||
|
// 匹配每一行中的时间戳信息
|
||||||
|
const timeReg = /\[(\d+),(\d+)\]/;
|
||||||
|
const timeMatch = line.match(timeReg);
|
||||||
|
if (!timeMatch) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 解构出起始时间和结束时间
|
||||||
|
const [_, startTime, endTime] = timeMatch;
|
||||||
|
if (isNaN(startTime) || isNaN(endTime)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 去除当前行中的时间戳信息,得到歌词内容
|
||||||
|
const content = line.replace(timeReg, "");
|
||||||
|
if (!content) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 对歌词内容中的时间戳和歌词内容分离
|
||||||
|
const contentArray = content
|
||||||
|
.split(/(\([1-9]\d*,[1-9]\d*,\d*\)[^\(]*)/g)
|
||||||
|
.filter((c) => c.trim())
|
||||||
|
.map((c) => {
|
||||||
|
// 匹配当前片段中的时间戳信息
|
||||||
|
const timeReg = /\((\d+),(\d+),(\d+)\)/;
|
||||||
|
const timeMatch = c.match(timeReg);
|
||||||
|
if (!timeMatch) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 解构出时间戳,持续时间和歌词内容
|
||||||
|
const [_, time, duration] = timeMatch;
|
||||||
|
const content = c.replace(timeReg, "");
|
||||||
|
if (!content) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
time: Number(time) / 1000 + 0.1,
|
||||||
|
duration: Number(duration) / 1000,
|
||||||
|
content,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((c) => c);
|
||||||
|
// 返回当前行解析出的时间信息和歌词内容信息
|
||||||
|
return {
|
||||||
|
time: Number(startTime) / 1000,
|
||||||
|
endTime: Number(endTime) / 1000,
|
||||||
|
content: contentArray,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((line) => line);
|
||||||
|
return parsedLyrics;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("逐字歌词处理出错:" + err);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
let lines = lyrics.split("\n");
|
|
||||||
let parsedLyrics = [];
|
|
||||||
// 解析每一句
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
const line = lines[i];
|
|
||||||
// 创建一个空对象,用于存放当前句的信息
|
|
||||||
let parsedLine = {
|
|
||||||
time: undefined, // 开始时间
|
|
||||||
endTime: undefined, // 结束时间
|
|
||||||
content: undefined, // 歌词内容
|
|
||||||
};
|
|
||||||
// 分离出时间信息,并转换为秒
|
|
||||||
let timeInfo = line
|
|
||||||
.substring(line.indexOf("[") + 1, line.indexOf("]"))
|
|
||||||
.split(",");
|
|
||||||
// 将开始时间转换为秒
|
|
||||||
parsedLine.time = Number(timeInfo[0]) / 1000;
|
|
||||||
// 将结束时间转换为秒
|
|
||||||
parsedLine.endTime = Number(timeInfo[1]) / 1000;
|
|
||||||
// 若时间信息不合法,将跳过该句
|
|
||||||
if (isNaN(parsedLine.time) || isNaN(parsedLine.endTime)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// 寻找歌词内容
|
|
||||||
const lyricArr = line.match(/\[[1-9]\d*,[1-9]\d*]/g);
|
|
||||||
if (!lyricArr) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// 去除时间信息,获取歌词内容
|
|
||||||
let contentArray = [];
|
|
||||||
// 分离成单个字或词,并解析时间信息
|
|
||||||
let splitContent = line.split(/(\([1-9]\d*,[1-9]\d*,\d*\)[^\(]*)/g);
|
|
||||||
for (let j = 0; j < splitContent.length; j++) {
|
|
||||||
const splitc = splitContent[j];
|
|
||||||
if (splitc == "") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// 创建一个对象,用于存放当前字或词的信息,并添加到当前句的歌词内容中
|
|
||||||
let contentObj = {
|
|
||||||
time: undefined, // 开始时间
|
|
||||||
duration: undefined, // 持续时间
|
|
||||||
content: "", // 字或词的文本内容
|
|
||||||
};
|
|
||||||
// 提取时间和文本信息,并转换为秒
|
|
||||||
let time = splitc.match(/\([1-9]\d*,[1-9]\d*,\d*\)/);
|
|
||||||
if (!time) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let timeArray = time[0].slice(1, -1).split(",");
|
|
||||||
// 将开始时间转换为秒
|
|
||||||
contentObj.time = Number(timeArray[0]) / 1000;
|
|
||||||
// 将持续时间转换为秒
|
|
||||||
contentObj.duration = Number(timeArray[1]) / 1000;
|
|
||||||
// 获取字或词的文本内容
|
|
||||||
contentObj.content = splitc.slice(time[0].length);
|
|
||||||
contentArray.push(contentObj);
|
|
||||||
}
|
|
||||||
parsedLine.content = contentArray;
|
|
||||||
parsedLyrics.push(parsedLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsedLyrics;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default parseLyric;
|
export default parseLyric;
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ axios.defaults.withCredentials = true;
|
|||||||
// 请求拦截
|
// 请求拦截
|
||||||
axios.interceptors.request.use(
|
axios.interceptors.request.use(
|
||||||
(request) => {
|
(request) => {
|
||||||
if (!request.hiddenBar) $loadingBar.start();
|
if (!request.hiddenBar && typeof $loadingBar !== "undefined")
|
||||||
|
$loadingBar.start();
|
||||||
return request;
|
return request;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
@@ -32,7 +33,7 @@ axios.interceptors.request.use(
|
|||||||
// 响应拦截
|
// 响应拦截
|
||||||
axios.interceptors.response.use(
|
axios.interceptors.response.use(
|
||||||
(response) => {
|
(response) => {
|
||||||
$loadingBar.finish();
|
if (typeof $loadingBar !== "undefined") $loadingBar.finish();
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
@@ -41,7 +42,7 @@ axios.interceptors.response.use(
|
|||||||
const data = error.response.data;
|
const data = error.response.data;
|
||||||
switch (error.response.status) {
|
switch (error.response.status) {
|
||||||
case 401:
|
case 401:
|
||||||
console.error("您未登录");
|
console.error("无权限访问");
|
||||||
break;
|
break;
|
||||||
case 301:
|
case 301:
|
||||||
console.error("请求发生重定向");
|
console.error("请求发生重定向");
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import getLanguageData from "./getLanguageData";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 歌曲时长时间戳转换
|
* 歌曲时长时间戳转换
|
||||||
* @param {number} mss 毫秒数
|
* @param {number} mss 毫秒数
|
||||||
@@ -47,26 +49,30 @@ export const getCommentTime = (t) => {
|
|||||||
: userDate.getMinutes();
|
: userDate.getMinutes();
|
||||||
// 判断时间差
|
// 判断时间差
|
||||||
if (nowDate - t <= 60000) {
|
if (nowDate - t <= 60000) {
|
||||||
return "刚刚发布";
|
return getLanguageData("just");
|
||||||
} else if (nowDate - t > 60000 && nowDate - t <= 3600000) {
|
} else if (nowDate - t > 60000 && nowDate - t <= 3600000) {
|
||||||
const pastTimeUnix = nowDate - t;
|
const pastTimeUnix = nowDate - t;
|
||||||
const pastTime = new Date(Number(pastTimeUnix));
|
const pastTime = new Date(Number(pastTimeUnix));
|
||||||
return `${pastTime.getMinutes()} 分钟前`;
|
return `${pastTime.getMinutes("yesterday")} ${getLanguageData(
|
||||||
|
"minutesAgo"
|
||||||
|
)}`;
|
||||||
} else if (todayLast - t > 3600000 && todayLast - t <= 86400000) {
|
} else if (todayLast - t > 3600000 && todayLast - t <= 86400000) {
|
||||||
return `${UH}:${Um}`;
|
return `${UH}:${Um}`;
|
||||||
} else if (todayLast - t > 86400000 && todayLast - t <= 172800000) {
|
} else if (todayLast - t > 86400000 && todayLast - t <= 172800000) {
|
||||||
return `昨天 ${UH}:${Um}`;
|
return `${getLanguageData("yesterday")} ${UH}:${Um}`;
|
||||||
} else if (todayLast - t > 172800000 && todayLast - t <= 31557600000) {
|
} else if (todayLast - t > 172800000 && todayLast - t <= 31557600000) {
|
||||||
return `${userDate.getMonth() + 1}月${userDate.getDate()}日`;
|
return `${userDate.getMonth() + 1}${getLanguageData(
|
||||||
|
"month"
|
||||||
|
)}${userDate.getDate()}${getLanguageData("day")}`;
|
||||||
} else {
|
} else {
|
||||||
return `${userDate.getFullYear()}年${
|
return `${userDate.getFullYear()}${getLanguageData("year")}${
|
||||||
userDate.getMonth() + 1
|
userDate.getMonth() + 1
|
||||||
}月${userDate.getDate()}日`;
|
}${getLanguageData("month")}${userDate.getDate()}${getLanguageData("day")}`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 过万数字转化
|
* 过万/亿数字转化
|
||||||
* @param {number} num 需要格式化的数字
|
* @param {number} num 需要格式化的数字
|
||||||
* @returns {string|number} 格式化后的字符串或原样返回的数字
|
* @returns {string|number} 格式化后的字符串或原样返回的数字
|
||||||
*/
|
*/
|
||||||
@@ -74,8 +80,17 @@ export const formatNumber = (num) => {
|
|||||||
const n = Number(num);
|
const n = Number(num);
|
||||||
if (n === 0 || n < 10000) {
|
if (n === 0 || n < 10000) {
|
||||||
return n;
|
return n;
|
||||||
|
} else if (n < 100000000) {
|
||||||
|
const numString = (n / 10000).toFixed(1);
|
||||||
|
return numString.endsWith(".0")
|
||||||
|
? numString.slice(0, -2) + getLanguageData("million")
|
||||||
|
: numString + getLanguageData("million");
|
||||||
|
} else {
|
||||||
|
const numString = (n / 100000000).toFixed(1);
|
||||||
|
return numString.endsWith(".0")
|
||||||
|
? numString.slice(0, -2) + getLanguageData("billion")
|
||||||
|
: numString + getLanguageData("billion");
|
||||||
}
|
}
|
||||||
return (n / 10000).toFixed(1) + "万";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,46 +1,50 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="playlist" v-if="albumDetail">
|
<div class="album" v-if="albumDetail">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
<n-avatar
|
<n-image
|
||||||
|
show-toolbar-tooltip
|
||||||
class="coverImg"
|
class="coverImg"
|
||||||
:src="
|
:src="getCoverUrl(albumDetail.picUrl, 1024)"
|
||||||
albumDetail.picUrl
|
:previewed-img-props="{ style: { borderRadius: '8px' } }"
|
||||||
? albumDetail.picUrl.replace(/^http:/, 'https:') +
|
:preview-src="getCoverUrl(albumDetail.picUrl)"
|
||||||
'?param=1024y1024'
|
|
||||||
: null
|
|
||||||
"
|
|
||||||
fallback-src="/images/pic/default.png"
|
fallback-src="/images/pic/default.png"
|
||||||
/>
|
/>
|
||||||
<img src="/images/pic/album.png" class="album" alt="album" />
|
<img src="/images/pic/album.png" class="album" alt="album" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="meta">
|
||||||
|
<div class="title">
|
||||||
|
<n-text class="name">{{ albumDetail.name }}</n-text>
|
||||||
|
<n-text
|
||||||
|
class="creator"
|
||||||
|
@click="router.push(`/artist/songs?id=${albumDetail.artist.id}`)"
|
||||||
|
>
|
||||||
|
{{ albumDetail.artist.name }}
|
||||||
|
</n-text>
|
||||||
|
</div>
|
||||||
<div class="intr">
|
<div class="intr">
|
||||||
<span class="name">歌单简介</span>
|
<span class="name">{{
|
||||||
|
$t("general.name.desc", { name: $t("general.name.album") })
|
||||||
|
}}</span>
|
||||||
<span class="desc text-hidden">
|
<span class="desc text-hidden">
|
||||||
{{ albumDetail.description }}
|
{{
|
||||||
|
albumDetail.description
|
||||||
|
? albumDetail.description
|
||||||
|
: $t("other.noDesc")
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
<n-button
|
<n-button
|
||||||
|
class="all-desc"
|
||||||
block
|
block
|
||||||
strong
|
strong
|
||||||
secondary
|
secondary
|
||||||
v-if="albumDetail?.description.length > 70"
|
v-if="albumDetail?.description.length > 70"
|
||||||
@click="albumDescShow = true"
|
@click="albumDescShow = true"
|
||||||
>
|
>
|
||||||
全部简介
|
{{ $t("general.name.allDesc") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-modal
|
|
||||||
class="s-modal"
|
|
||||||
v-model:show="albumDescShow"
|
|
||||||
preset="card"
|
|
||||||
title="歌单简介"
|
|
||||||
:bordered="false"
|
|
||||||
>
|
|
||||||
<n-scrollbar>
|
|
||||||
<n-text v-html="albumDetail.description.replace(/\n/g, '<br>')" />
|
|
||||||
</n-scrollbar>
|
|
||||||
</n-modal>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tag" v-if="albumDetail.tags">
|
<n-space class="tag" v-if="albumDetail.tags">
|
||||||
<n-tag
|
<n-tag
|
||||||
class="tags"
|
class="tags"
|
||||||
round
|
round
|
||||||
@@ -50,36 +54,70 @@
|
|||||||
>
|
>
|
||||||
{{ item }}
|
{{ item }}
|
||||||
</n-tag>
|
</n-tag>
|
||||||
|
</n-space>
|
||||||
|
<n-space class="control">
|
||||||
|
<n-button strong secondary round type="primary" @click="playAllSong">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon :component="MusicList" />
|
||||||
|
</template>
|
||||||
|
{{ $t("general.name.play") }}
|
||||||
|
</n-button>
|
||||||
|
<n-dropdown
|
||||||
|
placement="right-start"
|
||||||
|
trigger="click"
|
||||||
|
:show-arrow="true"
|
||||||
|
:options="dropdownOptions"
|
||||||
|
>
|
||||||
|
<n-button strong secondary circle>
|
||||||
|
<template #icon>
|
||||||
|
<n-icon :component="More" />
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</n-dropdown>
|
||||||
|
</n-space>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<span class="name">{{ albumDetail.name }}</span>
|
<n-text class="name">{{ albumDetail.name }}</n-text>
|
||||||
<span
|
<n-text
|
||||||
class="creator"
|
class="creator"
|
||||||
@click="router.push(`/artist/songs?id=${albumDetail.artist.id}`)"
|
@click="router.push(`/artist/songs?id=${albumDetail.artist.id}`)"
|
||||||
>
|
>
|
||||||
|
<n-icon :depth="3" :component="People" />
|
||||||
{{ albumDetail.artist.name }}
|
{{ albumDetail.artist.name }}
|
||||||
</span>
|
</n-text>
|
||||||
<div class="time">
|
<n-space class="time">
|
||||||
<div class="createTime">
|
<div class="num">
|
||||||
<span class="num">发行时间:</span>
|
<n-icon :depth="3" :component="Time" />
|
||||||
{{ getLongTime(albumDetail.publishTime) }}
|
<n-text v-html="getLongTime(albumDetail.publishTime)" />
|
||||||
</div>
|
|
||||||
<div class="company" v-if="albumDetail.company">
|
|
||||||
<span class="num">发行公司:</span>
|
|
||||||
{{ albumDetail.company }}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="num" v-if="albumDetail.company">
|
||||||
|
<n-icon :depth="3" :component="City" />
|
||||||
|
<n-text v-html="albumDetail.company" />
|
||||||
</div>
|
</div>
|
||||||
|
</n-space>
|
||||||
</div>
|
</div>
|
||||||
<DataLists :listData="albumData" hideAlbum />
|
<DataLists :listData="albumData" hideAlbum />
|
||||||
|
<!-- 专辑简介 -->
|
||||||
|
<n-modal
|
||||||
|
class="s-modal"
|
||||||
|
v-model:show="albumDescShow"
|
||||||
|
preset="card"
|
||||||
|
:title="$t('general.name.desc', { name: $t('general.name.album') })"
|
||||||
|
:bordered="false"
|
||||||
|
>
|
||||||
|
<n-scrollbar>
|
||||||
|
<n-text v-html="albumDetail.description.replace(/\n/g, '<br>')" />
|
||||||
|
</n-scrollbar>
|
||||||
|
</n-modal>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="title" v-else-if="!albumId">
|
<div class="title" v-else-if="!albumId">
|
||||||
<span class="key">参数不完整</span>
|
<span class="key">{{ $t("general.name.noKeywords") }}</span>
|
||||||
<br />
|
<br />
|
||||||
<n-button strong secondary @click="router.go(-1)" style="margin-top: 20px">
|
<n-button strong secondary @click="router.go(-1)" style="margin-top: 20px">
|
||||||
返回上一级
|
{{ $t("general.name.goBack") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="loading" v-else>
|
<div class="loading" v-else>
|
||||||
@@ -96,25 +134,113 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { getAlbum } from "@/api/album";
|
import { NIcon, NAvatar, NText } from "naive-ui";
|
||||||
|
import { getAlbum, likeAlbum } from "@/api/album";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { getSongTime, getLongTime } from "@/utils/timeTools.js";
|
import { getSongTime, getLongTime } from "@/utils/timeTools";
|
||||||
|
import {
|
||||||
|
MusicList,
|
||||||
|
LinkTwo,
|
||||||
|
More,
|
||||||
|
Like,
|
||||||
|
Unlike,
|
||||||
|
People,
|
||||||
|
Time,
|
||||||
|
City,
|
||||||
|
} from "@icon-park/vue-next";
|
||||||
|
import { userStore, musicStore, settingStore } from "@/store";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import DataLists from "@/components/DataList/DataLists.vue";
|
import DataLists from "@/components/DataList/DataLists.vue";
|
||||||
const router = useRouter();
|
import getCoverUrl from "@/utils/getCoverUrl";
|
||||||
|
|
||||||
// 歌单数据
|
const { t } = useI18n();
|
||||||
|
const router = useRouter();
|
||||||
|
const user = userStore();
|
||||||
|
const music = musicStore();
|
||||||
|
const setting = settingStore();
|
||||||
|
|
||||||
|
// 专辑数据
|
||||||
const albumId = ref(router.currentRoute.value.query.id);
|
const albumId = ref(router.currentRoute.value.query.id);
|
||||||
const albumDetail = ref(null);
|
const albumDetail = ref(null);
|
||||||
const albumData = ref([]);
|
const albumData = ref([]);
|
||||||
const albumDescShow = ref(false);
|
const albumDescShow = ref(false);
|
||||||
|
|
||||||
|
// 图标渲染
|
||||||
|
const renderIcon = (icon) => {
|
||||||
|
return () => {
|
||||||
|
return h(
|
||||||
|
NIcon,
|
||||||
|
{ style: { transform: "translateX(2px)" } },
|
||||||
|
{
|
||||||
|
default: () => icon,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 判断收藏还是取消
|
||||||
|
const isLikeOrDislike = (id) => {
|
||||||
|
const playlists = user.getUserAlbumLists.list;
|
||||||
|
if (playlists.length) {
|
||||||
|
return !playlists.some((item) => item.id === Number(id));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 专辑下拉菜单数据
|
||||||
|
const dropdownOptions = ref([]);
|
||||||
|
|
||||||
|
// 更改专辑下拉菜单数据
|
||||||
|
const setDropdownOptions = () => {
|
||||||
|
dropdownOptions.value = [
|
||||||
|
{
|
||||||
|
key: "copy",
|
||||||
|
label: t("menu.copy", {
|
||||||
|
name: t("general.name.album"),
|
||||||
|
other: t("general.name.link"),
|
||||||
|
}),
|
||||||
|
props: {
|
||||||
|
onClick: () => {
|
||||||
|
if (navigator.clipboard) {
|
||||||
|
try {
|
||||||
|
navigator.clipboard.writeText(
|
||||||
|
`https://music.163.com/#/playlist?id=${albumId.value}`
|
||||||
|
);
|
||||||
|
$message.success(t("general.message.copySuccess"));
|
||||||
|
} catch (err) {
|
||||||
|
console.error(t("general.message.copyFailure"), err);
|
||||||
|
$message.error(t("general.message.copyFailure"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$message.error(t("general.message.notSupported"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
icon: renderIcon(h(LinkTwo)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "like",
|
||||||
|
label: isLikeOrDislike(albumId.value)
|
||||||
|
? t("menu.collection", { name: t("general.name.album") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.name.album") }),
|
||||||
|
show: user.userLogin,
|
||||||
|
props: {
|
||||||
|
onClick: () => {
|
||||||
|
toChangeLike(albumId.value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
icon: renderIcon(h(isLikeOrDislike(albumId.value) ? Like : Unlike)),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
// 获取歌单信息
|
// 获取歌单信息
|
||||||
const getAlbumData = (id) => {
|
const getAlbumData = (id) => {
|
||||||
getAlbum(id).then((res) => {
|
getAlbum(id).then((res) => {
|
||||||
console.log(res);
|
console.log(res);
|
||||||
// 专辑信息
|
// 专辑信息
|
||||||
albumDetail.value = res.album;
|
albumDetail.value = res.album;
|
||||||
$setSiteTitle(res.album.name + " - 专辑");
|
$setSiteTitle(res.album.name + " - " + t("general.name.album"));
|
||||||
// 专辑歌曲
|
// 专辑歌曲
|
||||||
if (res.songs) {
|
if (res.songs) {
|
||||||
albumData.value = [];
|
albumData.value = [];
|
||||||
@@ -128,19 +254,113 @@ const getAlbumData = (id) => {
|
|||||||
alia: v.alia,
|
alia: v.alia,
|
||||||
time: getSongTime(v.dt),
|
time: getSongTime(v.dt),
|
||||||
fee: v.fee,
|
fee: v.fee,
|
||||||
|
sourceId: id,
|
||||||
pc: v.pc ? v.pc : null,
|
pc: v.pc ? v.pc : null,
|
||||||
mv: v.mv ? v.mv : null,
|
mv: v.mv ? v.mv : null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$message.error("获取专辑歌曲失败");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 播放专辑所有歌曲
|
||||||
|
const playAllSong = () => {
|
||||||
|
try {
|
||||||
|
// 获取元素
|
||||||
|
const songDom = document.getElementById("datalists").firstElementChild;
|
||||||
|
const allSongDom = document.querySelectorAll("#datalists > *");
|
||||||
|
// 是否有元素存在 play
|
||||||
|
let isHasPlay = false;
|
||||||
|
// 遍历
|
||||||
|
allSongDom.forEach((child) => {
|
||||||
|
if (child.classList.contains("play")) {
|
||||||
|
isHasPlay = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!isHasPlay) {
|
||||||
|
// 双击操作
|
||||||
|
const event = new MouseEvent("dblclick", {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
view: window,
|
||||||
|
});
|
||||||
|
// 双击或单击
|
||||||
|
if (setting.listClickMode === "dblclick") {
|
||||||
|
songDom.dispatchEvent(event);
|
||||||
|
} else if (setting.listClickMode === "click") {
|
||||||
|
songDom.click();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
music.setPlayState(true);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error($message.error(t("general.message.operationFailed")), err);
|
||||||
|
$message.error($message.error(t("general.message.operationFailed")));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 收藏/取消收藏
|
||||||
|
const toChangeLike = async (id) => {
|
||||||
|
const type = isLikeOrDislike(id) ? 1 : 2;
|
||||||
|
const likeMsg = t("general.name.album");
|
||||||
|
const isThereASpace = setting.language === "zh-CN" ? "" : " ";
|
||||||
|
try {
|
||||||
|
const res = await likeAlbum(type, id);
|
||||||
|
if (res.code === 200) {
|
||||||
|
$message.success(
|
||||||
|
`${likeMsg + isThereASpace}${
|
||||||
|
type == 1
|
||||||
|
? t("menu.collection", { name: t("general.dialog.success") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.dialog.success") })
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
user.setUserAlbumLists(() => {
|
||||||
|
setDropdownOptions();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$message.error(
|
||||||
|
`${likeMsg + isThereASpace}${
|
||||||
|
type == 1
|
||||||
|
? t("menu.collection", { name: t("general.dialog.failed") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.dialog.failed") })
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
$message.error(
|
||||||
|
`${likeMsg + isThereASpace}${
|
||||||
|
type == 1
|
||||||
|
? t("menu.collection", { name: t("general.dialog.failed") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.dialog.failed") })
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
console.error(
|
||||||
|
`${likeMsg + isThereASpace}${
|
||||||
|
type == 1
|
||||||
|
? t("menu.collection", { name: t("general.dialog.failed") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.dialog.failed") })
|
||||||
|
}`,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (albumId.value) {
|
if (albumId.value) {
|
||||||
getAlbumData(albumId.value);
|
getAlbumData(albumId.value);
|
||||||
|
if (
|
||||||
|
user.userLogin &&
|
||||||
|
!user.getUserAlbumLists.has &&
|
||||||
|
!user.getUserAlbumLists.isLoading
|
||||||
|
) {
|
||||||
|
user.setUserAlbumLists(() => {
|
||||||
|
setDropdownOptions();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setDropdownOptions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -157,7 +377,7 @@ watch(
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.playlist,
|
.album,
|
||||||
.loading {
|
.loading {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -172,22 +392,59 @@ watch(
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 24px;
|
top: 24px;
|
||||||
|
@media (max-width: 990px) {
|
||||||
|
margin-right: 0;
|
||||||
|
width: 30vw;
|
||||||
|
}
|
||||||
.cover {
|
.cover {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
// box-shadow: 0 0 16px 0px rgb(0 0 0 / 20%);
|
|
||||||
.n-avatar {
|
|
||||||
border-radius: 8px;
|
|
||||||
width: 80%;
|
width: 80%;
|
||||||
height: 80%;
|
height: 80%;
|
||||||
|
border-radius: 8px;
|
||||||
|
position: relative;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
.coverImg {
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1;
|
||||||
|
:deep(img) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.album {
|
.album {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 4%;
|
right: -20%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.meta {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
.title {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 0;
|
||||||
|
.name {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
}
|
||||||
|
.creator {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.intr {
|
.intr {
|
||||||
@@ -208,13 +465,22 @@ watch(
|
|||||||
}
|
}
|
||||||
.tag {
|
.tag {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
.tags {
|
.tags {
|
||||||
margin-right: 8px;
|
line-height: 0;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--main-second-color);
|
||||||
|
color: var(--main-color);
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.control {
|
||||||
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,6 +496,8 @@ watch(
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.creator {
|
.creator {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
@@ -237,7 +505,10 @@ watch(
|
|||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
|
}
|
||||||
|
.n-icon {
|
||||||
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.time {
|
.time {
|
||||||
@@ -245,15 +516,18 @@ watch(
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 1100px) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
.num {
|
.num {
|
||||||
color: #999;
|
// color: #999;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
.n-icon {
|
||||||
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
div {
|
|
||||||
margin-right: 12px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,20 +545,110 @@ watch(
|
|||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
.left {
|
.left {
|
||||||
margin-bottom: 12px;
|
position: relative;
|
||||||
position: static;
|
top: 0;
|
||||||
width: 60vw;
|
width: 100%;
|
||||||
|
height: 30vw;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
.cover {
|
||||||
|
height: 100%;
|
||||||
|
min-width: 30vw;
|
||||||
|
width: 30vw;
|
||||||
|
margin-right: 60px;
|
||||||
|
}
|
||||||
|
.meta {
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
.name {
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
.creator {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
.intr {
|
.intr {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
.name,
|
||||||
|
.all-desc {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.desc {
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.control {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: -60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.right {
|
.right {
|
||||||
|
margin-top: 80px;
|
||||||
.meta {
|
.meta {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 540px) {
|
||||||
|
.left {
|
||||||
|
.cover {
|
||||||
|
margin-right: 44px;
|
||||||
|
}
|
||||||
|
.meta {
|
||||||
|
.title {
|
||||||
|
margin-bottom: 0;
|
||||||
.name {
|
.name {
|
||||||
font-size: 26px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.intr,
|
||||||
|
.tag {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.control {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
.left {
|
||||||
|
.meta {
|
||||||
|
.title {
|
||||||
|
.name {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.creator {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 370px) {
|
||||||
|
.left {
|
||||||
|
.meta {
|
||||||
|
.title {
|
||||||
|
.name {
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.control {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
margin-top: 80px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { getArtistAblums } from "@/api/artist";
|
import { getArtistAblums } from "@/api/artist";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { getLongTime } from "@/utils/timeTools.js";
|
import { getLongTime } from "@/utils/timeTools";
|
||||||
import CoverLists from "@/components/DataList/CoverLists.vue";
|
import CoverLists from "@/components/DataList/CoverLists.vue";
|
||||||
import Pagination from "@/components/Pagination/index.vue";
|
import Pagination from "@/components/Pagination/index.vue";
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -51,7 +51,7 @@ const getArtistAblumsData = (id, limit = 30, offset = 0) => {
|
|||||||
$message.error("搜索内容为空");
|
$message.error("搜索内容为空");
|
||||||
}
|
}
|
||||||
// 请求后回顶
|
// 请求后回顶
|
||||||
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
|
if (typeof $scrollToTop !== "undefined") $scrollToTop();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="all-songs">
|
<div class="all-songs">
|
||||||
<div class="title" v-if="artistId">
|
<div class="title" v-if="artistId">
|
||||||
<span class="key">{{ artistName ? artistName : "未知歌手" }}</span>
|
<template v-if="artistData[0]">
|
||||||
<span>全部歌曲</span>
|
<span class="key">{{
|
||||||
|
artistName ? artistName : $t("general.name.unknownArtist")
|
||||||
|
}}</span>
|
||||||
|
<span>{{ $t("general.name.allSong") }} </span>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="title" v-else>
|
<div class="title" v-else>
|
||||||
<span class="key">未提供所需信息</span>
|
<span class="key">{{ $t("general.name.noKeywords") }}</span>
|
||||||
<br />
|
<br />
|
||||||
<n-button
|
<n-button
|
||||||
strong
|
strong
|
||||||
@@ -13,7 +17,7 @@
|
|||||||
@click="router.go(-1)"
|
@click="router.go(-1)"
|
||||||
style="margin-top: 20px"
|
style="margin-top: 20px"
|
||||||
>
|
>
|
||||||
返回上一级
|
{{ $t("general.name.goBack") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="songs" v-if="artistId">
|
<div class="songs" v-if="artistId">
|
||||||
@@ -30,13 +34,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { getArtistAllSongs } from "@/api/artist";
|
import { getArtistDetail, getArtistAllSongs } from "@/api/artist";
|
||||||
import { getMusicDetail } from "@/api/song";
|
import { getMusicDetail } from "@/api/song";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { getSongTime } from "@/utils/timeTools.js";
|
import { getSongTime } from "@/utils/timeTools";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import DataLists from "@/components/DataList/DataLists.vue";
|
import DataLists from "@/components/DataList/DataLists.vue";
|
||||||
import Pagination from "@/components/Pagination/index.vue";
|
import Pagination from "@/components/Pagination/index.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// 歌手信息
|
// 歌手信息
|
||||||
@@ -51,15 +57,25 @@ const pageNumber = ref(
|
|||||||
: 1
|
: 1
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 获取歌手名称
|
||||||
|
const getArtistDetailData = (id) => {
|
||||||
|
getArtistDetail(id).then((res) => {
|
||||||
|
artistName.value = res.data.artist.name;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// 获取歌手信息
|
// 获取歌手信息
|
||||||
const getArtistAllSongsData = (id, limit = 30, offset = 0, order = "hot") => {
|
const getArtistAllSongsData = (id, limit = 30, offset = 0, order = "hot") => {
|
||||||
getArtistAllSongs(id, limit, offset, order).then((res) => {
|
if (!id) return false;
|
||||||
|
getArtistAllSongs(id, limit, offset, order)
|
||||||
|
.then((res) => {
|
||||||
console.log(res);
|
console.log(res);
|
||||||
|
// 获取歌手名称
|
||||||
|
getArtistDetailData(id);
|
||||||
|
// 全部歌曲数据
|
||||||
if (res.songs[0]) {
|
if (res.songs[0]) {
|
||||||
// 数据总数
|
// 数据总数
|
||||||
totalCount.value = res.total;
|
totalCount.value = res.total;
|
||||||
// 歌手名称
|
|
||||||
artistName.value = res.songs[0].ar[0].name;
|
|
||||||
// 列表数据
|
// 列表数据
|
||||||
const ids = res.songs.map((obj) => obj.id);
|
const ids = res.songs.map((obj) => obj.id);
|
||||||
getMusicDetail(ids.join(",")).then((res) => {
|
getMusicDetail(ids.join(",")).then((res) => {
|
||||||
@@ -81,10 +97,15 @@ const getArtistAllSongsData = (id, limit = 30, offset = 0, order = "hot") => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$message.error("歌手全部歌曲为空");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
}
|
}
|
||||||
// 请求后回顶并结束加载条
|
// 请求后回顶
|
||||||
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
|
if (typeof $scrollToTop !== "undefined") $scrollToTop();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
router.go(-1);
|
||||||
|
console.error(t("general.message.acquisitionFailed"), err);
|
||||||
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -17,15 +17,15 @@
|
|||||||
<div class="num">
|
<div class="num">
|
||||||
<n-text class="musicSize" @click="tabChange('songs')">
|
<n-text class="musicSize" @click="tabChange('songs')">
|
||||||
<n-icon :component="MusicNoteFilled" />
|
<n-icon :component="MusicNoteFilled" />
|
||||||
{{ artistData.musicSize }} 首歌
|
{{ $t("general.name.songSize", { size: artistData.musicSize }) }}
|
||||||
</n-text>
|
</n-text>
|
||||||
<n-text class="albumSize" @click="tabChange('albums')">
|
<n-text class="albumSize" @click="tabChange('albums')">
|
||||||
<n-icon :component="AlbumFilled" />
|
<n-icon :component="AlbumFilled" />
|
||||||
{{ artistData.albumSize }} 张专辑
|
{{ $t("general.name.albumSize", { size: artistData.albumSize }) }}
|
||||||
</n-text>
|
</n-text>
|
||||||
<n-text class="mvSize" @click="tabChange('videos')">
|
<n-text class="mvSize" @click="tabChange('videos')">
|
||||||
<n-icon :component="VideocamRound" />
|
<n-icon :component="VideocamRound" />
|
||||||
{{ artistData.mvSize }} 个 MV
|
{{ $t("general.name.mvSize", { size: artistData.mvSize }) }}
|
||||||
</n-text>
|
</n-text>
|
||||||
</div>
|
</div>
|
||||||
<n-text class="desc text-hidden" @click="artistDescShow = true">
|
<n-text class="desc text-hidden" @click="artistDescShow = true">
|
||||||
@@ -51,7 +51,13 @@
|
|||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
{{ artistLikeBtn ? "收藏歌手" : "取消收藏歌手" }}
|
{{
|
||||||
|
artistLikeBtn
|
||||||
|
? $t("menu.collection", { name: $t("general.name.artists") })
|
||||||
|
: $t("menu.cancelCollection", {
|
||||||
|
name: $t("general.name.artists"),
|
||||||
|
})
|
||||||
|
}}
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
<!-- 歌手介绍 -->
|
<!-- 歌手介绍 -->
|
||||||
@@ -59,7 +65,7 @@
|
|||||||
class="s-modal"
|
class="s-modal"
|
||||||
v-model:show="artistDescShow"
|
v-model:show="artistDescShow"
|
||||||
preset="card"
|
preset="card"
|
||||||
title="歌手介绍"
|
:title="$t('general.name.artistDesc')"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
>
|
>
|
||||||
<n-scrollbar>
|
<n-scrollbar>
|
||||||
@@ -69,7 +75,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="error" v-else-if="!artistId">
|
<div class="error" v-else-if="!artistId">
|
||||||
<n-text>参数不完整</n-text>
|
<n-text>{{ $t("general.name.noKeywords") }}</n-text>
|
||||||
<br />
|
<br />
|
||||||
<n-button
|
<n-button
|
||||||
strong
|
strong
|
||||||
@@ -77,7 +83,7 @@
|
|||||||
@click="router.go(-1)"
|
@click="router.go(-1)"
|
||||||
style="margin-top: 20px"
|
style="margin-top: 20px"
|
||||||
>
|
>
|
||||||
返回上一级
|
{{ $t("general.name.goBack") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
<n-tabs
|
<n-tabs
|
||||||
@@ -87,8 +93,8 @@
|
|||||||
v-model:value="tabValue"
|
v-model:value="tabValue"
|
||||||
v-if="artistData"
|
v-if="artistData"
|
||||||
>
|
>
|
||||||
<n-tab name="songs"> 热门单曲 </n-tab>
|
<n-tab name="songs"> {{ $t("general.name.hotSong") }} </n-tab>
|
||||||
<n-tab name="albums"> 专辑 </n-tab>
|
<n-tab name="albums"> {{ $t("general.name.album") }} </n-tab>
|
||||||
<n-tab name="videos"> MV </n-tab>
|
<n-tab name="videos"> MV </n-tab>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
<main class="content" v-if="artistData">
|
<main class="content" v-if="artistData">
|
||||||
@@ -117,7 +123,9 @@ import {
|
|||||||
PersonAddAlt1Round,
|
PersonAddAlt1Round,
|
||||||
PersonRemoveAlt1Round,
|
PersonRemoveAlt1Round,
|
||||||
} from "@vicons/material";
|
} from "@vicons/material";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const user = userStore();
|
const user = userStore();
|
||||||
|
|
||||||
@@ -146,17 +154,15 @@ const getArtistDetailData = (id) => {
|
|||||||
musicSize: res.data.artist.musicSize,
|
musicSize: res.data.artist.musicSize,
|
||||||
mvSize: res.data.artist.mvSize,
|
mvSize: res.data.artist.mvSize,
|
||||||
};
|
};
|
||||||
$setSiteTitle(res.data.artist.name + " - 歌手");
|
$setSiteTitle(res.data.artist.name + " - " + t("general.name.artists"));
|
||||||
// 请求后回顶
|
// 请求后回顶
|
||||||
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
|
if (typeof $scrollToTop !== "undefined") $scrollToTop();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
router.go(-1);
|
router.go(-1);
|
||||||
console.error("歌手信息获取失败:" + err);
|
console.error(t("general.message.acquisitionFailed"), err);
|
||||||
$message.error("歌手信息获取失败");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
$message.error("参数不完整");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -193,13 +199,23 @@ const toLikeArtist = (data) => {
|
|||||||
likeArtist(type, data.id).then((res) => {
|
likeArtist(type, data.id).then((res) => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
$message.success(
|
$message.success(
|
||||||
`${data.name}${type == 1 ? "收藏成功" : "取消收藏成功"}`
|
`${data.name} ${
|
||||||
|
type == 1
|
||||||
|
? t("menu.collection", { name: t("general.dialog.success") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.dialog.success") })
|
||||||
|
}`
|
||||||
);
|
);
|
||||||
user.setUserArtistLists(() => {
|
user.setUserArtistLists(() => {
|
||||||
artistLikeBtn.value = isLikeOrDislike(artistId.value);
|
artistLikeBtn.value = isLikeOrDislike(artistId.value);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$message.error(`${data.name}${type == 1 ? "收藏失败" : "取消收藏失败"}`);
|
$message.error(
|
||||||
|
`${data.name} ${
|
||||||
|
type == 1
|
||||||
|
? t("menu.collection", { name: t("general.dialog.failed") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.dialog.failed") })
|
||||||
|
}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -308,13 +324,13 @@ watch(
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
.n-icon {
|
.n-icon {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
round
|
round
|
||||||
@click="router.push(`/all-songs?id=${artistId}&page=1`)"
|
@click="router.push(`/all-songs?id=${artistId}&page=1`)"
|
||||||
>
|
>
|
||||||
全部歌曲
|
{{ $t("general.name.allSong") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
</div>
|
</div>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { getArtistSongs } from "@/api/artist";
|
import { getArtistSongs } from "@/api/artist";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { getSongTime } from "@/utils/timeTools.js";
|
import { getSongTime } from "@/utils/timeTools";
|
||||||
import DataLists from "@/components/DataList/DataLists.vue";
|
import DataLists from "@/components/DataList/DataLists.vue";
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -73,8 +73,8 @@ watch(
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $mainSecondaryColor;
|
background-color: var(--main-second-color);
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { getArtistVideos } from "@/api/artist";
|
import { getArtistVideos } from "@/api/artist";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { formatNumber, getSongTime } from "@/utils/timeTools.js";
|
import { formatNumber, getSongTime } from "@/utils/timeTools";
|
||||||
import VideoLists from "@/components/DataList/VideoLists.vue";
|
import VideoLists from "@/components/DataList/VideoLists.vue";
|
||||||
import Pagination from "@/components/Pagination/index.vue";
|
import Pagination from "@/components/Pagination/index.vue";
|
||||||
|
|
||||||
@@ -59,8 +59,8 @@ const getArtistVideosData = (id, limit = 30, offset = 0) => {
|
|||||||
} else {
|
} else {
|
||||||
$message.error("搜索内容为空");
|
$message.error("搜索内容为空");
|
||||||
}
|
}
|
||||||
// 请求后回顶并结束加载条
|
// 请求后回顶
|
||||||
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
|
if (typeof $scrollToTop !== "undefined") $scrollToTop();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,17 +6,18 @@
|
|||||||
class="goback"
|
class="goback"
|
||||||
@click="router.push(`/comment?id=${music.getPlaySongData.id}&page=1`)"
|
@click="router.push(`/comment?id=${music.getPlaySongData.id}&page=1`)"
|
||||||
content-style="padding: 6px"
|
content-style="padding: 6px"
|
||||||
>前往当前播放歌曲
|
>
|
||||||
|
{{ $t("general.name.toCurrentlySong") }}
|
||||||
</n-card>
|
</n-card>
|
||||||
</Transition>
|
</Transition>
|
||||||
<div class="title" v-if="songId">
|
<div class="title" v-if="songId">
|
||||||
<span class="key">全部评论</span>
|
<span class="key">{{ $t("general.name.allComments") }}</span>
|
||||||
<n-card class="song">
|
<n-card class="song">
|
||||||
<SmallSongData :getDataByID="songId" />
|
<SmallSongData :getDataByID="songId" />
|
||||||
</n-card>
|
</n-card>
|
||||||
</div>
|
</div>
|
||||||
<div class="title" v-else>
|
<div class="title" v-else>
|
||||||
<span class="key">缺少必要参数</span>
|
<span class="key">{{ $t("general.name.noKeywords") }}</span>
|
||||||
<br />
|
<br />
|
||||||
<n-button
|
<n-button
|
||||||
strong
|
strong
|
||||||
@@ -24,12 +25,12 @@
|
|||||||
@click="router.go(-1)"
|
@click="router.go(-1)"
|
||||||
style="margin-top: 20px"
|
style="margin-top: 20px"
|
||||||
>
|
>
|
||||||
返回上一页
|
{{ $t("general.name.goBack") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="commentData" v-if="songId && commentData.allComments[0]">
|
<div class="commentData" v-if="songId && commentData.allComments[0]">
|
||||||
<div class="hotComments" v-if="commentData.hotComments[0]">
|
<div class="hotComments" v-if="commentData.hotComments[0]">
|
||||||
<n-h6 prefix="bar"> 热门评论 </n-h6>
|
<n-h6 prefix="bar"> {{ $t("general.name.hotComments") }} </n-h6>
|
||||||
<div class="loading" v-if="!commentData.hotComments[0]">
|
<div class="loading" v-if="!commentData.hotComments[0]">
|
||||||
<n-skeleton text :repeat="3" />
|
<n-skeleton text :repeat="3" />
|
||||||
<n-skeleton text style="width: 60%" />
|
<n-skeleton text style="width: 60%" />
|
||||||
@@ -44,8 +45,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="allComments" ref="allCommentsRef">
|
<div class="allComments" ref="allCommentsRef">
|
||||||
<n-h6 prefix="bar">
|
<n-h6 prefix="bar">
|
||||||
全部评论
|
{{ $t("general.name.allComments") }}
|
||||||
<span class="count">{{ commentsCount }} 条</span>
|
<span class="count">{{ commentsCount }} +</span>
|
||||||
</n-h6>
|
</n-h6>
|
||||||
<div class="loading" v-if="!commentData.allComments[0]">
|
<div class="loading" v-if="!commentData.allComments[0]">
|
||||||
<n-skeleton text :repeat="3" />
|
<n-skeleton text :repeat="3" />
|
||||||
@@ -73,9 +74,12 @@
|
|||||||
import { musicStore } from "@/store";
|
import { musicStore } from "@/store";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { getComment } from "@/api/comment";
|
import { getComment } from "@/api/comment";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import SmallSongData from "@/components/DataList/SmallSongData.vue";
|
import SmallSongData from "@/components/DataList/SmallSongData.vue";
|
||||||
import Comment from "@/components/Comment/index.vue";
|
import Comment from "@/components/Comment/index.vue";
|
||||||
import Pagination from "@/components/Pagination/index.vue";
|
import Pagination from "@/components/Pagination/index.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const music = musicStore();
|
const music = musicStore();
|
||||||
|
|
||||||
@@ -114,11 +118,11 @@ const getCommentData = (id, offset = 0) => {
|
|||||||
commentData.allComments = res.comments;
|
commentData.allComments = res.comments;
|
||||||
commentsCount.value = res.total;
|
commentsCount.value = res.total;
|
||||||
} else {
|
} else {
|
||||||
$message.info("暂无评论");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
router.go(-1);
|
router.go(-1);
|
||||||
}
|
}
|
||||||
// 滚动至顶部
|
// 请求后回顶
|
||||||
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
|
if (typeof $scrollToTop !== "undefined") $scrollToTop();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -134,7 +138,7 @@ const pageNumberChange = (val) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
$setSiteTitle("全部评论");
|
$setSiteTitle(t("general.name.allComments"));
|
||||||
// 获取评论数据
|
// 获取评论数据
|
||||||
if (songId.value) getCommentData(songId.value, (pageNumber.value - 1) * 20);
|
if (songId.value) getCommentData(songId.value, (pageNumber.value - 1) * 20);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,9 +6,11 @@
|
|||||||
<n-text class="num" v-html="new Date().getDate()" />
|
<n-text class="num" v-html="new Date().getDate()" />
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<n-gradient-text class="big" type="danger"> 每日推荐 </n-gradient-text>
|
<n-gradient-text class="big" type="danger">
|
||||||
|
{{ $t("home.modules.dailySongs.title") }}
|
||||||
|
</n-gradient-text>
|
||||||
<n-text class="tip" :depth="3">
|
<n-text class="tip" :depth="3">
|
||||||
根据你的音乐口味生成 · 每天 6:00 更新
|
{{ $t("home.modules.dailySongs.subtitle") }}
|
||||||
</n-text>
|
</n-text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -20,24 +22,28 @@
|
|||||||
import { getDailySongs } from "@/api/home";
|
import { getDailySongs } from "@/api/home";
|
||||||
import { musicStore } from "@/store";
|
import { musicStore } from "@/store";
|
||||||
import { CalendarTodayFilled } from "@vicons/material";
|
import { CalendarTodayFilled } from "@vicons/material";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import DataLists from "@/components/DataList/DataLists.vue";
|
import DataLists from "@/components/DataList/DataLists.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const music = musicStore();
|
const music = musicStore();
|
||||||
|
|
||||||
// 获取每日推荐数据
|
// 获取每日推荐数据
|
||||||
const getDailySongsData = () => {
|
const getDailySongsData = () => {
|
||||||
|
if (music.getDailySongs.length === 0) {
|
||||||
getDailySongs().then((res) => {
|
getDailySongs().then((res) => {
|
||||||
if (res.data.dailySongs) {
|
if (res.data.dailySongs) {
|
||||||
music.setDailySongs(res.data.dailySongs);
|
music.setDailySongs(res.data.dailySongs);
|
||||||
} else {
|
} else {
|
||||||
$message.error("每日推荐获取失败");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
$setSiteTitle("每日推荐");
|
$setSiteTitle(t("home.modules.dailySongs.title"));
|
||||||
if (music.getDailySongs.length === 0) getDailySongsData();
|
getDailySongsData();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
:loading="loading"
|
:loading="loading"
|
||||||
@click="loadingMore"
|
@click="loadingMore"
|
||||||
>
|
>
|
||||||
加载更多
|
{{ $t("general.name.loadMore") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
</div>
|
</div>
|
||||||
@@ -49,14 +49,16 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import { getArtistList } from "@/api/artist";
|
import { getArtistList } from "@/api/artist";
|
||||||
import ArtistLists from "@/components/DataList/ArtistLists.vue";
|
import ArtistLists from "@/components/DataList/ArtistLists.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// 歌手标签数据
|
// 歌手标签数据
|
||||||
const artistInitials = [
|
const artistInitials = [
|
||||||
{ key: "-1", value: "热门" },
|
{ key: "-1", value: t("general.type.hot") },
|
||||||
...Array.from({ length: 26 }, (_, i) => String.fromCharCode(i + 65)).map(
|
...Array.from({ length: 26 }, (_, i) => String.fromCharCode(i + 65)).map(
|
||||||
(v) => ({
|
(v) => ({
|
||||||
key: v,
|
key: v,
|
||||||
@@ -73,24 +75,24 @@ const artistInitialChoose = ref(
|
|||||||
|
|
||||||
// 歌手分类数据
|
// 歌手分类数据
|
||||||
const artistTypeNames = [
|
const artistTypeNames = [
|
||||||
"全部",
|
t("general.type.all"),
|
||||||
"华语",
|
t("general.type.china"),
|
||||||
"华语男",
|
t("general.type.chinaMale"),
|
||||||
"华语女",
|
t("general.type.chinaFemale"),
|
||||||
"华语组合",
|
t("general.type.chinaGroup"),
|
||||||
"欧美",
|
t("general.type.western"),
|
||||||
"欧美男",
|
t("general.type.westernMale"),
|
||||||
"欧美女",
|
t("general.type.westernFemale"),
|
||||||
"欧美组合",
|
t("general.type.westernGroup"),
|
||||||
"日本",
|
t("general.type.japan"),
|
||||||
"日本男",
|
t("general.type.japanMale"),
|
||||||
"日本女",
|
t("general.type.japanFemale"),
|
||||||
"日本组合",
|
t("general.type.japanGroup"),
|
||||||
"韩国",
|
t("general.type.korea"),
|
||||||
"韩国男",
|
t("general.type.koreaMale"),
|
||||||
"韩国女",
|
t("general.type.koreaFemale"),
|
||||||
"韩国组合",
|
t("general.type.koreaGroup"),
|
||||||
"其他",
|
t("general.type.other"),
|
||||||
];
|
];
|
||||||
const artistType = [-1, -1, 1, 2, 3, -1, 1, 2, 3, -1, 1, 2, 3, -1, 1, 2, 3, -1];
|
const artistType = [-1, -1, 1, 2, 3, -1, 1, 2, 3, -1, 1, 2, 3, -1, 1, 2, 3, -1];
|
||||||
const artistArea = [
|
const artistArea = [
|
||||||
@@ -132,7 +134,7 @@ const getArtistListData = (
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
hasMore.value = false;
|
hasMore.value = false;
|
||||||
$message.error("歌手内容为空");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -167,7 +169,6 @@ const artistTypeChange = (index) => {
|
|||||||
const loadingMore = () => {
|
const loadingMore = () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
artistsOffset.value += 30;
|
artistsOffset.value += 30;
|
||||||
if (artistsOffset.value >= 300) $message.info("太多了吧 😲");
|
|
||||||
getArtistListData(
|
getArtistListData(
|
||||||
artistType[artistTypeNamesChoose.value],
|
artistType[artistTypeNamesChoose.value],
|
||||||
artistArea[artistTypeNamesChoose.value],
|
artistArea[artistTypeNamesChoose.value],
|
||||||
@@ -200,7 +201,7 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
$setSiteTitle("发现 - 歌手");
|
$setSiteTitle(t("nav.discover") + " - " + t("nav.discoverChildren.artists"));
|
||||||
// 获取歌手数据
|
// 获取歌手数据
|
||||||
getArtistListData(
|
getArtistListData(
|
||||||
artistType[artistTypeNamesChoose.value],
|
artistType[artistTypeNamesChoose.value],
|
||||||
@@ -240,8 +241,8 @@ onMounted(() => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $mainSecondaryColor;
|
background-color: var(--main-second-color);
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.9);
|
transform: scale(0.9);
|
||||||
@@ -262,8 +263,8 @@ onMounted(() => {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $mainSecondaryColor;
|
background-color: var(--main-second-color);
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="discover">
|
<div class="discover">
|
||||||
<n-text class="title">发现</n-text>
|
<n-text class="title">{{ $t("nav.discover") }}</n-text>
|
||||||
<n-tabs
|
<n-tabs
|
||||||
class="main-tab"
|
class="main-tab"
|
||||||
type="segment"
|
type="segment"
|
||||||
@update:value="tabChange"
|
@update:value="tabChange"
|
||||||
v-model:value="tabValue"
|
v-model:value="tabValue"
|
||||||
>
|
>
|
||||||
<n-tab name="playlists"> 歌单 </n-tab>
|
<n-tab name="playlists">{{ $t("nav.discoverChildren.playlists") }}</n-tab>
|
||||||
<n-tab name="toplists"> 排行榜 </n-tab>
|
<n-tab name="toplists">{{ $t("nav.discoverChildren.toplists") }}</n-tab>
|
||||||
<n-tab name="artists"> 歌手 </n-tab>
|
<n-tab name="artists">{{ $t("nav.discoverChildren.artists") }}</n-tab>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
<main class="content">
|
<main class="content">
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
class="cat"
|
class="cat"
|
||||||
icon-placement="right"
|
icon-placement="right"
|
||||||
round
|
round
|
||||||
@click="catModelShow = true"
|
@click="catModalShow = true"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon class="up" :component="ChevronRightRound" />
|
<n-icon class="up" :component="ChevronRightRound" />
|
||||||
@@ -17,13 +17,13 @@
|
|||||||
</n-button>
|
</n-button>
|
||||||
<n-modal
|
<n-modal
|
||||||
class="s-modal"
|
class="s-modal"
|
||||||
v-model:show="catModelShow"
|
v-model:show="catModalShow"
|
||||||
preset="card"
|
preset="card"
|
||||||
title="歌单分类"
|
:title="$t('general.name.playlistType')"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
歌单分类
|
{{ $t("general.name.playlistType") }}
|
||||||
<n-tag
|
<n-tag
|
||||||
round
|
round
|
||||||
class="tag"
|
class="tag"
|
||||||
@@ -32,14 +32,15 @@
|
|||||||
marginLeft: '12px',
|
marginLeft: '12px',
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
transform: 'translateY(-2px)',
|
transform: 'translateY(-2px)',
|
||||||
|
cursor: 'pointer',
|
||||||
}"
|
}"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
@click="changeTagName('全部歌单')"
|
@click="changeTagName('全部歌单')"
|
||||||
>
|
>
|
||||||
全部歌单
|
{{ $t("general.name.allPLaylist") }}
|
||||||
</n-tag>
|
</n-tag>
|
||||||
</template>
|
</template>
|
||||||
<n-scrollbar style="max-height: 80vh">
|
<n-scrollbar>
|
||||||
<div class="all-cat" v-if="music.catList?.sub[0]">
|
<div class="all-cat" v-if="music.catList?.sub[0]">
|
||||||
<n-list>
|
<n-list>
|
||||||
<n-list-item
|
<n-list-item
|
||||||
@@ -75,7 +76,9 @@
|
|||||||
</n-list-item>
|
</n-list-item>
|
||||||
</n-list>
|
</n-list>
|
||||||
</div>
|
</div>
|
||||||
<div class="error" v-else>分类数据获取失败</div>
|
<div class="error" v-else>
|
||||||
|
{{ $t("general.message.acquisitionFailed") }}
|
||||||
|
</div>
|
||||||
</n-scrollbar>
|
</n-scrollbar>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
<!-- 精品歌单开关 -->
|
<!-- 精品歌单开关 -->
|
||||||
@@ -83,7 +86,7 @@
|
|||||||
v-if="getHaveHqPlaylists(music.highqualityCatList, catName)"
|
v-if="getHaveHqPlaylists(music.highqualityCatList, catName)"
|
||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
<n-text>精品歌单</n-text>
|
<n-text>{{ $t("general.name.bestPlaylist") }}</n-text>
|
||||||
<n-switch
|
<n-switch
|
||||||
v-model:value="hqPLayListOpen"
|
v-model:value="hqPLayListOpen"
|
||||||
@update:value="hqPLayListChange"
|
@update:value="hqPLayListChange"
|
||||||
@@ -115,7 +118,7 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
加载更多
|
{{ $t("general.name.loadMore") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
</div>
|
</div>
|
||||||
@@ -126,15 +129,17 @@ import { ChevronRightRound, LocalFireDepartmentRound } from "@vicons/material";
|
|||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { musicStore } from "@/store";
|
import { musicStore } from "@/store";
|
||||||
import { getHighqualityPlaylist, getTopPlaylist } from "@/api/playlist";
|
import { getHighqualityPlaylist, getTopPlaylist } from "@/api/playlist";
|
||||||
import { formatNumber } from "@/utils/timeTools.js";
|
import { formatNumber } from "@/utils/timeTools";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import CoverLists from "@/components/DataList/CoverLists.vue";
|
import CoverLists from "@/components/DataList/CoverLists.vue";
|
||||||
import Pagination from "@/components/Pagination/index.vue";
|
import Pagination from "@/components/Pagination/index.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const music = musicStore();
|
const music = musicStore();
|
||||||
|
|
||||||
// 分类数据
|
// 分类数据
|
||||||
const catModelShow = ref(false);
|
const catModalShow = ref(false);
|
||||||
const catName = ref(
|
const catName = ref(
|
||||||
router.currentRoute.value.query.cat
|
router.currentRoute.value.query.cat
|
||||||
? router.currentRoute.value.query.cat
|
? router.currentRoute.value.query.cat
|
||||||
@@ -205,10 +210,10 @@ const getPlaylistData = (cat = "全部歌单", limit = 30, offset = 0) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$message.error("歌单列表为空");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
}
|
}
|
||||||
// 请求后回顶
|
// 请求后回顶
|
||||||
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
|
if (typeof $scrollToTop !== "undefined") $scrollToTop();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -239,7 +244,7 @@ const getHqPlaylistData = (cat = "全部歌单", limit = 30) => {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
hasMore.value = false;
|
hasMore.value = false;
|
||||||
$message.error("精品歌单列表为空");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -254,7 +259,7 @@ const changeTagName = (name) => {
|
|||||||
page: 1,
|
page: 1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
catModelShow.value = false;
|
catModalShow.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 排序方式变化
|
// 排序方式变化
|
||||||
@@ -299,6 +304,7 @@ watch(
|
|||||||
: false;
|
: false;
|
||||||
if (val.name == "dsc-playlists") {
|
if (val.name == "dsc-playlists") {
|
||||||
if (hqPLayListOpen.value) {
|
if (hqPLayListOpen.value) {
|
||||||
|
playlistsData.value = [];
|
||||||
getHqPlaylistData(catName.value);
|
getHqPlaylistData(catName.value);
|
||||||
} else {
|
} else {
|
||||||
pageNumber.value = val.query.page ? Number(val.query.page) : 1;
|
pageNumber.value = val.query.page ? Number(val.query.page) : 1;
|
||||||
@@ -313,7 +319,7 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
$setSiteTitle("发现 - 歌单");
|
$setSiteTitle(t("nav.discover") + " - " + t("general.name.playlist"));
|
||||||
// 获取歌单分类
|
// 获取歌单分类
|
||||||
if (!music.catList.sub || !music.highqualityCatList[0])
|
if (!music.catList.sub || !music.highqualityCatList[0])
|
||||||
music.setCatList(true);
|
music.setCatList(true);
|
||||||
@@ -348,8 +354,8 @@ onMounted(() => {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $mainSecondaryColor;
|
background-color: var(--main-second-color);
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
@@ -373,14 +379,14 @@ onMounted(() => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $mainSecondaryColor;
|
background-color: var(--main-second-color);
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
.icon {
|
.icon {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="toplists">
|
<div class="toplists">
|
||||||
<n-divider v-if="toplistData.officialList[0]">官方榜</n-divider>
|
<n-divider v-if="toplistData.officialList[0]">
|
||||||
|
{{ $t("nav.officialList") }}
|
||||||
|
</n-divider>
|
||||||
<Transition mode="out-in">
|
<Transition mode="out-in">
|
||||||
<n-grid
|
<n-grid
|
||||||
class="official"
|
class="official"
|
||||||
@@ -49,7 +51,7 @@
|
|||||||
</n-gi>
|
</n-gi>
|
||||||
</n-grid>
|
</n-grid>
|
||||||
</Transition>
|
</Transition>
|
||||||
<n-divider>全球榜</n-divider>
|
<n-divider>{{ $t("nav.globalList") }}</n-divider>
|
||||||
<CoverLists :listData="toplistData.globalList" listType="topList" />
|
<CoverLists :listData="toplistData.globalList" listType="topList" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -57,9 +59,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { getToplist } from "@/api/album";
|
import { getToplist } from "@/api/album";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { formatNumber } from "@/utils/timeTools.js";
|
import { formatNumber } from "@/utils/timeTools";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import CoverLists from "@/components/DataList/CoverLists.vue";
|
import CoverLists from "@/components/DataList/CoverLists.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// 排行榜数据
|
// 排行榜数据
|
||||||
@@ -91,13 +95,13 @@ const getToplistData = () => {
|
|||||||
});
|
});
|
||||||
console.log(toplistData);
|
console.log(toplistData);
|
||||||
} else {
|
} else {
|
||||||
$message.error("排行榜获取失败");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
$setSiteTitle("发现 - 排行榜");
|
$setSiteTitle(t("nav.discover") + " - " + t("nav.discoverChildren.toplists"));
|
||||||
getToplistData();
|
getToplistData();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="history">
|
<div class="history">
|
||||||
<div class="title" v-if="music.getPlayHistory[0]">
|
<div class="title" v-if="music.getPlayHistory[0]">
|
||||||
<span class="key">播放历史</span>
|
<span class="key">{{ $t("nav.avatar.history") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="title" v-else>
|
<div class="title" v-else>
|
||||||
<span class="key">暂无播放历史</span>
|
<span class="key">{{ $t("other.noHistory") }}</span>
|
||||||
<br />
|
<br />
|
||||||
<n-button
|
<n-button
|
||||||
strong
|
strong
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
@click="router.go(-1)"
|
@click="router.go(-1)"
|
||||||
style="margin-top: 20px"
|
style="margin-top: 20px"
|
||||||
>
|
>
|
||||||
返回上一页
|
{{ $t("general.name.goBack") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
<DataLists
|
<DataLists
|
||||||
@@ -20,7 +20,9 @@
|
|||||||
:listData="music.getPlayHistory"
|
:listData="music.getPlayHistory"
|
||||||
/>
|
/>
|
||||||
<n-divider v-if="music.getPlayHistory[0]" class="tip" dashed>
|
<n-divider v-if="music.getPlayHistory[0]" class="tip" dashed>
|
||||||
<n-text :depth="3" style="font-size: 12px">仅显示最近 100 首</n-text>
|
<n-text :depth="3" style="font-size: 12px">
|
||||||
|
{{ $t("other.justShow", { num: 100 }) }}
|
||||||
|
</n-text>
|
||||||
</n-divider>
|
</n-divider>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -28,13 +30,15 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { musicStore } from "@/store";
|
import { musicStore } from "@/store";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import DataLists from "@/components/DataList/DataLists.vue";
|
import DataLists from "@/components/DataList/DataLists.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const music = musicStore();
|
const music = musicStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
$setSiteTitle("播放历史");
|
$setSiteTitle(t("nav.avatar.history"));
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,24 @@
|
|||||||
<div class="home">
|
<div class="home">
|
||||||
<Banner v-if="setting.bannerShow" />
|
<Banner v-if="setting.bannerShow" />
|
||||||
<!-- 个性化推荐 -->
|
<!-- 个性化推荐 -->
|
||||||
<n-h3 class="title" prefix="bar"> 专属推荐 </n-h3>
|
<n-h3 class="title" prefix="bar">{{ $t("home.title.exclusive") }}</n-h3>
|
||||||
<n-grid class="recommendation" cols="4" item-responsive x-gap="20">
|
<n-grid class="recommend" :x-gap="20" :cols="2">
|
||||||
<n-grid-item span="1 950:2">
|
<n-gi class="rec-left">
|
||||||
|
<!-- 每日推荐 -->
|
||||||
<PaDailySongs />
|
<PaDailySongs />
|
||||||
</n-grid-item>
|
<!-- 其他推荐 -->
|
||||||
<n-grid-item span="3 950:2">
|
<n-grid class="rec-func" x-gap="12" :cols="2">
|
||||||
|
<n-gi>
|
||||||
|
<PaRadar />
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<PaLikeSongs />
|
||||||
|
</n-gi>
|
||||||
|
</n-grid>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi class="rec-right">
|
||||||
<PaPersonalFm />
|
<PaPersonalFm />
|
||||||
</n-grid-item>
|
</n-gi>
|
||||||
</n-grid>
|
</n-grid>
|
||||||
<!-- 公共推荐 -->
|
<!-- 公共推荐 -->
|
||||||
<PaPlayLists />
|
<PaPlayLists />
|
||||||
@@ -26,11 +36,16 @@ import PaArtists from "@/components/Personalized/PaArtists.vue";
|
|||||||
import PaAlbum from "@/components/Personalized/PaAlbum.vue";
|
import PaAlbum from "@/components/Personalized/PaAlbum.vue";
|
||||||
import PaDailySongs from "@/components/Personalized/PaDailySongs.vue";
|
import PaDailySongs from "@/components/Personalized/PaDailySongs.vue";
|
||||||
import PaPersonalFm from "@/components/Personalized/PaPersonalFm.vue";
|
import PaPersonalFm from "@/components/Personalized/PaPersonalFm.vue";
|
||||||
|
import PaRadar from "@/components/Personalized/PaRadar.vue";
|
||||||
|
import PaLikeSongs from "@/components/Personalized/PaLikeSongs.vue";
|
||||||
|
|
||||||
const setting = settingStore();
|
const setting = settingStore();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
$setSiteTitle("SPlayer");
|
if (typeof $setSiteTitle !== "undefined")
|
||||||
|
$setSiteTitle(import.meta.env.VITE_SITE_TITLE);
|
||||||
|
// 回顶
|
||||||
|
if (typeof $scrollToTop !== "undefined") $scrollToTop();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -42,10 +57,25 @@ onMounted(() => {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
}
|
}
|
||||||
:deep(.recommendation) {
|
.recommend {
|
||||||
@media (max-width: 750px) {
|
@media (max-width: 850px) {
|
||||||
grid-template-columns: repeat(1, minmax(0px, 1fr)) !important;
|
grid-template-columns: repeat(1, minmax(0px, 1fr)) !important;
|
||||||
gap: 20px 0 !important;
|
gap: 20px 0 !important;
|
||||||
|
.rec-left {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
.padailysongs {
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.rec-left,
|
||||||
|
.rec-right {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
.rec-func {
|
||||||
|
height: 70px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="login">
|
<div class="login">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<img src="/images/logo/favicon.png" alt="logo" />
|
<img src="/images/logo/favicon.png" alt="logo" />
|
||||||
<span>登录云音乐</span>
|
<n-text>{{ $t("login.login", { name: siteTitle }) }}</n-text>
|
||||||
</div>
|
</div>
|
||||||
<n-tabs
|
<n-tabs
|
||||||
animated
|
animated
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
}"
|
}"
|
||||||
@update:value="tabChange"
|
@update:value="tabChange"
|
||||||
>
|
>
|
||||||
<n-tab-pane name="qr" tab="二维码登录">
|
<n-tab-pane name="qr" :tab="$t('login.qr')">
|
||||||
<n-card class="qr-img">
|
<n-card class="qr-img">
|
||||||
<n-skeleton
|
<n-skeleton
|
||||||
v-if="!qrImg"
|
v-if="!qrImg"
|
||||||
@@ -32,19 +32,19 @@
|
|||||||
:size="180"
|
:size="180"
|
||||||
level="H"
|
level="H"
|
||||||
background="#00000000"
|
background="#00000000"
|
||||||
foreground="#f55e55"
|
:foreground="setting.themeData.primaryColor"
|
||||||
/>
|
/>
|
||||||
</n-card>
|
</n-card>
|
||||||
<span class="tip">{{ qrText }}</span>
|
<span class="tip">{{ qrText }}</span>
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane name="phone" tab="手机号登录">
|
<n-tab-pane name="phone" :tab="$t('login.phone')">
|
||||||
<n-alert
|
<n-alert
|
||||||
style="width: 100%; margin-top: -20px; margin-bottom: 12px"
|
style="width: 100%; margin-top: -20px; margin-bottom: 12px"
|
||||||
type="warning"
|
type="warning"
|
||||||
>
|
>
|
||||||
该登录方式暂时无法使用
|
{{ $t("login.canNotUse") }}
|
||||||
</n-alert>
|
</n-alert>
|
||||||
<n-form
|
<!-- <n-form
|
||||||
class="phone"
|
class="phone"
|
||||||
ref="phoneFormRef"
|
ref="phoneFormRef"
|
||||||
:model="phoneFormData"
|
:model="phoneFormData"
|
||||||
@@ -83,18 +83,25 @@
|
|||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item>
|
<n-form-item>
|
||||||
<n-button style="width: 100%" type="primary" @click="phoneLogin">
|
<n-button style="width: 100%" type="primary" @click="phoneLogin">
|
||||||
登录
|
{{$t("login.login")}}
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form> -->
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="email" :tab="$t('login.email')">
|
||||||
|
<n-alert
|
||||||
|
style="width: 100%; margin-top: -20px; margin-bottom: 12px"
|
||||||
|
type="warning"
|
||||||
|
>
|
||||||
|
{{ $t("login.canNotUse") }}
|
||||||
|
</n-alert>
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane name="email" tab="邮箱登录"> 还没搞 </n-tab-pane>
|
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { userStore, musicStore } from "@/store";
|
import { userStore, musicStore, settingStore } from "@/store";
|
||||||
import {
|
import {
|
||||||
getLoginState,
|
getLoginState,
|
||||||
getQrKey,
|
getQrKey,
|
||||||
@@ -105,17 +112,21 @@ import {
|
|||||||
} from "@/api/login";
|
} from "@/api/login";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { PhoneAndroidRound, PasswordRound } from "@vicons/material";
|
import { PhoneAndroidRound, PasswordRound } from "@vicons/material";
|
||||||
import { formRules } from "@/utils/formRules.js";
|
import { formRules } from "@/utils/formRules";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import QrcodeVue from "qrcode.vue";
|
import QrcodeVue from "qrcode.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const user = userStore();
|
const user = userStore();
|
||||||
const music = musicStore();
|
const music = musicStore();
|
||||||
|
const setting = settingStore();
|
||||||
|
const siteTitle = import.meta.env.VITE_SITE_TITLE;
|
||||||
const { numberRule, mobileRule } = formRules();
|
const { numberRule, mobileRule } = formRules();
|
||||||
|
|
||||||
// 二维码数据
|
// 二维码数据
|
||||||
const qrImg = ref(null);
|
const qrImg = ref(null);
|
||||||
const qrText = ref("请打开云音乐 APP 扫码登录");
|
const qrText = ref(t("login.qrText1"));
|
||||||
|
|
||||||
// 手机号登录数据
|
// 手机号登录数据
|
||||||
const phoneFormRef = ref(null);
|
const phoneFormRef = ref(null);
|
||||||
@@ -139,21 +150,22 @@ const loginStateMessage = ref(null);
|
|||||||
|
|
||||||
// 储存登录信息
|
// 储存登录信息
|
||||||
const saveLoginData = (data) => {
|
const saveLoginData = (data) => {
|
||||||
|
data.cookie = data.cookie.replaceAll(" HTTPOnly", "");
|
||||||
user.setCookie(data.cookie);
|
user.setCookie(data.cookie);
|
||||||
// 验证用户登录信息
|
// 验证用户登录信息
|
||||||
getLoginState().then((res) => {
|
getLoginState().then((res) => {
|
||||||
if (res.data.profile) {
|
if (res.data.profile) {
|
||||||
user.setUserData(res.data.profile);
|
user.setUserData(res.data.profile);
|
||||||
user.userLogin = true;
|
user.userLogin = true;
|
||||||
qrText.value = "登录成功";
|
qrText.value = t("login.qrText4");
|
||||||
$message.success("登录成功");
|
$message.success(t("login.qrText4"));
|
||||||
// 自动签到
|
// 自动签到
|
||||||
if ($signIn) $signIn();
|
if ($signIn) $signIn();
|
||||||
clearInterval(qrCheckInterval.value);
|
clearInterval(qrCheckInterval.value);
|
||||||
router.go(-1);
|
router.push("/user");
|
||||||
} else {
|
} else {
|
||||||
user.userLogOut();
|
user.userLogOut();
|
||||||
$message.error("登录出错,请重试");
|
$message.error(t("login.qrText5"));
|
||||||
getQrKeyData();
|
getQrKeyData();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -164,9 +176,9 @@ const getQrKeyData = () => {
|
|||||||
// 检测是否登录
|
// 检测是否登录
|
||||||
getLoginState().then((res) => {
|
getLoginState().then((res) => {
|
||||||
if (res.data.profile && window.localStorage.getItem("cookie")) {
|
if (res.data.profile && window.localStorage.getItem("cookie")) {
|
||||||
$message.info("已登录,请勿重复登录");
|
$message.info(t("login.loggedIn"));
|
||||||
user.userLogin = true;
|
user.userLogin = true;
|
||||||
router.go(-1);
|
router.push("/user");
|
||||||
} else {
|
} else {
|
||||||
user.userLogOut();
|
user.userLogOut();
|
||||||
clearInterval(qrCheckInterval.value);
|
clearInterval(qrCheckInterval.value);
|
||||||
@@ -176,7 +188,7 @@ const getQrKeyData = () => {
|
|||||||
qrImg.value = `https://music.163.com/login?codekey=${res.data.unikey}`;
|
qrImg.value = `https://music.163.com/login?codekey=${res.data.unikey}`;
|
||||||
checkQrState(res.data.unikey);
|
checkQrState(res.data.unikey);
|
||||||
} else {
|
} else {
|
||||||
$message.error("登录二维码生成失败");
|
$message.error(t("login.qrText6"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -192,19 +204,16 @@ const checkQrState = (key) => {
|
|||||||
if (res.code == 800) {
|
if (res.code == 800) {
|
||||||
getQrKeyData();
|
getQrKeyData();
|
||||||
loginStateMessage.value = null;
|
loginStateMessage.value = null;
|
||||||
qrText.value = "当前二维码已失效,请重新扫码";
|
qrText.value = t("login.qrText2");
|
||||||
} else if (res.code == 801) {
|
} else if (res.code == 801) {
|
||||||
loginStateMessage.value = null;
|
loginStateMessage.value = null;
|
||||||
qrText.value = "请打开云音乐 APP 扫码登录";
|
qrText.value = t("login.qrText1");
|
||||||
} else if (res.code == 802) {
|
} else if (res.code == 802) {
|
||||||
qrText.value = "扫描成功,请在客户端确认登录";
|
qrText.value = t("login.qrText3");
|
||||||
if (!loginStateMessage.value) {
|
if (!loginStateMessage.value) {
|
||||||
loginStateMessage.value = $message.loading(
|
loginStateMessage.value = $message.loading(t("login.qrText3"), {
|
||||||
"扫描成功,请在客户端确认登录",
|
|
||||||
{
|
|
||||||
duration: 0,
|
duration: 0,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else if (res.code == 803) {
|
} else if (res.code == 803) {
|
||||||
loginStateMessage.value.destroy();
|
loginStateMessage.value.destroy();
|
||||||
@@ -289,7 +298,7 @@ const tabChange = (val) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
$setSiteTitle("登录");
|
$setSiteTitle(t("login.login"));
|
||||||
// 隐藏控制条
|
// 隐藏控制条
|
||||||
music.setPlayBarState(false);
|
music.setPlayBarState(false);
|
||||||
// 获取二维码登录 key
|
// 获取二维码登录 key
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="new-album">
|
<div class="new-album">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<span class="key">全部新碟</span>
|
<span class="key">{{ $t("home.title.newAlbum") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<n-space class="category">
|
<n-space class="category">
|
||||||
<n-tag
|
<n-tag
|
||||||
@@ -30,10 +30,12 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { getAlbumNew } from "@/api/album";
|
import { getAlbumNew } from "@/api/album";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { getLongTime } from "@/utils/timeTools.js";
|
import { getLongTime } from "@/utils/timeTools";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import CoverLists from "@/components/DataList/CoverLists.vue";
|
import CoverLists from "@/components/DataList/CoverLists.vue";
|
||||||
import Pagination from "@/components/Pagination/index.vue";
|
import Pagination from "@/components/Pagination/index.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// 新碟数据
|
// 新碟数据
|
||||||
@@ -52,24 +54,24 @@ const albumAreaChoose = ref(
|
|||||||
);
|
);
|
||||||
const albumArea = [
|
const albumArea = [
|
||||||
{
|
{
|
||||||
label: "全部",
|
label: t("general.type.all"),
|
||||||
value: "ALL",
|
value: "ALL",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "华语",
|
label: t("general.type.china"),
|
||||||
value: "ZH",
|
value: "ZH",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "欧美",
|
label: t("general.type.western"),
|
||||||
value: "EA",
|
value: "EA",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "韩国",
|
label: t("general.type.japan"),
|
||||||
value: "KR",
|
value: "JP",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "日本",
|
label: t("general.type.korea"),
|
||||||
value: "JP",
|
value: "KR",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -92,10 +94,10 @@ const getAlbumNewData = (area, limit = 30, offset = 0) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$message.error("全部新碟为空");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
}
|
}
|
||||||
// 请求后回顶并结束加载条
|
// 请求后回顶
|
||||||
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
|
if (typeof $scrollToTop !== "undefined") $scrollToTop();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -149,7 +151,7 @@ const changeArea = (area) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
$setSiteTitle("全部新碟");
|
$setSiteTitle(t("home.title.newAlbum"));
|
||||||
getAlbumNewData(
|
getAlbumNewData(
|
||||||
albumAreaChoose.value,
|
albumAreaChoose.value,
|
||||||
pagelimit.value,
|
pagelimit.value,
|
||||||
@@ -178,8 +180,8 @@ onMounted(() => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $mainSecondaryColor;
|
background-color: var(--main-second-color);
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.9);
|
transform: scale(0.9);
|
||||||
|
|||||||
@@ -2,54 +2,51 @@
|
|||||||
<div class="playlist" v-if="playListDetail">
|
<div class="playlist" v-if="playListDetail">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
<n-avatar
|
<n-image
|
||||||
|
show-toolbar-tooltip
|
||||||
class="coverImg"
|
class="coverImg"
|
||||||
:src="
|
:src="getCoverUrl(playListDetail.coverImgUrl, 1024)"
|
||||||
playListDetail.coverImgUrl
|
:previewed-img-props="{ style: { borderRadius: '8px' } }"
|
||||||
? playListDetail.coverImgUrl.replace(/^http:/, 'https:') +
|
:preview-src="getCoverUrl(playListDetail.coverImgUrl)"
|
||||||
'?param=1024y1024'
|
|
||||||
: null
|
|
||||||
"
|
|
||||||
fallback-src="/images/pic/default.png"
|
fallback-src="/images/pic/default.png"
|
||||||
/>
|
/>
|
||||||
<img src="/images/pic/album.png" class="album" alt="album" />
|
<n-image
|
||||||
|
preview-disabled
|
||||||
|
class="shadow"
|
||||||
|
:src="getCoverUrl(playListDetail.coverImgUrl, 1024)"
|
||||||
|
fallback-src="/images/pic/default.png"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="meta">
|
||||||
|
<div class="title">
|
||||||
|
<n-text class="name text-hidden">{{ playListDetail.name }}</n-text>
|
||||||
|
<n-text class="creator">{{ playListDetail.creator.nickname }}</n-text>
|
||||||
</div>
|
</div>
|
||||||
<div class="intr">
|
<div class="intr">
|
||||||
<span class="name">歌单简介</span>
|
<span class="name">{{
|
||||||
|
$t("general.name.desc", { name: $t("general.name.playlist") })
|
||||||
|
}}</span>
|
||||||
<span class="desc text-hidden">
|
<span class="desc text-hidden">
|
||||||
{{
|
{{
|
||||||
playListDetail.description
|
playListDetail.description
|
||||||
? playListDetail.description
|
? playListDetail.description
|
||||||
: "太懒了吧,连简介都不写"
|
: $t("other.noDesc")
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<n-button
|
<n-button
|
||||||
|
class="all-desc"
|
||||||
block
|
block
|
||||||
strong
|
strong
|
||||||
secondary
|
secondary
|
||||||
v-if="playListDetail?.description?.length > 70"
|
v-if="playListDetail?.description?.length > 70"
|
||||||
@click="playListDescShow = true"
|
@click="playListDescShow = true"
|
||||||
>
|
>
|
||||||
全部简介
|
{{ $t("general.name.allDesc") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-modal
|
|
||||||
class="s-modal"
|
|
||||||
v-model:show="playListDescShow"
|
|
||||||
preset="card"
|
|
||||||
title="歌单简介"
|
|
||||||
:bordered="false"
|
|
||||||
>
|
|
||||||
<n-scrollbar>
|
|
||||||
<n-text
|
|
||||||
v-html="playListDetail.description.replace(/\n/g, '<br>')"
|
|
||||||
/>
|
|
||||||
</n-scrollbar>
|
|
||||||
</n-modal>
|
|
||||||
</div>
|
</div>
|
||||||
<n-space class="tag" v-if="playListDetail.tags">
|
<n-space class="tag" v-if="playListDetail.tags">
|
||||||
<n-tag
|
<n-tag
|
||||||
class="tags"
|
class="tags"
|
||||||
size="large"
|
|
||||||
round
|
round
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
v-for="item in playListDetail.tags"
|
v-for="item in playListDetail.tags"
|
||||||
@@ -59,36 +56,45 @@
|
|||||||
{{ item }}
|
{{ item }}
|
||||||
</n-tag>
|
</n-tag>
|
||||||
</n-space>
|
</n-space>
|
||||||
<!-- <div class="control" v-if="true">
|
<n-space class="control">
|
||||||
<n-space>
|
<n-button strong secondary round type="primary" @click="playAllSong">
|
||||||
<n-button strong secondary round>
|
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon :component="EditNoteRound" />
|
<n-icon :component="MusicList" />
|
||||||
</template>
|
</template>
|
||||||
编辑
|
{{ $t("general.name.play") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button strong secondary round type="primary">
|
<n-dropdown
|
||||||
|
placement="right-start"
|
||||||
|
trigger="click"
|
||||||
|
:show-arrow="true"
|
||||||
|
:options="dropdownOptions"
|
||||||
|
>
|
||||||
|
<n-button strong secondary circle>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon :component="DeleteRound" />
|
<n-icon :component="More" />
|
||||||
</template>
|
</template>
|
||||||
</n-button>
|
</n-button>
|
||||||
|
</n-dropdown>
|
||||||
</n-space>
|
</n-space>
|
||||||
</div> -->
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<span class="name">{{ playListDetail.name }}</span>
|
<n-text class="name">{{ playListDetail.name }}</n-text>
|
||||||
<span class="creator">{{ playListDetail.creator.nickname }}</span>
|
<n-text class="creator">
|
||||||
<div class="time">
|
<n-icon :depth="3" :component="People" />
|
||||||
<div class="createTime">
|
{{ playListDetail.creator.nickname }}
|
||||||
<span class="num">创建时间:</span>
|
</n-text>
|
||||||
{{ getLongTime(playListDetail.createTime) }}
|
<n-space class="time">
|
||||||
</div>
|
<div class="num">
|
||||||
<div class="updateTime">
|
<n-icon :depth="3" :component="Newlybuild" />
|
||||||
<span class="num">更新时间:</span>
|
<n-text v-html="getLongTime(playListDetail.createTime)" />
|
||||||
{{ getLongTime(playListDetail.updateTime) }}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="num">
|
||||||
|
<n-icon :depth="3" :component="Write" />
|
||||||
|
<n-text v-html="getLongTime(playListDetail.updateTime)" />
|
||||||
</div>
|
</div>
|
||||||
|
</n-space>
|
||||||
</div>
|
</div>
|
||||||
<DataLists :listData="playListData" />
|
<DataLists :listData="playListData" />
|
||||||
<Pagination
|
<Pagination
|
||||||
@@ -99,13 +105,29 @@
|
|||||||
@pageSizeChange="pageSizeChange"
|
@pageSizeChange="pageSizeChange"
|
||||||
@pageNumberChange="pageNumberChange"
|
@pageNumberChange="pageNumberChange"
|
||||||
/>
|
/>
|
||||||
|
<!-- 歌单简介 -->
|
||||||
|
<n-modal
|
||||||
|
class="s-modal"
|
||||||
|
v-model:show="playListDescShow"
|
||||||
|
preset="card"
|
||||||
|
:title="$t('general.name.desc', { name: $t('general.name.playlist') })"
|
||||||
|
:bordered="false"
|
||||||
|
>
|
||||||
|
<n-scrollbar>
|
||||||
|
<n-text v-html="playListDetail.description.replace(/\n/g, '<br>')" />
|
||||||
|
</n-scrollbar>
|
||||||
|
</n-modal>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="title" v-else-if="!playListId">
|
<div class="title" v-else-if="!playListId || !loadingState">
|
||||||
<span class="key">参数不完整</span>
|
<span class="key">{{
|
||||||
|
loadingState
|
||||||
|
? $t("general.name.noKeywords")
|
||||||
|
: $t("general.message.acquisitionFailed")
|
||||||
|
}}</span>
|
||||||
<br />
|
<br />
|
||||||
<n-button strong secondary @click="router.go(-1)" style="margin-top: 20px">
|
<n-button strong secondary @click="router.go(-1)" style="margin-top: 20px">
|
||||||
返回上一级
|
{{ $t("general.name.goBack") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="loading" v-else>
|
<div class="loading" v-else>
|
||||||
@@ -122,16 +144,38 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { getPlayListDetail, getAllPlayList } from "@/api/playlist";
|
import { NIcon, NAvatar, NText } from "naive-ui";
|
||||||
|
import {
|
||||||
|
getPlayListDetail,
|
||||||
|
getAllPlayList,
|
||||||
|
delPlayList,
|
||||||
|
likePlaylist,
|
||||||
|
} from "@/api/playlist";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { userStore, musicStore } from "@/store";
|
import { userStore, musicStore, settingStore } from "@/store";
|
||||||
import { getSongTime, getLongTime } from "@/utils/timeTools.js";
|
import { getSongTime, getLongTime } from "@/utils/timeTools";
|
||||||
import { EditNoteRound, DeleteRound } from "@vicons/material";
|
import {
|
||||||
|
MusicList,
|
||||||
|
LinkTwo,
|
||||||
|
More,
|
||||||
|
DeleteFour,
|
||||||
|
Like,
|
||||||
|
Unlike,
|
||||||
|
Newlybuild,
|
||||||
|
Write,
|
||||||
|
People,
|
||||||
|
} from "@icon-park/vue-next";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import DataLists from "@/components/DataList/DataLists.vue";
|
import DataLists from "@/components/DataList/DataLists.vue";
|
||||||
import Pagination from "@/components/Pagination/index.vue";
|
import Pagination from "@/components/Pagination/index.vue";
|
||||||
|
import getCoverUrl from "@/utils/getCoverUrl";
|
||||||
|
// import SpecialPlayLists from "./SpecialPlayLists.json";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const user = userStore();
|
const user = userStore();
|
||||||
const music = musicStore();
|
const music = musicStore();
|
||||||
|
const setting = settingStore();
|
||||||
|
|
||||||
// 歌单数据
|
// 歌单数据
|
||||||
const playListId = ref(router.currentRoute.value.query.id);
|
const playListId = ref(router.currentRoute.value.query.id);
|
||||||
@@ -139,6 +183,7 @@ const playListDetail = ref(null);
|
|||||||
const playListData = ref([]);
|
const playListData = ref([]);
|
||||||
const playListDescShow = ref(false);
|
const playListDescShow = ref(false);
|
||||||
const pagelimit = ref(30);
|
const pagelimit = ref(30);
|
||||||
|
const loadingState = ref(true);
|
||||||
const pageNumber = ref(
|
const pageNumber = ref(
|
||||||
router.currentRoute.value.query.page
|
router.currentRoute.value.query.page
|
||||||
? Number(router.currentRoute.value.query.page)
|
? Number(router.currentRoute.value.query.page)
|
||||||
@@ -146,19 +191,114 @@ const pageNumber = ref(
|
|||||||
);
|
);
|
||||||
const totalCount = ref(0);
|
const totalCount = ref(0);
|
||||||
|
|
||||||
|
// 图标渲染
|
||||||
|
const renderIcon = (icon) => {
|
||||||
|
return () => {
|
||||||
|
return h(
|
||||||
|
NIcon,
|
||||||
|
{ style: { transform: "translateX(2px)" } },
|
||||||
|
{
|
||||||
|
default: () => icon,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 判断收藏还是取消
|
||||||
|
const isLikeOrDislike = (id) => {
|
||||||
|
const playlists = user.getUserPlayLists.like;
|
||||||
|
if (playlists.length) {
|
||||||
|
return !playlists.some((item) => item.id === Number(id));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 判断是否可删除
|
||||||
|
const isCanDelete = (id) => {
|
||||||
|
const playlists = user.getUserPlayLists.own;
|
||||||
|
if (playlists.length) {
|
||||||
|
return playlists.some((item) => item.id === Number(id));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 歌单下拉菜单数据
|
||||||
|
const dropdownOptions = ref([]);
|
||||||
|
|
||||||
|
// 更改歌单下拉菜单数据
|
||||||
|
const setDropdownOptions = () => {
|
||||||
|
dropdownOptions.value = [
|
||||||
|
{
|
||||||
|
key: "copy",
|
||||||
|
label: t("menu.copy", {
|
||||||
|
name: t("general.name.playlist"),
|
||||||
|
other: t("general.name.link"),
|
||||||
|
}),
|
||||||
|
props: {
|
||||||
|
onClick: () => {
|
||||||
|
if (navigator.clipboard) {
|
||||||
|
try {
|
||||||
|
navigator.clipboard.writeText(
|
||||||
|
`https://music.163.com/#/playlist?id=${playListId.value}`
|
||||||
|
);
|
||||||
|
$message.success(t("general.message.copySuccess"));
|
||||||
|
} catch (err) {
|
||||||
|
console.error(t("general.message.copyFailure"), err);
|
||||||
|
$message.error(t("general.message.copyFailure"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$message.error(t("general.message.notSupported"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
icon: renderIcon(h(LinkTwo)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "del",
|
||||||
|
label: t("menu.del"),
|
||||||
|
show: user.userLogin && isCanDelete(playListId.value),
|
||||||
|
props: {
|
||||||
|
onClick: () => {
|
||||||
|
toDelPlayList(playListDetail.value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
icon: renderIcon(h(DeleteFour)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "like",
|
||||||
|
label: isLikeOrDislike(playListId.value)
|
||||||
|
? t("menu.collection", { name: t("general.name.playlist") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.name.playlist") }),
|
||||||
|
show: user.userLogin && !isCanDelete(playListId.value),
|
||||||
|
props: {
|
||||||
|
onClick: () => {
|
||||||
|
toChangeLike(playListId.value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
icon: renderIcon(h(isLikeOrDislike(playListId.value) ? Like : Unlike)),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
// 获取歌单信息
|
// 获取歌单信息
|
||||||
const getPlayListDetailData = (id) => {
|
const getPlayListDetailData = (id) => {
|
||||||
getPlayListDetail(id).then((res) => {
|
getPlayListDetail(id)
|
||||||
|
.then((res) => {
|
||||||
console.log(res);
|
console.log(res);
|
||||||
if (res.playlist) {
|
|
||||||
// 歌单总数
|
// 歌单总数
|
||||||
totalCount.value = res.playlist.trackCount;
|
totalCount.value = res.playlist.trackCount;
|
||||||
// 歌单信息
|
// 歌单信息
|
||||||
playListDetail.value = res.playlist;
|
playListDetail.value = res.playlist;
|
||||||
$setSiteTitle(res.playlist.name + " - 歌单");
|
$setSiteTitle(res.playlist.name + " - " + t("general.name.playlist"));
|
||||||
} else {
|
})
|
||||||
$message.error("获取歌单信息失败");
|
.catch((err) => {
|
||||||
}
|
$setSiteTitle(t("general.name.playlist"));
|
||||||
|
loadingState.value = false;
|
||||||
|
console.error(
|
||||||
|
$message.error(t("general.message.acquisitionFailed")),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -178,18 +318,127 @@ const getAllPlayListData = (id, limit = 30, offset = 0) => {
|
|||||||
alia: v.alia,
|
alia: v.alia,
|
||||||
time: getSongTime(v.dt),
|
time: getSongTime(v.dt),
|
||||||
fee: v.fee,
|
fee: v.fee,
|
||||||
|
sourceId: id,
|
||||||
pc: v.pc ? v.pc : null,
|
pc: v.pc ? v.pc : null,
|
||||||
mv: v.mv ? v.mv : null,
|
mv: v.mv ? v.mv : null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$message.error("获取歌单内歌曲失败");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
}
|
}
|
||||||
// 请求后回顶
|
// 请求后回顶
|
||||||
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
|
if (typeof $scrollToTop !== "undefined") $scrollToTop();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 播放歌单所有歌曲
|
||||||
|
const playAllSong = () => {
|
||||||
|
try {
|
||||||
|
// 获取元素
|
||||||
|
const songDom = document.getElementById("datalists").firstElementChild;
|
||||||
|
const allSongDom = document.querySelectorAll("#datalists > *");
|
||||||
|
// 是否有元素存在 play
|
||||||
|
let isHasPlay = false;
|
||||||
|
// 遍历
|
||||||
|
allSongDom.forEach((child) => {
|
||||||
|
if (child.classList.contains("play")) {
|
||||||
|
isHasPlay = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!isHasPlay) {
|
||||||
|
// 双击操作
|
||||||
|
const event = new MouseEvent("dblclick", {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
view: window,
|
||||||
|
});
|
||||||
|
// 双击或单击
|
||||||
|
if (setting.listClickMode === "dblclick") {
|
||||||
|
songDom.dispatchEvent(event);
|
||||||
|
} else if (setting.listClickMode === "click") {
|
||||||
|
songDom.click();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
music.setPlayState(true);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error($message.error(t("general.message.operationFailed")), err);
|
||||||
|
$message.error($message.error(t("general.message.operationFailed")));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除歌单
|
||||||
|
const toDelPlayList = (data) => {
|
||||||
|
if (data.id === user.getUserPlayLists?.own[0].id) {
|
||||||
|
$message.warning(t("menu.unableToDelete"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$dialog.warning({
|
||||||
|
class: "s-dialog",
|
||||||
|
title: t("general.dialog.delete"),
|
||||||
|
content: t("menu.delQuestion", {
|
||||||
|
name: data.name,
|
||||||
|
}),
|
||||||
|
positiveText: t("general.dialog.delete"),
|
||||||
|
negativeText: t("general.dialog.cancel"),
|
||||||
|
onPositiveClick: () => {
|
||||||
|
delPlayList(data.id).then((res) => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
$message.success(t("general.message.deleteSuccess"));
|
||||||
|
user.setUserPlayLists();
|
||||||
|
router.push("/user/playlists");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 收藏/取消收藏
|
||||||
|
const toChangeLike = async (id) => {
|
||||||
|
const type = isLikeOrDislike(id) ? 1 : 2;
|
||||||
|
const likeMsg = t("general.name.playlist");
|
||||||
|
const isThereASpace = setting.language === "zh-CN" ? "" : " ";
|
||||||
|
try {
|
||||||
|
const res = await likePlaylist(type, id);
|
||||||
|
if (res.code === 200) {
|
||||||
|
$message.success(
|
||||||
|
`${likeMsg + isThereASpace}${
|
||||||
|
type == 1
|
||||||
|
? t("menu.collection", { name: t("general.dialog.success") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.dialog.success") })
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
user.setUserPlayLists(() => {
|
||||||
|
setDropdownOptions();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$message.error(
|
||||||
|
`${likeMsg + isThereASpace}${
|
||||||
|
type == 1
|
||||||
|
? t("menu.collection", { name: t("general.dialog.failed") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.dialog.failed") })
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
$message.error(
|
||||||
|
`${likeMsg + isThereASpace}${
|
||||||
|
type == 1
|
||||||
|
? t("menu.collection", { name: t("general.dialog.failed") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.dialog.failed") })
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
console.error(
|
||||||
|
`${likeMsg + isThereASpace}${
|
||||||
|
type == 1
|
||||||
|
? t("menu.collection", { name: t("general.dialog.failed") })
|
||||||
|
: t("menu.cancelCollection", { name: t("general.dialog.failed") })
|
||||||
|
}`,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (playListId.value) {
|
if (playListId.value) {
|
||||||
getPlayListDetailData(playListId.value);
|
getPlayListDetailData(playListId.value);
|
||||||
@@ -198,6 +447,17 @@ onMounted(() => {
|
|||||||
pagelimit.value,
|
pagelimit.value,
|
||||||
(pageNumber.value - 1) * pagelimit.value
|
(pageNumber.value - 1) * pagelimit.value
|
||||||
);
|
);
|
||||||
|
if (
|
||||||
|
user.userLogin &&
|
||||||
|
!user.getUserPlayLists.has &&
|
||||||
|
!user.getUserPlayLists.isLoading
|
||||||
|
) {
|
||||||
|
user.setUserPlayLists(() => {
|
||||||
|
setDropdownOptions();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setDropdownOptions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -265,22 +525,64 @@ watch(
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 24px;
|
top: 24px;
|
||||||
|
@media (max-width: 990px) {
|
||||||
|
margin-right: 0;
|
||||||
|
width: 30vw;
|
||||||
|
}
|
||||||
.cover {
|
.cover {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
// box-shadow: 0 0 16px 0px rgb(0 0 0 / 20%);
|
|
||||||
.n-avatar {
|
|
||||||
border-radius: 8px;
|
|
||||||
width: 80%;
|
width: 80%;
|
||||||
height: 80%;
|
height: 80%;
|
||||||
|
border-radius: 8px;
|
||||||
|
position: relative;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
.album {
|
.coverImg {
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1;
|
||||||
|
:deep(img) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.shadow {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 12px;
|
||||||
right: 4%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
filter: blur(16px) opacity(0.6);
|
||||||
|
transform: scale(0.92, 0.96);
|
||||||
|
z-index: 0;
|
||||||
|
background-size: cover;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.meta {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
.title {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 0;
|
||||||
|
.name {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
}
|
||||||
|
.creator {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.intr {
|
.intr {
|
||||||
@@ -292,6 +594,9 @@ watch(
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
@media (max-width: 990px) {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.desc {
|
.desc {
|
||||||
-webkit-line-clamp: 4;
|
-webkit-line-clamp: 4;
|
||||||
@@ -302,17 +607,23 @@ watch(
|
|||||||
.tag {
|
.tag {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
.tags {
|
.tags {
|
||||||
|
line-height: 0;
|
||||||
|
font-size: 13px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $mainSecondaryColor;
|
background-color: var(--main-second-color);
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.control {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.right {
|
.right {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -326,6 +637,8 @@ watch(
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.creator {
|
.creator {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
@@ -333,7 +646,10 @@ watch(
|
|||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
|
}
|
||||||
|
.n-icon {
|
||||||
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.time {
|
.time {
|
||||||
@@ -346,10 +662,13 @@ watch(
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
.num {
|
.num {
|
||||||
color: #999;
|
// color: #999;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
.n-icon {
|
||||||
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
div {
|
|
||||||
margin-right: 12px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -367,21 +686,109 @@ watch(
|
|||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
.left {
|
.left {
|
||||||
margin-bottom: 12px;
|
position: relative;
|
||||||
position: static;
|
top: 0;
|
||||||
width: 60vw;
|
width: 100%;
|
||||||
|
height: 40vw;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
.intr,
|
display: flex;
|
||||||
.tag {
|
flex-direction: row;
|
||||||
|
.cover {
|
||||||
|
height: 100%;
|
||||||
|
min-width: 40vw;
|
||||||
|
margin-right: 30px;
|
||||||
|
}
|
||||||
|
.meta {
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
.name {
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
.creator {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.intr {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
.name,
|
||||||
|
.all-desc {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.desc {
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.control {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: -60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.right {
|
.right {
|
||||||
|
margin-top: 80px;
|
||||||
.meta {
|
.meta {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 540px) {
|
||||||
|
.left {
|
||||||
|
.cover {
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
.meta {
|
||||||
|
.title {
|
||||||
.name {
|
.name {
|
||||||
font-size: 26px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.intr,
|
||||||
|
.tag {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.control {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
.left {
|
||||||
|
.meta {
|
||||||
|
.title {
|
||||||
|
margin-bottom: 0;
|
||||||
|
.name {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.creator {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 370px) {
|
||||||
|
.left {
|
||||||
|
.meta {
|
||||||
|
.title {
|
||||||
|
.name {
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.control {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
margin-top: 80px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
src/views/PlayList/SpecialPlayLists.json
Normal file
6
src/views/PlayList/SpecialPlayLists.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 3136952023,
|
||||||
|
"name": "私人雷达"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -14,9 +14,12 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { getSearchData } from "@/api/search";
|
import { getSearchData } from "@/api/search";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { getLongTime } from "@/utils/timeTools.js";
|
import { getLongTime } from "@/utils/timeTools";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import CoverLists from "@/components/DataList/CoverLists.vue";
|
import CoverLists from "@/components/DataList/CoverLists.vue";
|
||||||
import Pagination from "@/components/Pagination/index.vue";
|
import Pagination from "@/components/Pagination/index.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// 搜索数据
|
// 搜索数据
|
||||||
@@ -49,10 +52,10 @@ const getSearchDataList = (keywords, limit = 30, offset = 0, type = 10) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$message.error("搜索内容为空");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
}
|
}
|
||||||
// 请求后回顶并结束加载条
|
// 请求后回顶
|
||||||
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
|
if (typeof $scrollToTop !== "undefined") $scrollToTop();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,10 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { getSearchData } from "@/api/search";
|
import { getSearchData } from "@/api/search";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import ArtistLists from "@/components/DataList/ArtistLists.vue";
|
import ArtistLists from "@/components/DataList/ArtistLists.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// 搜索数据
|
// 搜索数据
|
||||||
@@ -35,10 +37,10 @@ const getSearchDataList = (keywords, limit = 30, offset = 0, type = 100) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$message.error("搜索内容为空");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
}
|
}
|
||||||
// 请求后回顶并结束加载条
|
// 请求后回顶
|
||||||
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
|
if (typeof $scrollToTop !== "undefined") $scrollToTop();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<div class="title" v-if="searchKeywords">
|
<div class="title" v-if="searchKeywords">
|
||||||
<span class="key">{{ searchKeywords }}</span>
|
<n-text class="key" v-html="searchKeywords" />
|
||||||
<span>的搜索结果</span>
|
<n-text v-html="$t('nav.search.results')" />
|
||||||
</div>
|
</div>
|
||||||
<div class="title" v-else>
|
<div class="title" v-else>
|
||||||
<span class="key">未提供搜索关键字</span>
|
<span class="key">{{ $t("general.name.noKeywords") }}</span>
|
||||||
<br />
|
<br />
|
||||||
<n-button
|
<n-button
|
||||||
strong
|
strong
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
@click="router.go(-1)"
|
@click="router.go(-1)"
|
||||||
style="margin-top: 20px"
|
style="margin-top: 20px"
|
||||||
>
|
>
|
||||||
返回上一级
|
{{ $t("general.name.goBack") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
<n-tabs
|
<n-tabs
|
||||||
@@ -23,11 +23,11 @@
|
|||||||
v-model:value="tabValue"
|
v-model:value="tabValue"
|
||||||
v-if="searchKeywords"
|
v-if="searchKeywords"
|
||||||
>
|
>
|
||||||
<n-tab name="songs"> 单曲 </n-tab>
|
<n-tab name="songs">{{ $t("general.name.song") }}</n-tab>
|
||||||
<n-tab name="artists"> 歌手 </n-tab>
|
<n-tab name="artists">{{ $t("general.name.artists") }}</n-tab>
|
||||||
<n-tab name="albums"> 专辑 </n-tab>
|
<n-tab name="albums">{{ $t("general.name.album") }}</n-tab>
|
||||||
<n-tab name="videos"> 视频 </n-tab>
|
<n-tab name="videos">{{ $t("general.name.videos") }}</n-tab>
|
||||||
<n-tab name="playlists"> 歌单 </n-tab>
|
<n-tab name="playlists">{{ $t("general.name.playlist") }}</n-tab>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
<main class="content" v-if="searchKeywords">
|
<main class="content" v-if="searchKeywords">
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
@@ -43,6 +43,9 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// 搜索关键词
|
// 搜索关键词
|
||||||
@@ -74,7 +77,8 @@ const tabChange = (value) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
$setSiteTitle(searchKeywords.value + "的搜索结果");
|
if (searchKeywords.value)
|
||||||
|
$setSiteTitle(searchKeywords.value + " " + t("nav.search.results"));
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,12 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { getSearchData } from "@/api/search";
|
import { getSearchData } from "@/api/search";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { formatNumber } from "@/utils/timeTools.js";
|
import { formatNumber } from "@/utils/timeTools";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import CoverLists from "@/components/DataList/CoverLists.vue";
|
import CoverLists from "@/components/DataList/CoverLists.vue";
|
||||||
import Pagination from "@/components/Pagination/index.vue";
|
import Pagination from "@/components/Pagination/index.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// 搜索数据
|
// 搜索数据
|
||||||
@@ -49,10 +52,10 @@ const getSearchDataList = (keywords, limit = 30, offset = 0, type = 1000) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$message.error("搜索内容为空");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
}
|
}
|
||||||
// 请求后回顶
|
// 请求后回顶
|
||||||
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
|
if (typeof $scrollToTop !== "undefined") $scrollToTop();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,11 +13,14 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { getSearchData } from "@/api/search";
|
import { getSearchData } from "@/api/search";
|
||||||
import { getMusicDetail } from "@/api/song";
|
// import { getMusicDetail } from "@/api/song";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { getSongTime } from "@/utils/timeTools.js";
|
import { getSongTime } from "@/utils/timeTools";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import DataLists from "@/components/DataList/DataLists.vue";
|
import DataLists from "@/components/DataList/DataLists.vue";
|
||||||
import Pagination from "@/components/Pagination/index.vue";
|
import Pagination from "@/components/Pagination/index.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// 搜索数据
|
// 搜索数据
|
||||||
@@ -58,10 +61,10 @@ const getSearchDataList = (keywords, limit = 30, offset = 0, type = 1) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$message.error("搜索内容为空");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
}
|
}
|
||||||
// 请求后回顶并结束加载条
|
// 请求后回顶
|
||||||
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
|
if (typeof $scrollToTop !== "undefined") $scrollToTop();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,12 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { getSearchData } from "@/api/search";
|
import { getSearchData } from "@/api/search";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { formatNumber, getSongTime } from "@/utils/timeTools.js";
|
import { formatNumber, getSongTime } from "@/utils/timeTools";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import VideoLists from "@/components/DataList/VideoLists.vue";
|
import VideoLists from "@/components/DataList/VideoLists.vue";
|
||||||
import Pagination from "@/components/Pagination/index.vue";
|
import Pagination from "@/components/Pagination/index.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// 搜索数据
|
// 搜索数据
|
||||||
@@ -50,10 +53,10 @@ const getSearchDataList = (keywords, limit = 30, offset = 0, type = 1004) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$message.error("搜索内容为空");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
}
|
}
|
||||||
// 请求后回顶并结束加载条
|
// 请求后回顶
|
||||||
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
|
if (typeof $scrollToTop !== "undefined") $scrollToTop();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="setting">
|
<div class="setting">
|
||||||
<div class="title">全局设置</div>
|
<div class="title">{{ $t("nav.avatar.setting") }}</div>
|
||||||
<n-tabs
|
<n-tabs
|
||||||
class="main-tab"
|
class="main-tab"
|
||||||
type="segment"
|
type="segment"
|
||||||
@update:value="tabChange"
|
@update:value="tabChange"
|
||||||
v-model:value="tabValue"
|
v-model:value="tabValue"
|
||||||
>
|
>
|
||||||
<n-tab name="main"> 基础 </n-tab>
|
<n-tab name="main">{{ $t("setting.main") }}</n-tab>
|
||||||
<n-tab name="player"> 播放器 </n-tab>
|
<n-tab name="player">{{ $t("setting.player") }}</n-tab>
|
||||||
<n-tab name="other"> 其他 </n-tab>
|
<n-tab name="other">{{ $t("general.type.other") }}</n-tab>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
<main class="content">
|
<main class="content">
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
@@ -25,7 +25,9 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// Tab 默认选中
|
// Tab 默认选中
|
||||||
@@ -48,8 +50,9 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
$setSiteTitle("全局设置");
|
$setSiteTitle(t("nav.avatar.setting"));
|
||||||
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
|
// 回顶
|
||||||
|
if (typeof $scrollToTop !== "undefined") $scrollToTop();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -79,6 +82,14 @@ onMounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
|
.dev {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
.n-tag {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
.tip {
|
.tip {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
|
|||||||
@@ -1,28 +1,85 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="set-main">
|
<div class="set-main">
|
||||||
<n-card class="set-item">
|
<n-card
|
||||||
<div class="name">明暗模式</div>
|
class="set-item"
|
||||||
<n-select class="set" v-model:value="theme" :options="darkOptions" />
|
:content-style="{
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="top">
|
||||||
|
<div class="name">
|
||||||
|
{{ $t("setting.themeType") }}
|
||||||
|
<span class="tip">{{ $t("setting.themeTypeTip") }}</span>
|
||||||
|
</div>
|
||||||
|
<n-button
|
||||||
|
v-if="themeType !== 'red'"
|
||||||
|
strong
|
||||||
|
secondary
|
||||||
|
@click="changeThemeColor(null, true)"
|
||||||
|
>
|
||||||
|
{{ $t("general.name.restore") }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
<n-grid
|
||||||
|
class="color-selete"
|
||||||
|
:x-gap="16"
|
||||||
|
:y-gap="16"
|
||||||
|
responsive="screen"
|
||||||
|
cols="3 s:4 m:5 l:6"
|
||||||
|
>
|
||||||
|
<n-grid-item
|
||||||
|
v-for="item in themeColorData"
|
||||||
|
:key="item"
|
||||||
|
:style="{ '--color': item.primaryColor }"
|
||||||
|
:class="item.label === themeType ? 'item check' : 'item'"
|
||||||
|
@click="changeThemeColor(item)"
|
||||||
|
>
|
||||||
|
<n-text v-html="language === 'zh-CN' ? item.name : item.label" />
|
||||||
|
</n-grid-item>
|
||||||
|
</n-grid>
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card class="set-item">
|
<n-card class="set-item">
|
||||||
<div class="name">明暗模式跟随系统</div>
|
<div class="name">{{ $t("setting.language") }}</div>
|
||||||
<n-switch v-model:value="themeAuto" :round="false" />
|
<n-select
|
||||||
|
class="set"
|
||||||
|
v-model:value="language"
|
||||||
|
:options="languageOptions"
|
||||||
|
@update:value="changeLanguage"
|
||||||
|
/>
|
||||||
|
</n-card>
|
||||||
|
<n-card class="set-item">
|
||||||
|
<div class="name">{{ $t("setting.theme") }}</div>
|
||||||
|
<n-select
|
||||||
|
class="set"
|
||||||
|
v-model:value="theme"
|
||||||
|
:options="themeOptions"
|
||||||
|
@update:value="themeAuto = false"
|
||||||
|
/>
|
||||||
|
</n-card>
|
||||||
|
<n-card class="set-item">
|
||||||
|
<div class="name">{{ $t("setting.themeAuto") }}</div>
|
||||||
|
<n-switch
|
||||||
|
v-model:value="themeAuto"
|
||||||
|
:round="false"
|
||||||
|
@update:value="themeAutoOpen"
|
||||||
|
/>
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card class="set-item">
|
<n-card class="set-item">
|
||||||
<div class="name">
|
<div class="name">
|
||||||
每日签到
|
{{ $t("setting.autoSignIn") }}
|
||||||
<span class="tip">是否自动进行每日签到</span>
|
<span class="tip">{{ $t("setting.autoSignInTip") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<n-switch v-model:value="autoSignIn" :round="false" />
|
<n-switch v-model:value="autoSignIn" :round="false" />
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card class="set-item">
|
<n-card class="set-item">
|
||||||
<div class="name">显示轮播图</div>
|
<div class="name">{{ $t("setting.bannerShow") }}</div>
|
||||||
<n-switch v-model:value="bannerShow" :round="false" />
|
<n-switch v-model:value="bannerShow" :round="false" />
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card class="set-item">
|
<n-card class="set-item">
|
||||||
<div class="name">
|
<div class="name">
|
||||||
列表点击方式
|
{{ $t("setting.listClickMode") }}
|
||||||
<span class="tip">移动端该设置项无效,单击同时生效</span>
|
<span class="tip">{{ $t("setting.listClickModeTip") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<n-select
|
<n-select
|
||||||
class="set"
|
class="set"
|
||||||
@@ -31,20 +88,36 @@
|
|||||||
/>
|
/>
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card class="set-item">
|
<n-card class="set-item">
|
||||||
<div class="name">显示搜索历史</div>
|
<div class="name">{{ $t("setting.searchHistory") }}</div>
|
||||||
<n-switch v-model:value="searchHistory" :round="false" />
|
<n-switch v-model:value="searchHistory" :round="false" />
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card class="set-item">
|
<n-card class="set-item">
|
||||||
<div class="name">
|
<div class="name">
|
||||||
显示底栏歌词
|
{{ $t("setting.bottomLyricShow") }}
|
||||||
<span class="tip">是否在播放时显示歌词</span>
|
<span class="tip">{{ $t("setting.bottomLyricShowTip") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<n-switch v-model:value="bottomLyricShow" :round="false" />
|
<n-switch v-model:value="bottomLyricShow" :round="false" />
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card class="set-item">
|
<n-card class="set-item">
|
||||||
<div class="name">
|
<div class="name">
|
||||||
歌曲音质
|
{{ $t("setting.songVolumeFade") }}
|
||||||
<span class="tip">无损音质及以上需要您为黑胶会员</span>
|
<span class="tip">{{ $t("setting.songVolumeFadeTip") }}</span>
|
||||||
|
</div>
|
||||||
|
<n-switch v-model:value="songVolumeFade" :round="false" />
|
||||||
|
</n-card>
|
||||||
|
<n-card class="set-item">
|
||||||
|
<div class="name">
|
||||||
|
{{ $t("setting.memoryLastPlaybackPosition") }}
|
||||||
|
<span class="tip">{{
|
||||||
|
$t("setting.memoryLastPlaybackPositionTip")
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<n-switch v-model:value="memoryLastPlaybackPosition" :round="false" />
|
||||||
|
</n-card>
|
||||||
|
<n-card class="set-item">
|
||||||
|
<div class="name">
|
||||||
|
{{ $t("setting.songLevel") }}
|
||||||
|
<span class="tip">{{ $t("setting.songLevelTip") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<n-select
|
<n-select
|
||||||
class="set"
|
class="set"
|
||||||
@@ -52,15 +125,43 @@
|
|||||||
:options="songLevelOptions"
|
:options="songLevelOptions"
|
||||||
/>
|
/>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
<n-card class="set-item">
|
||||||
|
<div class="name">
|
||||||
|
{{ $t("setting.useUnmServerShow") }}
|
||||||
|
<span class="tip">
|
||||||
|
{{
|
||||||
|
useUnmServerShow
|
||||||
|
? $t("setting.useUnmServerShowTip1")
|
||||||
|
: $t("setting.useUnmServerShowTip2")
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<n-switch
|
||||||
|
v-model:value="useUnmServer"
|
||||||
|
:round="false"
|
||||||
|
:disabled="!useUnmServerShow"
|
||||||
|
/>
|
||||||
|
</n-card>
|
||||||
|
<n-card class="set-item">
|
||||||
|
<div class="name">
|
||||||
|
{{ $t("setting.showLyricSetting") }}
|
||||||
|
<span class="tip">{{ $t("setting.showLyricSettingTip") }}</span>
|
||||||
|
</div>
|
||||||
|
<n-switch v-model:value="showLyricSetting" :round="false" />
|
||||||
|
</n-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { settingStore, userStore } from "@/store";
|
import { settingStore, userStore } from "@/store";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useOsTheme } from "naive-ui";
|
||||||
|
import themeColorData from "@/components/Provider/themeColor.json";
|
||||||
|
|
||||||
const setting = settingStore();
|
const setting = settingStore();
|
||||||
const user = userStore();
|
const user = userStore();
|
||||||
|
const osThemeRef = useOsTheme();
|
||||||
const {
|
const {
|
||||||
theme,
|
theme,
|
||||||
themeAuto,
|
themeAuto,
|
||||||
@@ -70,56 +171,201 @@ const {
|
|||||||
bannerShow,
|
bannerShow,
|
||||||
autoSignIn,
|
autoSignIn,
|
||||||
searchHistory,
|
searchHistory,
|
||||||
|
themeType,
|
||||||
|
showLyricSetting,
|
||||||
|
songVolumeFade,
|
||||||
|
useUnmServer,
|
||||||
|
memoryLastPlaybackPosition,
|
||||||
|
language,
|
||||||
} = storeToRefs(setting);
|
} = storeToRefs(setting);
|
||||||
|
|
||||||
|
// 国际化
|
||||||
|
const { locale, t } = useI18n();
|
||||||
|
|
||||||
|
// UNM 开关显示
|
||||||
|
const useUnmServerShow = import.meta.env.VITE_UNM_API ? true : false;
|
||||||
|
|
||||||
// 深浅模式
|
// 深浅模式
|
||||||
const darkOptions = [
|
const themeOptions = ref([]);
|
||||||
|
const themeChange = () => {
|
||||||
|
themeOptions.value = [
|
||||||
{
|
{
|
||||||
label: "浅色模式",
|
label: t("nav.avatar.light"),
|
||||||
value: "light",
|
value: "light",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "深色模式",
|
label: t("nav.avatar.dark"),
|
||||||
value: "dark",
|
value: "dark",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开启自动跟随
|
||||||
|
const themeAutoOpen = (val) => {
|
||||||
|
console.log(osThemeRef.value);
|
||||||
|
if (val) {
|
||||||
|
theme.value = osThemeRef.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 列表模式
|
// 列表模式
|
||||||
const listClickModeOptions = [
|
const listClickModeOptions = ref([]);
|
||||||
|
const listClickModeChange = () => {
|
||||||
|
listClickModeOptions.value = [
|
||||||
{
|
{
|
||||||
label: "双击播放",
|
label: t("setting.dblclick"),
|
||||||
value: "dblclick",
|
value: "dblclick",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "单击播放",
|
label: t("setting.click"),
|
||||||
value: "click",
|
value: "click",
|
||||||
},
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 语言
|
||||||
|
const languageOptions = [
|
||||||
|
{
|
||||||
|
label: "🇨🇳 简体中文",
|
||||||
|
value: "zh-CN",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "🇬🇧 English",
|
||||||
|
value: "en",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 语言切换
|
||||||
|
const changeLanguage = (value, option) => {
|
||||||
|
const html = document.documentElement;
|
||||||
|
locale.value = value;
|
||||||
|
if (html) html.setAttribute("lang", value);
|
||||||
|
changeAllOptions();
|
||||||
|
console.log(t("setting.changeLanguage", { name: value }));
|
||||||
|
$message.success(t("setting.changeLanguage", { name: option.label }));
|
||||||
|
};
|
||||||
|
|
||||||
// 歌曲音质
|
// 歌曲音质
|
||||||
const songLevelOptions = [
|
const songLevelOptions = ref([]);
|
||||||
|
const songLevelChange = () => {
|
||||||
|
songLevelOptions.value = [
|
||||||
{
|
{
|
||||||
label: "标准",
|
label: t("setting.standard"),
|
||||||
value: "standard",
|
value: "standard",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "较高",
|
label: t("setting.higher"),
|
||||||
value: "higher",
|
value: "higher",
|
||||||
},
|
},
|
||||||
,
|
,
|
||||||
{
|
{
|
||||||
label: "极高",
|
label: t("setting.exhigh"),
|
||||||
value: "exhigh",
|
value: "exhigh",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "无损",
|
label: t("setting.lossless"),
|
||||||
value: "lossless",
|
value: "lossless",
|
||||||
disabled: user.userData?.vipType ? false : true,
|
disabled: user.userData?.vipType ? false : true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Hi-Res",
|
label: t("setting.hires"),
|
||||||
value: "hires",
|
value: "hires",
|
||||||
disabled: user.userData?.vipType ? false : true,
|
disabled: user.userData?.vipType ? false : true,
|
||||||
},
|
},
|
||||||
];
|
{
|
||||||
|
label: t("setting.jyeffect"),
|
||||||
|
value: "jyeffect",
|
||||||
|
disabled: user.userData?.vipType ? false : true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("setting.jymaster"),
|
||||||
|
value: "jymaster",
|
||||||
|
disabled: user.userData?.vipType ? false : true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更改所有配置
|
||||||
|
const changeAllOptions = () => {
|
||||||
|
themeChange();
|
||||||
|
listClickModeChange();
|
||||||
|
songLevelChange();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更换主题色
|
||||||
|
const changeThemeColor = (data, reset = false) => {
|
||||||
|
if (reset) {
|
||||||
|
$dialog.warning({
|
||||||
|
class: "s-dialog",
|
||||||
|
title: t("general.name.restore"),
|
||||||
|
content: t("setting.themeTypeDialog"),
|
||||||
|
positiveText: t("general.name.restore"),
|
||||||
|
negativeText: t("general.dialog.cancel"),
|
||||||
|
onPositiveClick: () => {
|
||||||
|
$message.success(t("other.cleanAll"));
|
||||||
|
themeType.value = "red";
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$message.success(t("setting.themeChange", { name: data.name }));
|
||||||
|
themeType.value = data.label;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
changeAllOptions();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.set-item {
|
||||||
|
.top {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.color-selete {
|
||||||
|
margin-top: 16px;
|
||||||
|
.item {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 80px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: var(--color);
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 12px;
|
||||||
|
top: -4px;
|
||||||
|
left: -4px;
|
||||||
|
right: -4px;
|
||||||
|
bottom: -4px;
|
||||||
|
border: 2px solid var(--color);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
&.check {
|
||||||
|
&::before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
.n-text {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -2,29 +2,36 @@
|
|||||||
<div class="set-other">
|
<div class="set-other">
|
||||||
<n-card class="set-item">
|
<n-card class="set-item">
|
||||||
<div class="name">
|
<div class="name">
|
||||||
系统重置
|
{{ $t("setting.resetApp") }}
|
||||||
<span class="tip">若程序显示异常或出现问题时可尝试此操作</span>
|
<span class="tip">{{ $t("setting.resetAppTip") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<n-button strong secondary type="error" @click="resetApp">
|
<n-button strong secondary type="error" @click="resetApp">
|
||||||
重置
|
{{ $t("general.name.restore") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-card>
|
</n-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
// 系统重置
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
// 程序重置
|
||||||
const resetApp = () => {
|
const resetApp = () => {
|
||||||
const cleanAll = () => {
|
const cleanAll = () => {
|
||||||
$message ? $message.success("重置成功") : alert("重置成功");
|
$message
|
||||||
|
? $message.success(t("other.cleanAll"))
|
||||||
|
: alert(t("other.cleanAll"));
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
};
|
};
|
||||||
$dialog.warning({
|
$dialog.warning({
|
||||||
title: "系统重置",
|
class: "s-dialog",
|
||||||
content: "确认重置为默认状态?你的登录状态以及自定义设置都将丢失!",
|
title: t("setting.resetApp"),
|
||||||
positiveText: "重置",
|
content: t("setting.resetAppWarning"),
|
||||||
negativeText: "取消",
|
positiveText: t("setting.resetApp"),
|
||||||
|
negativeText: t("general.dialog.cancel"),
|
||||||
onPositiveClick: () => {
|
onPositiveClick: () => {
|
||||||
$cleanAll ? $cleanAll() : cleanAll();
|
$cleanAll ? $cleanAll() : cleanAll();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="set-player">
|
<div class="set-player">
|
||||||
<n-card class="set-item">
|
<n-card class="set-item">
|
||||||
<div class="name">播放器样式</div>
|
<div class="name">
|
||||||
|
{{ $t("setting.playerStyle") }}
|
||||||
|
<span class="tip">{{ $t("setting.playerStyleTip") }}</span>
|
||||||
|
</div>
|
||||||
<n-select
|
<n-select
|
||||||
class="set"
|
class="set"
|
||||||
v-model:value="playerStyle"
|
v-model:value="playerStyle"
|
||||||
@@ -10,43 +13,82 @@
|
|||||||
</n-card>
|
</n-card>
|
||||||
<n-card class="set-item">
|
<n-card class="set-item">
|
||||||
<div class="name">
|
<div class="name">
|
||||||
替换无法播放的歌曲链接
|
{{ $t("setting.backgroundImageShow") }}
|
||||||
<span class="tip">
|
<span class="tip">{{
|
||||||
{{
|
backgroundImageShow === "blur"
|
||||||
useUnmServerShow
|
? $t("setting.backgroundImageShowTip1")
|
||||||
? "是否使用 UNM 替换无法播放的歌曲链接"
|
: $t("setting.backgroundImageShowTip2")
|
||||||
: "请配置 UNM-Server 后使用解灰功能"
|
}}</span>
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<n-switch
|
<n-select
|
||||||
v-model:value="useUnmServer"
|
class="set"
|
||||||
:round="false"
|
v-model:value="backgroundImageShow"
|
||||||
:disabled="!useUnmServerShow"
|
:options="backgroundImageShowOptions"
|
||||||
/>
|
/>
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card class="set-item">
|
<n-card class="set-item">
|
||||||
<div class="name">显示歌词翻译</div>
|
<div class="name">
|
||||||
|
{{ $t("setting.showTransl") }}
|
||||||
|
<span class="tip">{{ $t("setting.showTranslTip") }}</span>
|
||||||
|
</div>
|
||||||
<n-switch v-model:value="showTransl" :round="false" />
|
<n-switch v-model:value="showTransl" :round="false" />
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card class="set-item">
|
<n-card class="set-item">
|
||||||
<div class="name">
|
<div class="name">
|
||||||
显示逐字歌词
|
{{ $t("setting.showRoma") }}
|
||||||
<span class="tip">是否在歌曲具有逐字歌词时显示,实验性功能</span>
|
<span class="tip">{{ $t("setting.showRomaTip") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<n-switch v-model:value="showYrc" :round="false" />
|
<n-switch v-model:value="showRoma" :round="false" />
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card class="set-item">
|
<n-card class="set-item">
|
||||||
<div class="name">
|
<div class="name">
|
||||||
智能暂停滚动
|
<div class="dev">
|
||||||
<span class="tip">鼠标移入歌词区域是否暂停滚动</span>
|
{{ $t("setting.showYrc") }}
|
||||||
|
<n-tag round :bordered="false" size="small" type="warning">
|
||||||
|
{{ $t("setting.dev") }}
|
||||||
|
<template #icon>
|
||||||
|
<n-icon :component="Code" />
|
||||||
|
</template>
|
||||||
|
</n-tag>
|
||||||
|
</div>
|
||||||
|
<span class="tip">{{ $t("setting.showYrcTip") }}</span>
|
||||||
|
</div>
|
||||||
|
<n-switch v-model:value="showYrc" :round="false" />
|
||||||
|
</n-card>
|
||||||
|
<template v-if="showYrc">
|
||||||
|
<n-card class="set-item">
|
||||||
|
<div class="name">
|
||||||
|
{{ $t("setting.showYrcAnimation") }}
|
||||||
|
<span class="tip">{{ $t("setting.showYrcAnimationTip") }}</span>
|
||||||
|
</div>
|
||||||
|
<n-switch v-model:value="showYrcAnimation" :round="false" />
|
||||||
|
</n-card>
|
||||||
|
<n-card class="set-item">
|
||||||
|
<div class="name">
|
||||||
|
{{ $t("setting.showYrcTransform") }}
|
||||||
|
<span class="tip">{{ $t("setting.showYrcTransformTip") }}</span>
|
||||||
|
</div>
|
||||||
|
<n-switch v-model:value="showYrcTransform" :round="false" />
|
||||||
|
</n-card>
|
||||||
|
</template>
|
||||||
|
<n-card class="set-item">
|
||||||
|
<div class="name">
|
||||||
|
{{ $t("setting.countDownShow") }}
|
||||||
|
<span class="tip">{{ $t("setting.countDownShowTip") }}</span>
|
||||||
|
</div>
|
||||||
|
<n-switch v-model:value="countDownShow" :round="false" />
|
||||||
|
</n-card>
|
||||||
|
<n-card class="set-item">
|
||||||
|
<div class="name">
|
||||||
|
{{ $t("setting.lrcMousePause") }}
|
||||||
|
<span class="tip">{{ $t("setting.lrcMousePauseTip") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<n-switch v-model:value="lrcMousePause" :round="false" />
|
<n-switch v-model:value="lrcMousePause" :round="false" />
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card class="set-item">
|
<n-card class="set-item">
|
||||||
<div class="name">
|
<div class="name">
|
||||||
歌词滚动位置
|
{{ $t("setting.lyricsBlock") }}
|
||||||
<span class="tip">歌词高亮时所处的位置</span>
|
<span class="tip">{{ $t("setting.lyricsBlockTip") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<n-select
|
<n-select
|
||||||
class="set"
|
class="set"
|
||||||
@@ -61,7 +103,7 @@
|
|||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="name">歌词文本大小</div>
|
<div class="name">{{ $t("setting.lyricsFontSize") }}</div>
|
||||||
<n-slider
|
<n-slider
|
||||||
v-model:value="lyricsFontSize"
|
v-model:value="lyricsFontSize"
|
||||||
:tooltip="false"
|
:tooltip="false"
|
||||||
@@ -69,9 +111,9 @@
|
|||||||
:min="3"
|
:min="3"
|
||||||
:step="0.01"
|
:step="0.01"
|
||||||
:marks="{
|
:marks="{
|
||||||
3: '最小',
|
3: t('setting.lyrics1'),
|
||||||
3.6: '默认',
|
3.6: t('setting.lyrics2'),
|
||||||
4: '最大',
|
4: t('setting.lyrics3'),
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<div :class="lyricsBlur ? 'more blur' : 'more'">
|
<div :class="lyricsBlur ? 'more blur' : 'more'">
|
||||||
@@ -96,7 +138,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card class="set-item">
|
<n-card class="set-item">
|
||||||
<div class="name">默认歌词位置</div>
|
<div class="name">{{ $t("setting.lyricsPosition") }}</div>
|
||||||
<n-select
|
<n-select
|
||||||
class="set"
|
class="set"
|
||||||
v-model:value="lyricsPosition"
|
v-model:value="lyricsPosition"
|
||||||
@@ -105,12 +147,12 @@
|
|||||||
</n-card>
|
</n-card>
|
||||||
<n-card class="set-item">
|
<n-card class="set-item">
|
||||||
<div class="name">
|
<div class="name">
|
||||||
歌词模糊
|
{{ $t("setting.lyricsBlur") }}
|
||||||
<span class="tip">未播放或已播放歌词模糊显示,实验性功能</span>
|
<span class="tip">{{ $t("setting.lyricsBlurTip") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<n-switch v-model:value="lyricsBlur" :round="false" />
|
<n-switch v-model:value="lyricsBlur" :round="false" />
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card class="set-item">
|
<!-- <n-card class="set-item">
|
||||||
<div class="name">
|
<div class="name">
|
||||||
显示音乐频谱
|
显示音乐频谱
|
||||||
<span class="tip">可能会导致一些意想不到的后果,实验性功能</span>
|
<span class="tip">可能会导致一些意想不到的后果,实验性功能</span>
|
||||||
@@ -120,13 +162,17 @@
|
|||||||
:round="false"
|
:round="false"
|
||||||
@click="changeMusicFrequency"
|
@click="changeMusicFrequency"
|
||||||
/>
|
/>
|
||||||
</n-card>
|
</n-card> -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { settingStore } from "@/store";
|
import { settingStore } from "@/store";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { Code } from "@icon-park/vue-next";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const setting = settingStore();
|
const setting = settingStore();
|
||||||
const {
|
const {
|
||||||
@@ -139,20 +185,21 @@ const {
|
|||||||
lyricsBlur,
|
lyricsBlur,
|
||||||
lrcMousePause,
|
lrcMousePause,
|
||||||
showYrc,
|
showYrc,
|
||||||
useUnmServer,
|
showRoma,
|
||||||
|
backgroundImageShow,
|
||||||
|
countDownShow,
|
||||||
|
showYrcAnimation,
|
||||||
|
showYrcTransform,
|
||||||
} = storeToRefs(setting);
|
} = storeToRefs(setting);
|
||||||
|
|
||||||
// UNM 开关显示
|
|
||||||
const useUnmServerShow = import.meta.env.VITE_UNM_API ? true : false;
|
|
||||||
|
|
||||||
// 歌词位置
|
// 歌词位置
|
||||||
const lyricsPositionOptions = [
|
const lyricsPositionOptions = [
|
||||||
{
|
{
|
||||||
label: "居左",
|
label: t("setting.positionLeft"),
|
||||||
value: "left",
|
value: "left",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "居中",
|
label: t("setting.positionCenter"),
|
||||||
value: "center",
|
value: "center",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -160,11 +207,11 @@ const lyricsPositionOptions = [
|
|||||||
// 歌词滚动位置
|
// 歌词滚动位置
|
||||||
const lyricsBlockOptions = [
|
const lyricsBlockOptions = [
|
||||||
{
|
{
|
||||||
label: "靠近顶部",
|
label: t("setting.blockStart"),
|
||||||
value: "start",
|
value: "start",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "水平居中",
|
label: t("setting.blockCenter"),
|
||||||
value: "center",
|
value: "center",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -172,35 +219,46 @@ const lyricsBlockOptions = [
|
|||||||
// 播放器样式
|
// 播放器样式
|
||||||
const playerStyleOptions = [
|
const playerStyleOptions = [
|
||||||
{
|
{
|
||||||
label: "封面模式",
|
label: t("setting.cover"),
|
||||||
value: "cover",
|
value: "cover",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "唱片模式",
|
label: t("setting.record"),
|
||||||
value: "record",
|
value: "record",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 播放背景类型
|
||||||
|
const backgroundImageShowOptions = [
|
||||||
|
{
|
||||||
|
label: t("setting.solid"),
|
||||||
|
value: "solid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("setting.blur"),
|
||||||
|
value: "blur",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// 音乐频谱提醒
|
// 音乐频谱提醒
|
||||||
const changeMusicFrequency = () => {
|
// const changeMusicFrequency = () => {
|
||||||
if (musicFrequency.value) {
|
// if (musicFrequency.value) {
|
||||||
$dialog.warning({
|
// $dialog.warning({
|
||||||
class: "s-dialog",
|
// class: "s-dialog",
|
||||||
title: "实验性功能",
|
// title: "实验性功能",
|
||||||
content: "确认开启音乐频谱?将于刷新后生效",
|
// content: "确认开启音乐频谱?将在重启应用后生效",
|
||||||
positiveText: "开启",
|
// positiveText: "开启",
|
||||||
negativeText: "取消",
|
// negativeText: "取消",
|
||||||
onMaskClick: () => {
|
// onMaskClick: () => {
|
||||||
musicFrequency.value = false;
|
// musicFrequency.value = false;
|
||||||
},
|
// },
|
||||||
onPositiveClick: () => {
|
// onPositiveClick: () => {
|
||||||
musicFrequency.value = true;
|
// musicFrequency.value = true;
|
||||||
location.reload();
|
// },
|
||||||
},
|
// onNegativeClick: () => {
|
||||||
onNegativeClick: () => {
|
// musicFrequency.value = false;
|
||||||
musicFrequency.value = false;
|
// },
|
||||||
},
|
// });
|
||||||
});
|
// }
|
||||||
}
|
// };
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,14 +2,24 @@
|
|||||||
<div class="song" v-if="musicDetail">
|
<div class="song" v-if="musicDetail">
|
||||||
<div class="detail">
|
<div class="detail">
|
||||||
<div class="pic">
|
<div class="pic">
|
||||||
<n-avatar
|
<n-image
|
||||||
|
show-toolbar-tooltip
|
||||||
class="coverImg"
|
class="coverImg"
|
||||||
:src="
|
:previewed-img-props="{ style: { borderRadius: '8px' } }"
|
||||||
musicDetail.al.picUrl
|
:preview-src="getCoverUrl(musicDetail?.al.picUrl)"
|
||||||
? musicDetail.al.picUrl.replace(/^http:/, 'https:') +
|
:src="getCoverUrl(musicDetail?.al.picUrl, 1024)"
|
||||||
'?param=1024y1024'
|
fallback-src="/images/pic/default.png"
|
||||||
: '/images/pic/default.png'
|
>
|
||||||
"
|
<template #placeholder>
|
||||||
|
<div class="cover-loading">
|
||||||
|
<n-spin />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-image>
|
||||||
|
<n-image
|
||||||
|
class="shadow"
|
||||||
|
preview-disabled
|
||||||
|
:src="getCoverUrl(musicDetail?.al.picUrl, 1024)"
|
||||||
fallback-src="/images/pic/default.png"
|
fallback-src="/images/pic/default.png"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -22,20 +32,43 @@
|
|||||||
v-if="musicDetail.alia[0]"
|
v-if="musicDetail.alia[0]"
|
||||||
v-html="musicDetail.alia[0]"
|
v-html="musicDetail.alia[0]"
|
||||||
/>
|
/>
|
||||||
<div class="all-artist">
|
<n-space class="tag">
|
||||||
<n-text class="tip" depth="3">歌手:</n-text>
|
<n-tag
|
||||||
<AllArtists v-if="musicDetail.ar" :artistsData="musicDetail.ar" />
|
v-if="musicDetail.fee == 1 || musicDetail.fee == 4"
|
||||||
|
class="vip"
|
||||||
|
round
|
||||||
|
:bordered="false"
|
||||||
|
>
|
||||||
|
{{ musicDetail.fee == 1 ? "VIP" : "EP" }}
|
||||||
|
</n-tag>
|
||||||
|
<n-tag
|
||||||
|
v-if="musicDetail.pc"
|
||||||
|
class="cloud"
|
||||||
|
round
|
||||||
|
type="info"
|
||||||
|
:bordered="false"
|
||||||
|
>
|
||||||
|
{{ $t("general.name.cloud") }}
|
||||||
|
</n-tag>
|
||||||
|
</n-space>
|
||||||
|
<div class="item">
|
||||||
|
<n-icon :depth="3" :component="People" />
|
||||||
|
<AllArtists
|
||||||
|
v-if="musicDetail.ar"
|
||||||
|
:artistsData="musicDetail.ar"
|
||||||
|
:isDark="false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="album">
|
<div class="item">
|
||||||
<n-text class="tip" depth="3">专辑:</n-text>
|
<n-icon :depth="3" :component="RecordDisc" />
|
||||||
<n-text
|
<n-text
|
||||||
class="text"
|
class="text"
|
||||||
v-html="musicDetail.al.name"
|
v-html="musicDetail.al.name"
|
||||||
@click="router.push(`/album?id=${musicDetail.al.id}`)"
|
@click="router.push(`/album?id=${musicDetail.al.id}`)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="time" v-if="musicDetail.publishTime">
|
<div class="item" v-if="musicDetail.publishTime">
|
||||||
<n-text class="tip" depth="3">发行日期:</n-text>
|
<n-icon :depth="3" :component="Time" />
|
||||||
<n-text
|
<n-text
|
||||||
class="text"
|
class="text"
|
||||||
v-html="getLongTime(musicDetail.publishTime)"
|
v-html="getLongTime(musicDetail.publishTime)"
|
||||||
@@ -50,9 +83,9 @@
|
|||||||
@click="addSong(musicDetail)"
|
@click="addSong(musicDetail)"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon :component="PlayArrowRound" />
|
<n-icon :component="PlayOne" />
|
||||||
</template>
|
</template>
|
||||||
播放
|
{{ $t("general.name.play") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button
|
<n-button
|
||||||
strong
|
strong
|
||||||
@@ -60,9 +93,9 @@
|
|||||||
@click="addPlayListRef.openAddToPlaylist(musicId)"
|
@click="addPlayListRef.openAddToPlaylist(musicId)"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon :component="PlaylistAddRound" />
|
<n-icon :component="ListAdd" />
|
||||||
</template>
|
</template>
|
||||||
添加
|
{{ $t("general.name.add") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button
|
<n-button
|
||||||
strong
|
strong
|
||||||
@@ -70,9 +103,9 @@
|
|||||||
@click="router.push(`/comment?id=${musicDetail.id}&page=1`)"
|
@click="router.push(`/comment?id=${musicDetail.id}&page=1`)"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon :component="MessageFilled" />
|
<n-icon :component="Comments" />
|
||||||
</template>
|
</template>
|
||||||
评论
|
{{ $t("general.name.comment") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button
|
<n-button
|
||||||
strong
|
strong
|
||||||
@@ -81,16 +114,22 @@
|
|||||||
@click="router.push(`/video?id=${musicDetail.mv}`)"
|
@click="router.push(`/video?id=${musicDetail.mv}`)"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon :component="VideocamRound" />
|
<n-icon :component="Youtube" />
|
||||||
</template>
|
</template>
|
||||||
MV
|
MV
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<n-divider />
|
<div class="comments" v-if="commentData[0]">
|
||||||
|
<n-h6 prefix="bar"> {{ $t("general.name.hotComments") }} </n-h6>
|
||||||
|
<div class="content">
|
||||||
|
<Comment v-for="item in commentData" :key="item" :commentData="item" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="simiPlayList" v-if="simiPlayList[0]">
|
<div class="simiPlayList" v-if="simiPlayList[0]">
|
||||||
<n-h6 prefix="bar"> 包含这首歌的歌单 </n-h6>
|
<n-divider />
|
||||||
|
<n-h6 prefix="bar"> {{ $t("other.containing") }} </n-h6>
|
||||||
<CoverLists :listData="simiPlayList" />
|
<CoverLists :listData="simiPlayList" />
|
||||||
</div>
|
</div>
|
||||||
<!-- 添加到歌单 -->
|
<!-- 添加到歌单 -->
|
||||||
@@ -100,20 +139,28 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { getSimiPlayList, getMusicDetail } from "@/api/song";
|
import { getSimiPlayList, getMusicDetail } from "@/api/song";
|
||||||
|
import { getComment } from "@/api/comment";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { musicStore } from "@/store";
|
import { musicStore } from "@/store";
|
||||||
import { getLongTime } from "@/utils/timeTools.js";
|
import { getLongTime } from "@/utils/timeTools";
|
||||||
import {
|
import {
|
||||||
PlayArrowRound,
|
PlayOne,
|
||||||
MessageFilled,
|
Comments,
|
||||||
VideocamRound,
|
ListAdd,
|
||||||
PlaylistAddRound,
|
Youtube,
|
||||||
} from "@vicons/material";
|
People,
|
||||||
import { formatNumber } from "@/utils/timeTools.js";
|
RecordDisc,
|
||||||
|
Time,
|
||||||
|
} from "@icon-park/vue-next";
|
||||||
|
import { formatNumber } from "@/utils/timeTools";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import AllArtists from "@/components/DataList/AllArtists.vue";
|
import AllArtists from "@/components/DataList/AllArtists.vue";
|
||||||
import CoverLists from "@/components/DataList/CoverLists.vue";
|
import CoverLists from "@/components/DataList/CoverLists.vue";
|
||||||
import AddPlaylist from "@/components/DataModel/AddPlaylist.vue";
|
import AddPlaylist from "@/components/DataModal/AddPlaylist.vue";
|
||||||
|
import Comment from "@/components/Comment/index.vue";
|
||||||
|
import getCoverUrl from "@/utils/getCoverUrl";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const music = musicStore();
|
const music = musicStore();
|
||||||
const addPlayListRef = ref(null);
|
const addPlayListRef = ref(null);
|
||||||
@@ -122,6 +169,9 @@ const addPlayListRef = ref(null);
|
|||||||
const musicId = ref(router.currentRoute.value.query.id);
|
const musicId = ref(router.currentRoute.value.query.id);
|
||||||
const musicDetail = ref(null);
|
const musicDetail = ref(null);
|
||||||
|
|
||||||
|
// 评论数据
|
||||||
|
const commentData = ref([]);
|
||||||
|
|
||||||
// 相似数据
|
// 相似数据
|
||||||
const simiPlayList = ref([]);
|
const simiPlayList = ref([]);
|
||||||
|
|
||||||
@@ -132,16 +182,39 @@ const getMusicDetailData = (id) => {
|
|||||||
if (res.songs[0]) {
|
if (res.songs[0]) {
|
||||||
musicDetail.value = res.songs[0];
|
musicDetail.value = res.songs[0];
|
||||||
$setSiteTitle(
|
$setSiteTitle(
|
||||||
res.songs[0].name + " - " + res.songs[0].ar[0].name + " - 单曲"
|
res.songs[0].name +
|
||||||
|
" - " +
|
||||||
|
res.songs[0].ar[0].name +
|
||||||
|
" - " +
|
||||||
|
t("general.name.song")
|
||||||
);
|
);
|
||||||
|
// 获取热门评论
|
||||||
|
getCommentData(id);
|
||||||
// 获取相似数据
|
// 获取相似数据
|
||||||
getSimiData(id);
|
getSimiData(id);
|
||||||
|
// 请求后回顶
|
||||||
|
if (typeof $scrollToTop !== "undefined") $scrollToTop();
|
||||||
} else {
|
} else {
|
||||||
$message.error("歌曲信息获取失败");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取评论数据
|
||||||
|
const getCommentData = (id) => {
|
||||||
|
getComment(id)
|
||||||
|
.then((res) => {
|
||||||
|
// 写入数据
|
||||||
|
if (res.total > 0) {
|
||||||
|
commentData.value = res.hotComments;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(t("general.message.acquisitionFailed"), err);
|
||||||
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// 获取相似数据
|
// 获取相似数据
|
||||||
const getSimiData = (id) => {
|
const getSimiData = (id) => {
|
||||||
getSimiPlayList(id).then((res) => {
|
getSimiPlayList(id).then((res) => {
|
||||||
@@ -205,11 +278,53 @@ watch(
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
max-width: 280px;
|
max-width: 280px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
|
||||||
margin-right: 40px;
|
margin-right: 40px;
|
||||||
.n-avatar {
|
position: relative;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
.coverImg {
|
||||||
|
border-radius: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: inherit;
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1;
|
||||||
|
:deep(img) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.cover-loading {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
padding-bottom: 100%;
|
||||||
|
background-color: #0001;
|
||||||
|
.n-spin-body {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.shadow {
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
filter: blur(16px) opacity(0.6);
|
||||||
|
transform: scale(0.92, 0.96);
|
||||||
|
z-index: 0;
|
||||||
|
background-size: cover;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
:deep(img) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.right {
|
.right {
|
||||||
@@ -228,19 +343,22 @@ watch(
|
|||||||
.alia {
|
.alia {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
.all-artist {
|
.tag {
|
||||||
margin-top: 12px;
|
margin: 12px 0;
|
||||||
display: flex;
|
}
|
||||||
flex-direction: row;
|
.item {
|
||||||
align-items: center;
|
font-size: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
.n-icon {
|
||||||
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
.album {
|
|
||||||
margin: 4px 0;
|
|
||||||
.text {
|
.text {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $mainColor;
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -252,6 +370,7 @@ watch(
|
|||||||
}
|
}
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
.pic {
|
.pic {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
@@ -267,5 +386,8 @@ watch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.comments {
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
<n-result
|
<n-result
|
||||||
class="error"
|
class="error"
|
||||||
status="403"
|
status="403"
|
||||||
title="禁止访问"
|
:title="$t('state.prohibition')"
|
||||||
description="总有些门是对你关闭的"
|
:description="$t('state.prohibitionDesc')"
|
||||||
>
|
>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<n-button type="primary" @click="router.go(-1)">回到上一页</n-button>
|
<n-button type="primary" @click="router.go(-1)">{{
|
||||||
|
$t("general.name.goBack")
|
||||||
|
}}</n-button>
|
||||||
</template>
|
</template>
|
||||||
</n-result>
|
</n-result>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
<n-result
|
<n-result
|
||||||
class="error"
|
class="error"
|
||||||
status="404"
|
status="404"
|
||||||
title="资源不存在"
|
:title="$t('state.notFound')"
|
||||||
description="怎么跑到这来了"
|
:description="$t('state.notFoundDesc')"
|
||||||
>
|
>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<n-button type="primary" @click="router.go(-1)">回到上一页</n-button>
|
<n-button type="primary" @click="router.go(-1)">{{
|
||||||
|
$t("general.name.goBack")
|
||||||
|
}}</n-button>
|
||||||
</template>
|
</template>
|
||||||
</n-result>
|
</n-result>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
<n-result
|
<n-result
|
||||||
class="error"
|
class="error"
|
||||||
status="500"
|
status="500"
|
||||||
title="服务器错误"
|
:title="$t('state.error')"
|
||||||
description="服务器寄了,等会再试吧"
|
:description="$t('state.errorDesc')"
|
||||||
>
|
>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<n-button type="primary" @click="router.push('/')">重新载入</n-button>
|
<n-button type="primary" @click="router.push('/')">{{
|
||||||
|
$t("general.name.reload")
|
||||||
|
}}</n-button>
|
||||||
</template>
|
</template>
|
||||||
</n-result>
|
</n-result>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -6,12 +6,14 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { userStore } from "@/store";
|
import { userStore } from "@/store";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import CoverLists from "@/components/DataList/CoverLists.vue";
|
import CoverLists from "@/components/DataList/CoverLists.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const user = userStore();
|
const user = userStore();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
$setSiteTitle("音乐库 - 收藏的专辑");
|
$setSiteTitle(t("nav.user") + " - " + t("nav.userChildren.album"));
|
||||||
if (!user.getUserAlbumLists.has && !user.getUserAlbumLists.isLoading)
|
if (!user.getUserAlbumLists.has && !user.getUserAlbumLists.isLoading)
|
||||||
user.setUserAlbumLists();
|
user.setUserAlbumLists();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,12 +6,14 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { userStore } from "@/store";
|
import { userStore } from "@/store";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import ArtistLists from "@/components/DataList/ArtistLists.vue";
|
import ArtistLists from "@/components/DataList/ArtistLists.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const user = userStore();
|
const user = userStore();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
$setSiteTitle("音乐库 - 收藏的歌手");
|
$setSiteTitle(t("nav.user") + " - " + t("nav.userChildren.artist"));
|
||||||
if (!user.getUserArtistLists.has && !user.getUserArtistLists.isLoading)
|
if (!user.getUserArtistLists.has && !user.getUserArtistLists.isLoading)
|
||||||
user.setUserArtistLists();
|
user.setUserArtistLists();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon :component="BackupRound" />
|
<n-icon :component="BackupRound" />
|
||||||
</template>
|
</template>
|
||||||
上传歌曲
|
{{ $t("general.name.upCloud") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
<input
|
<input
|
||||||
ref="upSongRef"
|
ref="upSongRef"
|
||||||
@@ -27,18 +27,21 @@
|
|||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-progress
|
<n-progress
|
||||||
type="line"
|
type="line"
|
||||||
color="#f55e55"
|
:color="setting.themeData.primaryColor"
|
||||||
class="progress"
|
class="progress"
|
||||||
:show-indicator="false"
|
:show-indicator="false"
|
||||||
:percentage="100 / (cloudSpace[1] / cloudSpace[0])"
|
:percentage="100 / (cloudSpace[1] / cloudSpace[0])"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<n-text>
|
<n-text>
|
||||||
已用 {{ (100 / (cloudSpace[1] / cloudSpace[0])).toFixed() }}%,剩余
|
{{
|
||||||
{{ cloudSpace[1] - cloudSpace[0] }} G
|
$t("general.name.cloudUsed", {
|
||||||
|
used: (100 / (cloudSpace[1] / cloudSpace[0])).toFixed(),
|
||||||
|
remaining: cloudSpace[1] - cloudSpace[0],
|
||||||
|
})
|
||||||
|
}}
|
||||||
</n-text>
|
</n-text>
|
||||||
</n-popover>
|
</n-popover>
|
||||||
|
|
||||||
<span>{{ cloudSpace[1] }} G</span>
|
<span>{{ cloudSpace[1] }} G</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,7 +57,7 @@
|
|||||||
class="s-modal close"
|
class="s-modal close"
|
||||||
v-model:show="upSongModal"
|
v-model:show="upSongModal"
|
||||||
preset="card"
|
preset="card"
|
||||||
title="云盘上传"
|
:title="$t('general.name.upCloud')"
|
||||||
:auto-focus="false"
|
:auto-focus="false"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
:close-on-esc="false"
|
:close-on-esc="false"
|
||||||
@@ -70,9 +73,11 @@
|
|||||||
/>
|
/>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<n-space justify="end" v-if="upSongType === 'error'">
|
<n-space justify="end" v-if="upSongType === 'error'">
|
||||||
<n-button @click="closeUpSongModal"> 取消 </n-button>
|
<n-button @click="closeUpSongModal">
|
||||||
|
{{ $t("general.dialog.cancel") }}
|
||||||
|
</n-button>
|
||||||
<n-button type="primary" @click="resetUpSongModal">
|
<n-button type="primary" @click="resetUpSongModal">
|
||||||
重新上传
|
{{ $t("general.dialog.resetUp") }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
@@ -83,12 +88,16 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { getCloud, upCloudSong } from "@/api/user";
|
import { getCloud, upCloudSong } from "@/api/user";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { getSongTime } from "@/utils/timeTools.js";
|
import { settingStore } from "@/store";
|
||||||
|
import { getSongTime } from "@/utils/timeTools";
|
||||||
import { BackupRound } from "@vicons/material";
|
import { BackupRound } from "@vicons/material";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import DataLists from "@/components/DataList/DataLists.vue";
|
import DataLists from "@/components/DataList/DataLists.vue";
|
||||||
import Pagination from "@/components/Pagination/index.vue";
|
import Pagination from "@/components/Pagination/index.vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const setting = settingStore();
|
||||||
|
|
||||||
// 云盘数据
|
// 云盘数据
|
||||||
const cloudSpace = ref([]);
|
const cloudSpace = ref([]);
|
||||||
@@ -133,11 +142,10 @@ const getCloudData = (limit = 30, offset = 0, scroll = true) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$message.error("搜索内容为空");
|
$message.error(t("general.message.acquisitionFailed"));
|
||||||
}
|
}
|
||||||
// 请求后回顶
|
// 请求后回顶
|
||||||
if ($mainContent && scroll)
|
if (typeof $scrollToTop !== "undefined") $scrollToTop();
|
||||||
$mainContent.scrollIntoView({ behavior: "smooth" });
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -146,7 +154,6 @@ const onUploadProgress = (progressEvent) => {
|
|||||||
const { loaded, total } = progressEvent;
|
const { loaded, total } = progressEvent;
|
||||||
const percentCompleted = Math.round((loaded * 100) / total);
|
const percentCompleted = Math.round((loaded * 100) / total);
|
||||||
upSongCompleted.value = Number(percentCompleted);
|
upSongCompleted.value = Number(percentCompleted);
|
||||||
console.log(`上传 ${percentCompleted}% 完成`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 歌曲上传
|
// 歌曲上传
|
||||||
@@ -162,21 +169,25 @@ const upCloudSongData = (e) => {
|
|||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
closeUpSongModal();
|
closeUpSongModal();
|
||||||
if (!res.privateCloud.simpleSong.al?.name) {
|
if (!res.privateCloud.simpleSong.al?.name) {
|
||||||
$message.warning("上传歌曲详细信息获取失败,可尝试歌曲纠正");
|
$message.warning(t("general.message.upCloudNotHas"));
|
||||||
}
|
}
|
||||||
$message.success(res.privateCloud.simpleSong?.name + " 上传成功");
|
$message.success(
|
||||||
|
t("general.message.upCloudSuccess", {
|
||||||
|
name: res.privateCloud.simpleSong?.name,
|
||||||
|
})
|
||||||
|
);
|
||||||
getCloudData(pagelimit.value, (pageNumber.value - 1) * pagelimit.value);
|
getCloudData(pagelimit.value, (pageNumber.value - 1) * pagelimit.value);
|
||||||
} else {
|
} else {
|
||||||
upSongType.value = "error";
|
upSongType.value = "error";
|
||||||
$message.error("歌曲上传出错,请重试");
|
$message.error(t("general.message.upCloudError"));
|
||||||
console.error("歌曲上传出错,请重试");
|
console.error(t("general.message.upCloudError"));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
upSongType.value = "error";
|
upSongType.value = "error";
|
||||||
closeUpSongModal();
|
closeUpSongModal();
|
||||||
$message.error("歌曲上传出现错误");
|
$message.error(t("general.message.upCloudFailure"));
|
||||||
console.error("歌曲上传出现错误:" + err);
|
console.error(t("general.message.upCloudFailure"), err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -232,7 +243,7 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
$setSiteTitle("音乐库 - 音乐云盘");
|
$setSiteTitle(t("nav.user") + " - " + t("nav.userChildren.cloud"));
|
||||||
getCloudData(pagelimit.value, (pageNumber.value - 1) * pagelimit.value);
|
getCloudData(pagelimit.value, (pageNumber.value - 1) * pagelimit.value);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user