mirror of
https://github.com/imsyy/SPlayer.git
synced 2025-11-25 11:29:26 +08:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2370d237a8 | ||
|
|
ffbe6229f9 | ||
|
|
bf1312889d | ||
|
|
2bf3d7db5a | ||
|
|
06ccb969e4 | ||
|
|
6867897e02 | ||
|
|
c78f94ae86 | ||
|
|
40ed0dada1 | ||
|
|
f4d2c5f337 | ||
|
|
aace9e97b0 | ||
|
|
b3641801df | ||
|
|
1dd877832c | ||
|
|
660cd33387 | ||
|
|
8caebf65f9 | ||
|
|
3b432dbd8b | ||
|
|
ee934c89f4 | ||
|
|
921b0eed0a |
13
.env
13
.env
@@ -2,10 +2,21 @@
|
||||
## 需部署 API,详见 https://github.com/Binaryify/NeteaseCloudMusicApi
|
||||
VITE_MUSIC_API = "https://api-music.imsyy.top/"
|
||||
|
||||
# 网易云解灰 API 地址
|
||||
# 网易云解灰 API 地址(可选功能)
|
||||
## 需部署 API,详见 https://github.com/imsyy/UNM-Server#%E8%BF%90%E8%A1%8C
|
||||
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 备案号
|
||||
## 若不需要,请设为空即可
|
||||
VITE_ICP = "豫ICP备2022018134号-1"
|
||||
|
||||
15
README.md
15
README.md
@@ -6,8 +6,12 @@
|
||||
</div>
|
||||
<br />
|
||||
|
||||
> 本项目采用 Vue 3 全家桶及 SCSS 开发
|
||||
> 目前主要以 PC 端为主,移动端做了基础适配,仅保证功能
|
||||
## 说明
|
||||
|
||||
- 本项目采用 [Vue 3](https://cn.vuejs.org/) 全家桶和 [Naïve UI](https://www.naiveui.com/) 组件库及 `SCSS` 开发
|
||||
- 目前主要以 `Web` 端为主,可能暂时不会考虑使用 `Electron` 构建客户端
|
||||
- 仅对移动端做了基础适配,**不保证功能全部可用**
|
||||
- 欢迎各位大佬指点和 `Star` 哦 😍
|
||||
|
||||
## 👀 Demo
|
||||
|
||||
@@ -16,7 +20,7 @@
|
||||
## 🎉 功能
|
||||
|
||||
- 支持扫码登录
|
||||
- 支持手机号登录(目前暂时无法使用)
|
||||
- 支持手机号登录(上游接口暂时无法使用)
|
||||
- 自动进行每日签到及云贝签到
|
||||
- 支持 [UnblockNeteaseMusic](https://github.com/UnblockNeteaseMusic/server),自动替换变灰歌曲
|
||||
- 由于酷我音源不支持 `https`,故网页端替换可能不全面
|
||||
@@ -32,7 +36,7 @@
|
||||
- 支持逐字歌词
|
||||
- 歌词滚动以及歌词翻译
|
||||
- MV 与视频播放
|
||||
- 音乐频谱显示( 实验性功能,需在设置中开启 )
|
||||
- 音乐频谱显示( 暂时去除,还待完善 )
|
||||
- 音乐渐入渐出
|
||||
- 支持 PWA
|
||||
- 支持评论区及评论点赞
|
||||
@@ -41,7 +45,10 @@
|
||||
|
||||
#### 待办
|
||||
|
||||
- [ ] 电台节目支持
|
||||
- [ ] 发表评论
|
||||
- [ ] `i18n` 支持
|
||||
- [ ] 重构(写成屎山了) 🤣
|
||||
|
||||
## 😍 Screenshots
|
||||
|
||||
|
||||
20
index.html
20
index.html
@@ -3,24 +3,34 @@
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/images/logo/favicon.svg">
|
||||
<link rel="icon" href="<%- logo %>">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests" /> -->
|
||||
<title>SPlayer</title>
|
||||
<meta name="keywords" content="SPlayer,云音乐,播放器,在线音乐,在线播放器,音乐播放器,在线音乐播放器" />
|
||||
<meta name="description" content="一个简约的在线音乐播放器,具有音乐搜索、播放、每日推荐、私人FM、歌词显示、歌曲评论、网易云登录与云盘等功能" />
|
||||
<title><%- title %></title>
|
||||
<meta name="author" content="<%- author %>" />
|
||||
<meta name="keywords" content="<%- keywords %>" />
|
||||
<meta name="description" content="<%- description %>" />
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<!-- HarmonyOS Sans -->
|
||||
<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>
|
||||
var _hmt = _hmt || [];
|
||||
(function () {
|
||||
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];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
</script>
|
||||
<% } %>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "splayer",
|
||||
"version": "1.1.5",
|
||||
"version": "1.1.6",
|
||||
"author": "imsyy",
|
||||
"home": "https://imsyy.top",
|
||||
"github": "https://github.com/imsyy/SPlayer",
|
||||
@@ -15,14 +15,18 @@
|
||||
"artplayer": "^4.5.12",
|
||||
"axios": "^1.2.0",
|
||||
"colorthief": "^2.4.0",
|
||||
"howler": "^2.2.3",
|
||||
"pinia": "^2.0.26",
|
||||
"pinia-plugin-persistedstate": "^3.0.1",
|
||||
"plyr": "^3.7.3",
|
||||
"qrcode.vue": "^3.3.3",
|
||||
"sass": "^1.56.1",
|
||||
"screenfull": "^6.0.2",
|
||||
"throttle-debounce": "^5.0.0",
|
||||
"vite-plugin-html": "^3.2.0",
|
||||
"vue": "^3.2.45",
|
||||
"vue-router": "^4.1.6"
|
||||
"vue-router": "^4.1.6",
|
||||
"vue-slider-component": "4.1.0-beta.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14",
|
||||
|
||||
304
pnpm-lock.yaml
generated
304
pnpm-lock.yaml
generated
@@ -16,6 +16,9 @@ dependencies:
|
||||
colorthief:
|
||||
specifier: ^2.4.0
|
||||
version: 2.4.0
|
||||
howler:
|
||||
specifier: ^2.2.3
|
||||
version: 2.2.3
|
||||
pinia:
|
||||
specifier: ^2.0.26
|
||||
version: 2.0.27(vue@3.2.45)
|
||||
@@ -34,12 +37,21 @@ dependencies:
|
||||
screenfull:
|
||||
specifier: ^6.0.2
|
||||
version: 6.0.2
|
||||
throttle-debounce:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0
|
||||
vite-plugin-html:
|
||||
specifier: ^3.2.0
|
||||
version: 3.2.0(vite@3.2.4)
|
||||
vue:
|
||||
specifier: ^3.2.45
|
||||
version: 3.2.45
|
||||
vue-router:
|
||||
specifier: ^4.1.6
|
||||
version: 4.1.6(vue@3.2.45)
|
||||
vue-slider-component:
|
||||
specifier: 4.1.0-beta.7
|
||||
version: 4.1.0-beta.7
|
||||
|
||||
devDependencies:
|
||||
'@jridgewell/sourcemap-codec':
|
||||
@@ -1259,7 +1271,6 @@ packages:
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-loong64@0.15.16:
|
||||
@@ -1268,7 +1279,6 @@ packages:
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@icon-park/vue-next@1.4.2(vue@3.2.45):
|
||||
@@ -1299,35 +1309,29 @@ packages:
|
||||
'@jridgewell/set-array': 1.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.4.14
|
||||
'@jridgewell/trace-mapping': 0.3.17
|
||||
dev: true
|
||||
|
||||
/@jridgewell/resolve-uri@3.1.0:
|
||||
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
dev: true
|
||||
|
||||
/@jridgewell/set-array@1.1.2:
|
||||
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
dev: true
|
||||
|
||||
/@jridgewell/source-map@0.3.2:
|
||||
resolution: {integrity: sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==}
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.2
|
||||
'@jridgewell/trace-mapping': 0.3.17
|
||||
dev: true
|
||||
|
||||
/@jridgewell/sourcemap-codec@1.4.14:
|
||||
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
|
||||
dev: true
|
||||
|
||||
/@jridgewell/trace-mapping@0.3.17:
|
||||
resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==}
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.0
|
||||
'@jridgewell/sourcemap-codec': 1.4.14
|
||||
dev: true
|
||||
|
||||
/@juggle/resize-observer@3.4.0:
|
||||
resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
|
||||
@@ -1343,12 +1347,10 @@ packages:
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
run-parallel: 1.2.0
|
||||
dev: true
|
||||
|
||||
/@nodelib/fs.stat@2.0.5:
|
||||
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
|
||||
engines: {node: '>= 8'}
|
||||
dev: true
|
||||
|
||||
/@nodelib/fs.walk@1.2.8:
|
||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||
@@ -1356,7 +1358,6 @@ packages:
|
||||
dependencies:
|
||||
'@nodelib/fs.scandir': 2.1.5
|
||||
fastq: 1.13.0
|
||||
dev: true
|
||||
|
||||
/@rollup/plugin-babel@5.3.1(@babel/core@7.20.12)(rollup@2.79.1):
|
||||
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
|
||||
@@ -1441,6 +1442,14 @@ packages:
|
||||
rollup: 2.79.1
|
||||
dev: true
|
||||
|
||||
/@rollup/pluginutils@4.2.1:
|
||||
resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==}
|
||||
engines: {node: '>= 8.0.0'}
|
||||
dependencies:
|
||||
estree-walker: 2.0.2
|
||||
picomatch: 2.3.1
|
||||
dev: false
|
||||
|
||||
/@rollup/pluginutils@5.0.2(rollup@2.79.1):
|
||||
resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -1612,7 +1621,6 @@ packages:
|
||||
resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/ajv@6.12.6:
|
||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||
@@ -1644,7 +1652,6 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
dev: true
|
||||
|
||||
/anymatch@3.1.3:
|
||||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
||||
@@ -1677,7 +1684,6 @@ packages:
|
||||
|
||||
/async@3.2.4:
|
||||
resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==}
|
||||
dev: true
|
||||
|
||||
/asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
@@ -1749,7 +1755,6 @@ packages:
|
||||
|
||||
/balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
dev: true
|
||||
|
||||
/bcrypt-pbkdf@1.0.2:
|
||||
resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==}
|
||||
@@ -1761,18 +1766,20 @@ packages:
|
||||
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
/boolbase@1.0.0:
|
||||
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
||||
dev: false
|
||||
|
||||
/brace-expansion@1.1.11:
|
||||
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
concat-map: 0.0.1
|
||||
dev: true
|
||||
|
||||
/brace-expansion@2.0.1:
|
||||
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
dev: true
|
||||
|
||||
/braces@3.0.2:
|
||||
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
|
||||
@@ -1793,7 +1800,6 @@ packages:
|
||||
|
||||
/buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
dev: true
|
||||
|
||||
/builtin-modules@3.3.0:
|
||||
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
|
||||
@@ -1807,6 +1813,13 @@ packages:
|
||||
get-intrinsic: 1.1.3
|
||||
dev: true
|
||||
|
||||
/camel-case@4.1.2:
|
||||
resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
|
||||
dependencies:
|
||||
pascal-case: 3.1.2
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/caniuse-lite@1.0.30001445:
|
||||
resolution: {integrity: sha512-8sdQIdMztYmzfTMO6KfLny878Ln9c2M0fc7EH60IjlP4Dc4PiCy7K2Vl3ITmWgOyPgVQKa5x+UP/KqFsxj4mBg==}
|
||||
dev: true
|
||||
@@ -1830,7 +1843,6 @@ packages:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
supports-color: 7.2.0
|
||||
dev: true
|
||||
|
||||
/chokidar@3.5.3:
|
||||
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
|
||||
@@ -1846,6 +1858,13 @@ packages:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
/clean-css@5.3.2:
|
||||
resolution: {integrity: sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==}
|
||||
engines: {node: '>= 10.0'}
|
||||
dependencies:
|
||||
source-map: 0.6.1
|
||||
dev: false
|
||||
|
||||
/color-convert@1.9.3:
|
||||
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
||||
dependencies:
|
||||
@@ -1857,7 +1876,6 @@ packages:
|
||||
engines: {node: '>=7.0.0'}
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
dev: true
|
||||
|
||||
/color-name@1.1.3:
|
||||
resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
|
||||
@@ -1865,7 +1883,10 @@ packages:
|
||||
|
||||
/color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
dev: true
|
||||
|
||||
/colorette@2.0.20:
|
||||
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
|
||||
dev: false
|
||||
|
||||
/colorthief@2.4.0:
|
||||
resolution: {integrity: sha512-0U48RGNRo5fVO+yusBwgp+d3augWSorXabnqXUu9SabEhCpCgZJEUjUTTI41OOBBYuMMxawa3177POT6qLfLeQ==}
|
||||
@@ -1883,7 +1904,11 @@ packages:
|
||||
|
||||
/commander@2.20.3:
|
||||
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
||||
dev: true
|
||||
|
||||
/commander@8.3.0:
|
||||
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
|
||||
engines: {node: '>= 12'}
|
||||
dev: false
|
||||
|
||||
/common-tags@1.8.2:
|
||||
resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==}
|
||||
@@ -1892,7 +1917,15 @@ packages:
|
||||
|
||||
/concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
dev: true
|
||||
|
||||
/connect-history-api-fallback@1.6.0:
|
||||
resolution: {integrity: sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==}
|
||||
engines: {node: '>=0.8'}
|
||||
dev: false
|
||||
|
||||
/consola@2.15.3:
|
||||
resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==}
|
||||
dev: false
|
||||
|
||||
/convert-source-map@1.9.0:
|
||||
resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
|
||||
@@ -1926,6 +1959,21 @@ packages:
|
||||
csstype: 3.0.11
|
||||
dev: true
|
||||
|
||||
/css-select@4.3.0:
|
||||
resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
css-what: 6.1.0
|
||||
domhandler: 4.3.1
|
||||
domutils: 2.8.0
|
||||
nth-check: 2.1.1
|
||||
dev: false
|
||||
|
||||
/css-what@6.1.0:
|
||||
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
|
||||
engines: {node: '>= 6'}
|
||||
dev: false
|
||||
|
||||
/csstype@2.6.21:
|
||||
resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==}
|
||||
|
||||
@@ -1997,6 +2045,50 @@ packages:
|
||||
engines: {node: '>=0.4.0'}
|
||||
dev: false
|
||||
|
||||
/dom-serializer@1.4.1:
|
||||
resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 4.3.1
|
||||
entities: 2.2.0
|
||||
dev: false
|
||||
|
||||
/domelementtype@2.3.0:
|
||||
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
|
||||
dev: false
|
||||
|
||||
/domhandler@4.3.1:
|
||||
resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
|
||||
engines: {node: '>= 4'}
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
dev: false
|
||||
|
||||
/domutils@2.8.0:
|
||||
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
|
||||
dependencies:
|
||||
dom-serializer: 1.4.1
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 4.3.1
|
||||
dev: false
|
||||
|
||||
/dot-case@3.0.4:
|
||||
resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
|
||||
dependencies:
|
||||
no-case: 3.0.4
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/dotenv-expand@8.0.3:
|
||||
resolution: {integrity: sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/dotenv@16.0.3:
|
||||
resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/ecc-jsbn@0.1.2:
|
||||
resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==}
|
||||
dependencies:
|
||||
@@ -2010,12 +2102,15 @@ packages:
|
||||
hasBin: true
|
||||
dependencies:
|
||||
jake: 10.8.5
|
||||
dev: true
|
||||
|
||||
/electron-to-chromium@1.4.284:
|
||||
resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==}
|
||||
dev: true
|
||||
|
||||
/entities@2.2.0:
|
||||
resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
|
||||
dev: false
|
||||
|
||||
/es-abstract@1.21.1:
|
||||
resolution: {integrity: sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2079,7 +2174,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-android-arm64@0.15.16:
|
||||
@@ -2088,7 +2182,6 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-darwin-64@0.15.16:
|
||||
@@ -2097,7 +2190,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-darwin-arm64@0.15.16:
|
||||
@@ -2106,7 +2198,6 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-freebsd-64@0.15.16:
|
||||
@@ -2115,7 +2206,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-freebsd-arm64@0.15.16:
|
||||
@@ -2124,7 +2214,6 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-linux-32@0.15.16:
|
||||
@@ -2133,7 +2222,6 @@ packages:
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-linux-64@0.15.16:
|
||||
@@ -2142,7 +2230,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-linux-arm64@0.15.16:
|
||||
@@ -2151,7 +2238,6 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-linux-arm@0.15.16:
|
||||
@@ -2160,7 +2246,6 @@ packages:
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-linux-mips64le@0.15.16:
|
||||
@@ -2169,7 +2254,6 @@ packages:
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-linux-ppc64le@0.15.16:
|
||||
@@ -2178,7 +2262,6 @@ packages:
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-linux-riscv64@0.15.16:
|
||||
@@ -2187,7 +2270,6 @@ packages:
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-linux-s390x@0.15.16:
|
||||
@@ -2196,7 +2278,6 @@ packages:
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-netbsd-64@0.15.16:
|
||||
@@ -2205,7 +2286,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-openbsd-64@0.15.16:
|
||||
@@ -2214,7 +2294,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-sunos-64@0.15.16:
|
||||
@@ -2223,7 +2302,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-windows-32@0.15.16:
|
||||
@@ -2232,7 +2310,6 @@ packages:
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-windows-64@0.15.16:
|
||||
@@ -2241,7 +2318,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-windows-arm64@0.15.16:
|
||||
@@ -2250,7 +2326,6 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild@0.15.16:
|
||||
@@ -2281,7 +2356,6 @@ packages:
|
||||
esbuild-windows-32: 0.15.16
|
||||
esbuild-windows-64: 0.15.16
|
||||
esbuild-windows-arm64: 0.15.16
|
||||
dev: true
|
||||
|
||||
/escalade@3.1.1:
|
||||
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
|
||||
@@ -2335,7 +2409,6 @@ packages:
|
||||
glob-parent: 5.1.2
|
||||
merge2: 1.4.1
|
||||
micromatch: 4.0.5
|
||||
dev: true
|
||||
|
||||
/fast-json-stable-stringify@2.1.0:
|
||||
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
|
||||
@@ -2344,13 +2417,11 @@ packages:
|
||||
resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==}
|
||||
dependencies:
|
||||
reusify: 1.0.4
|
||||
dev: true
|
||||
|
||||
/filelist@1.0.4:
|
||||
resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
|
||||
dependencies:
|
||||
minimatch: 5.1.0
|
||||
dev: true
|
||||
|
||||
/fill-range@7.0.1:
|
||||
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
|
||||
@@ -2396,6 +2467,15 @@ packages:
|
||||
mime-types: 2.1.35
|
||||
dev: false
|
||||
|
||||
/fs-extra@10.1.0:
|
||||
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
graceful-fs: 4.2.10
|
||||
jsonfile: 6.1.0
|
||||
universalify: 2.0.0
|
||||
dev: false
|
||||
|
||||
/fs-extra@9.1.0:
|
||||
resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -2419,7 +2499,6 @@ packages:
|
||||
|
||||
/function-bind@1.1.1:
|
||||
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
|
||||
dev: true
|
||||
|
||||
/function.prototype.name@1.1.5:
|
||||
resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==}
|
||||
@@ -2519,7 +2598,6 @@ packages:
|
||||
|
||||
/graceful-fs@4.2.10:
|
||||
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
|
||||
dev: true
|
||||
|
||||
/har-schema@2.0.0:
|
||||
resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==}
|
||||
@@ -2547,7 +2625,6 @@ packages:
|
||||
/has-flag@4.0.0:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/has-property-descriptors@1.0.0:
|
||||
resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==}
|
||||
@@ -2577,13 +2654,35 @@ packages:
|
||||
engines: {node: '>= 0.4.0'}
|
||||
dependencies:
|
||||
function-bind: 1.1.1
|
||||
dev: true
|
||||
|
||||
/he@1.2.0:
|
||||
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/highlight.js@11.7.0:
|
||||
resolution: {integrity: sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dev: true
|
||||
|
||||
/howler@2.2.3:
|
||||
resolution: {integrity: sha512-QM0FFkw0LRX1PR8pNzJVAY25JhIWvbKMBFM4gqk+QdV+kPXOhleWGCB6AiAF/goGjIHK2e/nIElplvjQwhr0jg==}
|
||||
dev: false
|
||||
|
||||
/html-minifier-terser@6.1.0:
|
||||
resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==}
|
||||
engines: {node: '>=12'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
camel-case: 4.1.2
|
||||
clean-css: 5.3.2
|
||||
commander: 8.3.0
|
||||
he: 1.2.0
|
||||
param-case: 3.0.4
|
||||
relateurl: 0.2.7
|
||||
terser: 5.16.1
|
||||
dev: false
|
||||
|
||||
/http-signature@1.2.0:
|
||||
resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==}
|
||||
engines: {node: '>=0.8', npm: '>=1.3.7'}
|
||||
@@ -2665,7 +2764,6 @@ packages:
|
||||
resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==}
|
||||
dependencies:
|
||||
has: 1.0.3
|
||||
dev: true
|
||||
|
||||
/is-date-object@1.0.5:
|
||||
resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
|
||||
@@ -2781,7 +2879,6 @@ packages:
|
||||
chalk: 4.1.2
|
||||
filelist: 1.0.4
|
||||
minimatch: 3.1.2
|
||||
dev: true
|
||||
|
||||
/jest-worker@26.6.2:
|
||||
resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==}
|
||||
@@ -2846,7 +2943,6 @@ packages:
|
||||
universalify: 2.0.0
|
||||
optionalDependencies:
|
||||
graceful-fs: 4.2.10
|
||||
dev: true
|
||||
|
||||
/jsonpointer@5.0.1:
|
||||
resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==}
|
||||
@@ -2898,6 +2994,12 @@ packages:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
dev: true
|
||||
|
||||
/lower-case@2.0.2:
|
||||
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
|
||||
dependencies:
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/lru-cache@5.1.1:
|
||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||
dependencies:
|
||||
@@ -2930,7 +3032,6 @@ packages:
|
||||
/merge2@1.4.1:
|
||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||
engines: {node: '>= 8'}
|
||||
dev: true
|
||||
|
||||
/micromatch@4.0.5:
|
||||
resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
|
||||
@@ -2938,7 +3039,6 @@ packages:
|
||||
dependencies:
|
||||
braces: 3.0.2
|
||||
picomatch: 2.3.1
|
||||
dev: true
|
||||
|
||||
/mime-db@1.52.0:
|
||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||
@@ -2956,14 +3056,12 @@ packages:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
dependencies:
|
||||
brace-expansion: 1.1.11
|
||||
dev: true
|
||||
|
||||
/minimatch@5.1.0:
|
||||
resolution: {integrity: sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
dev: true
|
||||
|
||||
/mlly@1.0.0:
|
||||
resolution: {integrity: sha512-QL108Hwt+u9bXdWgOI0dhzZfACovn5Aen4Xvc8Jasd9ouRH4NjnrXEiyP3nVvJo91zPlYjVRckta0Nt2zfoR6g==}
|
||||
@@ -3023,11 +3121,25 @@ packages:
|
||||
is-buffer: 1.1.6
|
||||
dev: false
|
||||
|
||||
/no-case@3.0.4:
|
||||
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
|
||||
dependencies:
|
||||
lower-case: 2.0.2
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/node-bitmap@0.0.1:
|
||||
resolution: {integrity: sha512-Jx5lPaaLdIaOsj2mVLWMWulXF6GQVdyLvNSxmiYCvZ8Ma2hfKX0POoR2kgKOqz+oFsRreq0yYZjQ2wjE9VNzCA==}
|
||||
engines: {node: '>=v0.6.5'}
|
||||
dev: false
|
||||
|
||||
/node-html-parser@5.4.2:
|
||||
resolution: {integrity: sha512-RaBPP3+51hPne/OolXxcz89iYvQvKOydaqoePpOgXcrOKZhjVIzmpKZz+Hd/RBO2/zN2q6CNJhQzucVz+u3Jyw==}
|
||||
dependencies:
|
||||
css-select: 4.3.0
|
||||
he: 1.2.0
|
||||
dev: false
|
||||
|
||||
/node-releases@2.0.8:
|
||||
resolution: {integrity: sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==}
|
||||
dev: true
|
||||
@@ -3036,6 +3148,12 @@ packages:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
/nth-check@2.1.1:
|
||||
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
dev: false
|
||||
|
||||
/oauth-sign@0.9.0:
|
||||
resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==}
|
||||
dev: false
|
||||
@@ -3075,12 +3193,26 @@ packages:
|
||||
kind-of: 6.0.3
|
||||
dev: false
|
||||
|
||||
/param-case@3.0.4:
|
||||
resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
|
||||
dependencies:
|
||||
dot-case: 3.0.4
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/parse-data-uri@0.2.0:
|
||||
resolution: {integrity: sha512-uOtts8NqDcaCt1rIsO3VFDRsAfgE4c6osG4d9z3l4dCBlxYFzni6Di/oNU270SDrjkfZuUvLZx1rxMyqh46Y9w==}
|
||||
dependencies:
|
||||
data-uri-to-buffer: 0.0.3
|
||||
dev: false
|
||||
|
||||
/pascal-case@3.1.2:
|
||||
resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
|
||||
dependencies:
|
||||
no-case: 3.0.4
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/path-is-absolute@1.0.1:
|
||||
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -3088,7 +3220,10 @@ packages:
|
||||
|
||||
/path-parse@1.0.7:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
dev: true
|
||||
|
||||
/pathe@0.2.0:
|
||||
resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==}
|
||||
dev: false
|
||||
|
||||
/pathe@1.0.0:
|
||||
resolution: {integrity: sha512-nPdMG0Pd09HuSsr7QOKUXO2Jr9eqaDiZvDwdyIhNG5SHYujkQHYKDfGQkulBxvbDHz8oHLsTgKN86LSwYzSHAg==}
|
||||
@@ -3198,7 +3333,6 @@ packages:
|
||||
|
||||
/queue-microtask@1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
dev: true
|
||||
|
||||
/randombytes@2.1.0:
|
||||
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
||||
@@ -3269,6 +3403,11 @@ packages:
|
||||
jsesc: 0.5.0
|
||||
dev: true
|
||||
|
||||
/relateurl@0.2.7:
|
||||
resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==}
|
||||
engines: {node: '>= 0.10'}
|
||||
dev: false
|
||||
|
||||
/request@2.88.2:
|
||||
resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -3308,12 +3447,10 @@ packages:
|
||||
is-core-module: 2.11.0
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
dev: true
|
||||
|
||||
/reusify@1.0.4:
|
||||
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
|
||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/rollup-plugin-terser@7.0.2(rollup@2.79.1):
|
||||
resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==}
|
||||
@@ -3334,7 +3471,6 @@ packages:
|
||||
hasBin: true
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/rollup@3.10.0:
|
||||
resolution: {integrity: sha512-JmRYz44NjC1MjVF2VKxc0M1a97vn+cDxeqWmnwyAF4FvpjK8YFdHpaqvQB+3IxCvX05vJxKZkoMDU8TShhmJVA==}
|
||||
@@ -3348,7 +3484,6 @@ packages:
|
||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||
dependencies:
|
||||
queue-microtask: 1.2.3
|
||||
dev: true
|
||||
|
||||
/safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
@@ -3425,7 +3560,6 @@ packages:
|
||||
dependencies:
|
||||
buffer-from: 1.1.2
|
||||
source-map: 0.6.1
|
||||
dev: true
|
||||
|
||||
/source-map@0.6.1:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
@@ -3519,12 +3653,10 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
dev: true
|
||||
|
||||
/supports-preserve-symlinks-flag@1.0.0:
|
||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dev: true
|
||||
|
||||
/temp-dir@2.0.0:
|
||||
resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==}
|
||||
@@ -3550,7 +3682,11 @@ packages:
|
||||
acorn: 8.8.1
|
||||
commander: 2.20.3
|
||||
source-map-support: 0.5.21
|
||||
dev: true
|
||||
|
||||
/throttle-debounce@5.0.0:
|
||||
resolution: {integrity: sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg==}
|
||||
engines: {node: '>=12.22'}
|
||||
dev: false
|
||||
|
||||
/through@2.3.8:
|
||||
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
||||
@@ -3584,6 +3720,10 @@ packages:
|
||||
resolution: {integrity: sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==}
|
||||
dev: true
|
||||
|
||||
/tslib@2.5.0:
|
||||
resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
|
||||
dev: false
|
||||
|
||||
/tunnel-agent@0.6.0:
|
||||
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
|
||||
dependencies:
|
||||
@@ -3675,7 +3815,6 @@ packages:
|
||||
/universalify@2.0.0:
|
||||
resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
dev: true
|
||||
|
||||
/unplugin-auto-import@0.12.0(rollup@2.79.1):
|
||||
resolution: {integrity: sha512-y1flixUHt0UioxeUwXe4N9GmIJskBz7uoC2qFGaUJO1feN9PYITHhRVqfXxYt7VAaZ24InNIeZIAIoQbRm3ZPw==}
|
||||
@@ -3780,6 +3919,26 @@ packages:
|
||||
extsprintf: 1.3.0
|
||||
dev: false
|
||||
|
||||
/vite-plugin-html@3.2.0(vite@3.2.4):
|
||||
resolution: {integrity: sha512-2VLCeDiHmV/BqqNn5h2V+4280KRgQzCFN47cst3WiNK848klESPQnzuC3okH5XHtgwHH/6s1Ho/YV6yIO0pgoQ==}
|
||||
peerDependencies:
|
||||
vite: '>=2.0.0'
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 4.2.1
|
||||
colorette: 2.0.20
|
||||
connect-history-api-fallback: 1.6.0
|
||||
consola: 2.15.3
|
||||
dotenv: 16.0.3
|
||||
dotenv-expand: 8.0.3
|
||||
ejs: 3.1.8
|
||||
fast-glob: 3.2.12
|
||||
fs-extra: 10.1.0
|
||||
html-minifier-terser: 6.1.0
|
||||
node-html-parser: 5.4.2
|
||||
pathe: 0.2.0
|
||||
vite: 3.2.4(sass@1.56.1)
|
||||
dev: false
|
||||
|
||||
/vite-plugin-pwa@0.14.1(vite@3.2.4)(workbox-build@6.5.4)(workbox-window@6.5.4):
|
||||
resolution: {integrity: sha512-5zx7yhQ8RTLwV71+GA9YsQQ63ALKG8XXIMqRJDdZkR8ZYftFcRgnzM7wOWmQZ/DATspyhPih5wCdcZnAIsM+mA==}
|
||||
peerDependencies:
|
||||
@@ -3831,7 +3990,6 @@ packages:
|
||||
sass: 1.56.1
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/vooks@0.2.12(vue@3.2.45):
|
||||
resolution: {integrity: sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==}
|
||||
@@ -3866,6 +4024,10 @@ packages:
|
||||
vue: 3.2.45
|
||||
dev: false
|
||||
|
||||
/vue-slider-component@4.1.0-beta.7:
|
||||
resolution: {integrity: sha512-Qb7K920ZG7PoQswoF6Ias+i3W2rd3k4fpk04JUl82kEUcN86Yg6et7bVSKWt/7VpQe8a5IT3BqCKSCOZ7AJgCA==}
|
||||
dev: false
|
||||
|
||||
/vue@3.2.45:
|
||||
resolution: {integrity: sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==}
|
||||
dependencies:
|
||||
|
||||
32
src/App.vue
32
src/App.vue
@@ -14,7 +14,10 @@
|
||||
ref="mainContent"
|
||||
class="main"
|
||||
id="main"
|
||||
:class="[music.showPlayList ? 'playlist' : null]"
|
||||
:class="{
|
||||
playlist: music.showPlayList,
|
||||
search: site.searchInputActive,
|
||||
}"
|
||||
>
|
||||
<n-back-top
|
||||
:bottom="music.getPlaylists[0] && music.showPlayBar ? 100 : 40"
|
||||
@@ -77,10 +80,10 @@ const spacePlayOrPause = (e) => {
|
||||
// 更改页面标题
|
||||
const setSiteTitle = (val) => {
|
||||
const title = val
|
||||
? val === "SPlayer"
|
||||
? val === import.meta.env.VITE_SITE_TITLE
|
||||
? val
|
||||
: val + " - SPlayer"
|
||||
: site.siteTitle;
|
||||
: val + " - " + import.meta.env.VITE_SITE_TITLE
|
||||
: sessionStorage.getItem("siteTitle") ?? import.meta.env.VITE_SITE_TITLE;
|
||||
site.siteTitle = title;
|
||||
sessionStorage.setItem("siteTitle", title);
|
||||
if (!music.getPlayState) {
|
||||
@@ -161,7 +164,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}`;
|
||||
console.info(
|
||||
`%c${logoText} %c ${copyrightNotice}`,
|
||||
@@ -232,12 +235,31 @@ onMounted(() => {
|
||||
margin: 0 auto;
|
||||
div:nth-of-type(2) {
|
||||
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 {
|
||||
div:nth-of-type(2) {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
&.search {
|
||||
div:nth-of-type(2) {
|
||||
&::after {
|
||||
pointer-events: all;
|
||||
background-color: #00000040;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +116,6 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
console.log("销毁");
|
||||
window.removeEventListener("resize", getBannerHeight);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
</template>
|
||||
|
||||
<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 { userStore } from "@/store";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
@@ -24,6 +24,12 @@
|
||||
:src="item.cover.replace(/^http:/, 'https:') + '?param=200y200'"
|
||||
fallback-src="/images/pic/default.png"
|
||||
/>
|
||||
<n-avatar
|
||||
round
|
||||
class="shadow"
|
||||
:src="item.cover.replace(/^http:/, 'https:') + '?param=200y200'"
|
||||
fallback-src="/images/pic/default.png"
|
||||
/>
|
||||
<n-icon size="40" :component="PeopleSearchOne" />
|
||||
</div>
|
||||
<n-text class="name text-hidden">{{ item.name }}</n-text>
|
||||
@@ -205,12 +211,26 @@ onMounted(() => {
|
||||
box-shadow: 0 4px 16px 0 #00000020;
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s;
|
||||
.n-avatar {
|
||||
.coverImg {
|
||||
filter: brightness(1);
|
||||
transform: scale(1);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: all 0.3s;
|
||||
z-index: 1;
|
||||
}
|
||||
.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 {
|
||||
opacity: 0;
|
||||
@@ -218,6 +238,7 @@ onMounted(() => {
|
||||
position: absolute;
|
||||
color: #fff;
|
||||
transition: all 0.3s;
|
||||
z-index: 1;
|
||||
}
|
||||
&:hover {
|
||||
box-shadow: 0 4px 16px 0 #00000040;
|
||||
@@ -225,10 +246,13 @@ onMounted(() => {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
.n-avatar {
|
||||
.coverImg {
|
||||
filter: brightness(0.8);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.shadow {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
.n-avatar {
|
||||
|
||||
@@ -23,6 +23,11 @@
|
||||
:src="item.cover.replace(/^http:/, 'https:') + '?param=300y300'"
|
||||
fallback-src="/images/pic/default.png"
|
||||
/>
|
||||
<n-avatar
|
||||
class="shadow"
|
||||
:src="item.cover.replace(/^http:/, 'https:') + '?param=300y300'"
|
||||
fallback-src="/images/pic/default.png"
|
||||
/>
|
||||
<n-icon class="play" size="40">
|
||||
<PlayOne theme="filled" />
|
||||
</n-icon>
|
||||
@@ -86,7 +91,16 @@
|
||||
</template>
|
||||
|
||||
<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 { delPlayList, likePlaylist } from "@/api/playlist";
|
||||
import { likeAlbum } from "@/api/album";
|
||||
import { musicStore, userStore } from "@/store";
|
||||
@@ -131,6 +145,19 @@ const props = defineProps({
|
||||
});
|
||||
const playlistUpdateRef = ref(null);
|
||||
|
||||
// 图标渲染
|
||||
const renderIcon = (icon) => {
|
||||
return () => {
|
||||
return h(
|
||||
NIcon,
|
||||
{ style: { transform: "translateX(2px)" } },
|
||||
{
|
||||
default: () => icon,
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
// 右键菜单数据
|
||||
const rightMenuX = ref(0);
|
||||
const rightMenuY = ref(0);
|
||||
@@ -150,9 +177,10 @@ const openRightMenu = (e, data) => {
|
||||
router.currentRoute.value.name === "user-playlists" ? true : false,
|
||||
props: {
|
||||
onClick: () => {
|
||||
playlistUpdateRef.value.openUpdateModel(data);
|
||||
playlistUpdateRef.value.openUpdateModal(data);
|
||||
},
|
||||
},
|
||||
icon: renderIcon(h(Editor)),
|
||||
},
|
||||
{
|
||||
key: "del",
|
||||
@@ -164,6 +192,7 @@ const openRightMenu = (e, data) => {
|
||||
toDelPlayList(data);
|
||||
},
|
||||
},
|
||||
icon: renderIcon(h(DeleteFour)),
|
||||
},
|
||||
{
|
||||
key: "likePlaylist",
|
||||
@@ -180,6 +209,7 @@ const openRightMenu = (e, data) => {
|
||||
toChangeLike(data.id);
|
||||
},
|
||||
},
|
||||
icon: renderIcon(h(isLikeOrDislike(data.id) ? Like : Unlike)),
|
||||
},
|
||||
{
|
||||
key: "likeAlbum",
|
||||
@@ -195,6 +225,7 @@ const openRightMenu = (e, data) => {
|
||||
toChangeLike(data.id);
|
||||
},
|
||||
},
|
||||
icon: renderIcon(h(isLikeOrDislike(data.id) ? Like : Unlike)),
|
||||
},
|
||||
{
|
||||
key: "copy",
|
||||
@@ -221,6 +252,7 @@ const openRightMenu = (e, data) => {
|
||||
}
|
||||
},
|
||||
},
|
||||
icon: renderIcon(h(LinkTwo)),
|
||||
},
|
||||
];
|
||||
rightMenuShow.value = true;
|
||||
@@ -279,10 +311,10 @@ const isLikeOrDislike = (id) => {
|
||||
const playlists = user.getUserPlayLists.like;
|
||||
const albums = user.getUserAlbumLists.list;
|
||||
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) {
|
||||
return !albums.some((item) => item.id === id);
|
||||
return !albums.some((item) => item.id === Number(id));
|
||||
}
|
||||
return true;
|
||||
};
|
||||
@@ -356,7 +388,7 @@ onMounted(() => {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
// overflow: hidden;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
@@ -365,7 +397,25 @@ onMounted(() => {
|
||||
border-radius: 8px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: all 0.3s;
|
||||
overflow: hidden;
|
||||
transition: filter 0.3s;
|
||||
z-index: 1;
|
||||
:deep(img) {
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
}
|
||||
.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 {
|
||||
opacity: 0;
|
||||
@@ -378,6 +428,7 @@ onMounted(() => {
|
||||
border-radius: 50%;
|
||||
transform: scale(0.8);
|
||||
transition: all 0.3s;
|
||||
z-index: 1;
|
||||
}
|
||||
.description {
|
||||
position: absolute;
|
||||
@@ -390,7 +441,9 @@ onMounted(() => {
|
||||
backdrop-filter: blur(4px);
|
||||
padding: 6px;
|
||||
border-top-left-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
transition: all 0.3s;
|
||||
z-index: 1;
|
||||
.num {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -404,10 +457,11 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
box-shadow: 0 15px 30px rgb(0 0 0 / 10%);
|
||||
.coverImg {
|
||||
filter: brightness(0.8);
|
||||
transform: scale(1.1);
|
||||
:deep(img) {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
.play {
|
||||
transform: scale(1);
|
||||
@@ -416,6 +470,9 @@ onMounted(() => {
|
||||
.description {
|
||||
opacity: 0;
|
||||
}
|
||||
.shadow {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<Transition mode="out-in">
|
||||
<div class="datalists" v-if="listData[0]">
|
||||
<div class="datalists" id="datalists" v-if="listData[0]">
|
||||
<n-card
|
||||
v-for="item in listData"
|
||||
:key="item"
|
||||
@@ -108,7 +108,7 @@
|
||||
<n-icon
|
||||
class="download"
|
||||
size="20"
|
||||
@click.stop="downloadSongRef.openDownloadModel(item)"
|
||||
@click.stop="downloadSongRef.openDownloadModal(item)"
|
||||
>
|
||||
<DownloadFour theme="filled" />
|
||||
</n-icon>
|
||||
@@ -202,7 +202,7 @@
|
||||
class="item"
|
||||
@click="
|
||||
() => {
|
||||
downloadSongRef.openDownloadModel(drawerData);
|
||||
downloadSongRef.openDownloadModal(drawerData);
|
||||
drawerShow = false;
|
||||
}
|
||||
"
|
||||
@@ -331,6 +331,7 @@ import { musicStore, settingStore, userStore } from "@/store";
|
||||
import { useRouter } from "vue-router";
|
||||
import { setCloudDel } from "@/api/user";
|
||||
import { NIcon } from "naive-ui";
|
||||
import { soundStop } from "@/utils/Player";
|
||||
import AllArtists from "./AllArtists.vue";
|
||||
import AddPlaylist from "@/components/DataModal/AddPlaylist.vue";
|
||||
import CloudMatch from "@/components/DataModal/CloudMatch.vue";
|
||||
@@ -418,7 +419,7 @@ const openRightMenu = (e, data) => {
|
||||
label: "下一首播放",
|
||||
icon: renderIcon(AddMusic),
|
||||
show:
|
||||
music.getPersonalFmMode || music.getPlaySongData.id == data.id
|
||||
music.getPersonalFmMode || music.getPlaySongData?.id == data.id
|
||||
? false
|
||||
: true,
|
||||
props: {
|
||||
@@ -444,7 +445,7 @@ const openRightMenu = (e, data) => {
|
||||
icon: renderIcon(DownloadFour),
|
||||
props: {
|
||||
onClick: () => {
|
||||
downloadSongRef.value.openDownloadModel(data);
|
||||
downloadSongRef.value.openDownloadModal(data);
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -581,7 +582,11 @@ const openDrawer = (data) => {
|
||||
// 播放并添加
|
||||
const playSong = (data, song) => {
|
||||
console.log(data, song);
|
||||
music.setPersonalFmMode(false);
|
||||
if (music.getPersonalFmMode) {
|
||||
soundStop($player);
|
||||
music.setPersonalFmMode(false);
|
||||
}
|
||||
music.setPlayState(true);
|
||||
if (router.currentRoute.value.name !== "history") music.setPlaylists(data);
|
||||
// 检查是否为云盘歌曲
|
||||
if (router.currentRoute.value.name === "user-cloud") {
|
||||
@@ -637,7 +642,8 @@ const jumpLink = (id, type) => {
|
||||
&:hover {
|
||||
border-color: var(--main-color);
|
||||
box-shadow: 0 1px 2px -2px var(--main-boxshadow-color),
|
||||
0 3px 6px 0 var(--main-boxshadow-color), 0 5px 12px 4px var(--main-boxshadow-hover-color);
|
||||
0 3px 6px 0 var(--main-boxshadow-color),
|
||||
0 5px 12px 4px var(--main-boxshadow-hover-color);
|
||||
.action {
|
||||
.like,
|
||||
.download {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
>
|
||||
<div class="copyright">
|
||||
<div class="desc">
|
||||
<n-text class="name">SPlayer</n-text>
|
||||
<n-text class="name">{{ siteTitle }}</n-text>
|
||||
<n-text class="version" :depth="3">
|
||||
v {{ packageJson.version }}
|
||||
</n-text>
|
||||
@@ -53,6 +53,7 @@ import { GithubOne } from "@icon-park/vue-next";
|
||||
import packageJson from "@/../package.json";
|
||||
|
||||
// 关于本站数据
|
||||
const siteTitle = import.meta.env.VITE_SITE_TITLE;
|
||||
const showAboutModal = ref(false);
|
||||
const icp = ref(import.meta.env.VITE_ICP ? import.meta.env.VITE_ICP : null);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<n-modal
|
||||
class="add-playlist s-modal"
|
||||
v-model:show="addToPlaylistModel"
|
||||
v-model:show="addToPlaylistModal"
|
||||
preset="card"
|
||||
:bordered="false"
|
||||
:on-after-leave="closeAddToPlaylist"
|
||||
@@ -61,7 +61,7 @@ const user = userStore();
|
||||
const createPlaylistRef = ref(null);
|
||||
|
||||
// 收藏到歌单数据
|
||||
const addToPlaylistModel = ref(false);
|
||||
const addToPlaylistModal = ref(false);
|
||||
const addToPlaylistId = ref(null);
|
||||
|
||||
// 收藏到歌单
|
||||
@@ -88,14 +88,14 @@ const openAddToPlaylist = (id) => {
|
||||
if (!user.getUserPlayLists.has && !user.getUserPlayLists.isLoading) {
|
||||
user.setUserPlayLists();
|
||||
}
|
||||
addToPlaylistModel.value = true;
|
||||
addToPlaylistModal.value = true;
|
||||
addToPlaylistId.value = id;
|
||||
console.log("开启", addToPlaylistModel.value, addToPlaylistId.value);
|
||||
console.log("开启", addToPlaylistModal.value, addToPlaylistId.value);
|
||||
};
|
||||
|
||||
// 关闭收藏到歌单
|
||||
const closeAddToPlaylist = () => {
|
||||
addToPlaylistModel.value = false;
|
||||
addToPlaylistModal.value = false;
|
||||
};
|
||||
|
||||
// 暴露方法
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<n-modal
|
||||
class="s-modal"
|
||||
v-model:show="cloudMatchModel"
|
||||
v-model:show="cloudMatchModal"
|
||||
preset="card"
|
||||
title="歌曲信息纠正"
|
||||
:bordered="false"
|
||||
@@ -65,7 +65,7 @@ const user = userStore();
|
||||
// 歌曲信息纠正数据
|
||||
const cloudDataLoad = inject("cloudDataLoad", null);
|
||||
const smallSongDataRef = ref(null);
|
||||
const cloudMatchModel = ref(false);
|
||||
const cloudMatchModal = ref(false);
|
||||
const cloudMatchBeforeData = ref(null);
|
||||
const cloudMatchId = ref(null);
|
||||
const cloudMatchValue = ref({
|
||||
@@ -102,7 +102,7 @@ const setCloudMatchBtn = (data) => {
|
||||
const openCloudMatch = (data) => {
|
||||
cloudMatchValue.value.sid = data.id;
|
||||
cloudMatchBeforeData.value = data;
|
||||
cloudMatchModel.value = true;
|
||||
cloudMatchModal.value = true;
|
||||
};
|
||||
|
||||
// 关闭歌曲纠正
|
||||
@@ -110,7 +110,7 @@ const closeCloudMatch = () => {
|
||||
cloudMatchBeforeData.value = null;
|
||||
cloudMatchId.value = null;
|
||||
cloudMatchValue.value.asid = null;
|
||||
cloudMatchModel.value = false;
|
||||
cloudMatchModal.value = false;
|
||||
};
|
||||
|
||||
// 暴露方法
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<n-modal
|
||||
class="s-modal downloadModel"
|
||||
v-model:show="downloadModel"
|
||||
class="s-modal downloadModal"
|
||||
v-model:show="downloadModal"
|
||||
preset="card"
|
||||
title="歌曲下载"
|
||||
:bordered="false"
|
||||
:on-after-leave="closeDownloadModel"
|
||||
:on-after-leave="closeDownloadModal"
|
||||
>
|
||||
<Transition mode="out-in">
|
||||
<div v-if="songData">
|
||||
@@ -42,7 +42,7 @@
|
||||
</Transition>
|
||||
<template #footer>
|
||||
<n-space justify="end">
|
||||
<n-button @click="closeDownloadModel"> 取消 </n-button>
|
||||
<n-button @click="closeDownloadModal"> 取消 </n-button>
|
||||
<n-button
|
||||
:disabled="!downloadChoose"
|
||||
:loading="downloadStatus"
|
||||
@@ -75,7 +75,7 @@ const router = useRouter();
|
||||
const songId = ref(null);
|
||||
const songData = ref(null);
|
||||
const downloadStatus = ref(false);
|
||||
const downloadModel = ref(false);
|
||||
const downloadModal = ref(false);
|
||||
const downloadChoose = ref(null);
|
||||
const downloadLevel = ref(null);
|
||||
|
||||
@@ -98,7 +98,7 @@ const toSongDownload = (id, br, name) => {
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
closeDownloadModel();
|
||||
closeDownloadModal();
|
||||
downloadStatus.value = false;
|
||||
$message.success(name + " 下载完成");
|
||||
});
|
||||
@@ -108,7 +108,7 @@ const toSongDownload = (id, br, name) => {
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
closeDownloadModel();
|
||||
closeDownloadModal();
|
||||
console.error("下载出现错误:" + err);
|
||||
$message.error("下载出现错误,请重试");
|
||||
});
|
||||
@@ -133,7 +133,7 @@ const getMusicDetailData = (id) => {
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
closeDownloadModel();
|
||||
closeDownloadModal();
|
||||
console.error("歌曲信息获取出现错误:" + err);
|
||||
$message.error("歌曲信息获取出现错误,请重试");
|
||||
});
|
||||
@@ -200,7 +200,7 @@ const getSongSize = (data, type) => {
|
||||
};
|
||||
|
||||
// 开启歌曲下载
|
||||
const openDownloadModel = (data) => {
|
||||
const openDownloadModal = (data) => {
|
||||
if (user.userLogin) {
|
||||
if (
|
||||
router.currentRoute.value.name === "user-cloud" ||
|
||||
@@ -209,7 +209,7 @@ const openDownloadModel = (data) => {
|
||||
data?.pc
|
||||
) {
|
||||
songId.value = data.id;
|
||||
downloadModel.value = true;
|
||||
downloadModal.value = true;
|
||||
getMusicDetailData(data.id);
|
||||
} else {
|
||||
$message.error("该歌曲需使用黑胶会员下载");
|
||||
@@ -220,22 +220,22 @@ const openDownloadModel = (data) => {
|
||||
};
|
||||
|
||||
// 关闭歌曲下载
|
||||
const closeDownloadModel = () => {
|
||||
const closeDownloadModal = () => {
|
||||
songId.value = null;
|
||||
songData.value = null;
|
||||
downloadStatus.value = false;
|
||||
downloadModel.value = false;
|
||||
downloadModal.value = false;
|
||||
downloadChoose.value = null;
|
||||
};
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
openDownloadModel,
|
||||
openDownloadModal,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.downloadModel {
|
||||
.downloadModal {
|
||||
.v-enter-active,
|
||||
.v-leave-active {
|
||||
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,7 +2,7 @@
|
||||
<n-drawer
|
||||
class="playlist-drawer"
|
||||
v-model:show="playListShow"
|
||||
:z-index="2000"
|
||||
:z-index="1"
|
||||
:width="400"
|
||||
:trap-focus="false"
|
||||
:block-scroll="false"
|
||||
@@ -11,7 +11,15 @@
|
||||
@after-leave="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">播放列表</n-text>
|
||||
<n-text class="num" :depth="3" v-if="music.getPlaylists.length > 0">
|
||||
{{ music.getPlaylists.length }} 首
|
||||
</n-text>
|
||||
</div>
|
||||
</template>
|
||||
<Transition mode="out-in">
|
||||
<div v-if="music.getPlaylists[0]">
|
||||
<n-card
|
||||
@@ -31,9 +39,13 @@
|
||||
@click="changeIndex(index)"
|
||||
>
|
||||
<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 }}
|
||||
</div>
|
||||
</n-text>
|
||||
<div v-else class="bar">
|
||||
<div
|
||||
v-for="item in 3"
|
||||
@@ -70,6 +82,7 @@
|
||||
<script setup>
|
||||
import { musicStore } from "@/store";
|
||||
import { DeleteFour } from "@icon-park/vue-next";
|
||||
import { soundStop } from "@/utils/Player";
|
||||
import AllArtists from "@/components/DataList/AllArtists.vue";
|
||||
|
||||
const music = musicStore();
|
||||
@@ -79,8 +92,17 @@ const playListShow = ref(false);
|
||||
|
||||
// 改变播放索引
|
||||
const changeIndex = (index) => {
|
||||
music.persistData.playSongIndex = index;
|
||||
music.setPlayState(true);
|
||||
try {
|
||||
if (music.persistData.playSongIndex !== index) {
|
||||
soundStop($player);
|
||||
music.persistData.playSongIndex = index;
|
||||
music.isLoadingSong = true;
|
||||
music.setPlayState(true);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("切换失败:" + err);
|
||||
$message.error("切换失败,请刷新后重试");
|
||||
}
|
||||
};
|
||||
|
||||
// 监听播放列表显隐
|
||||
@@ -89,7 +111,7 @@ watch(
|
||||
() => music.showPlayList,
|
||||
(val) => {
|
||||
playListShow.value = val;
|
||||
nextTick(() => {
|
||||
nextTick().then(() => {
|
||||
if (val && music.getPlaylists[0]) {
|
||||
const el = document.getElementById(
|
||||
`playlist${music.persistData.playSongIndex}`
|
||||
@@ -129,6 +151,17 @@ onBeforeUnmount(() => {
|
||||
.v-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.num {
|
||||
font-size: 14px;
|
||||
&::before {
|
||||
content: "-";
|
||||
margin: 0 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.songs {
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<n-modal
|
||||
class="s-modal"
|
||||
v-model:show="playlistUpdateModel"
|
||||
v-model:show="playlistUpdateModal"
|
||||
preset="card"
|
||||
title="歌单编辑"
|
||||
:bordered="false"
|
||||
:on-after-leave="closeUpdateModel"
|
||||
:on-after-leave="closeUpdateModal"
|
||||
>
|
||||
<n-form
|
||||
ref="playlistUpdateRef"
|
||||
@@ -42,7 +42,7 @@
|
||||
</n-form>
|
||||
<template #footer>
|
||||
<n-space justify="end">
|
||||
<n-button @click="closeUpdateModel"> 取消 </n-button>
|
||||
<n-button @click="closeUpdateModal"> 取消 </n-button>
|
||||
<n-button type="primary" @click="toUpdatePlayList"> 编辑 </n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
<script setup>
|
||||
import { playlistUpdate } from "@/api/playlist";
|
||||
import { formRules } from "@/utils/formRules.js";
|
||||
import { formRules } from "@/utils/formRules";
|
||||
import { musicStore, userStore } from "@/store";
|
||||
|
||||
const { textRule } = formRules();
|
||||
@@ -61,7 +61,7 @@ const user = userStore();
|
||||
// 更新歌单数据
|
||||
const playlistUpdateId = ref(null);
|
||||
const playlistUpdateRef = ref(null);
|
||||
const playlistUpdateModel = ref(false);
|
||||
const playlistUpdateModal = ref(false);
|
||||
const playlistUpdateRules = {
|
||||
name: textRule,
|
||||
};
|
||||
@@ -86,7 +86,7 @@ const toUpdatePlayList = (e) => {
|
||||
console.log(res);
|
||||
if (res.code === 200) {
|
||||
$message.success("编辑成功");
|
||||
closeUpdateModel();
|
||||
closeUpdateModal();
|
||||
user.setUserPlayLists();
|
||||
} else {
|
||||
$message.error("编辑失败,请重试");
|
||||
@@ -113,24 +113,24 @@ const openSelect = () => {
|
||||
};
|
||||
|
||||
// 开启编辑歌单
|
||||
const openUpdateModel = (data) => {
|
||||
const openUpdateModal = (data) => {
|
||||
playlistUpdateValue.value = {
|
||||
name: data.name,
|
||||
desc: data.desc,
|
||||
tags: data.tags,
|
||||
};
|
||||
playlistUpdateId.value = data.id;
|
||||
playlistUpdateModel.value = true;
|
||||
playlistUpdateModal.value = true;
|
||||
};
|
||||
|
||||
// 关闭更新歌单弹窗
|
||||
const closeUpdateModel = () => {
|
||||
playlistUpdateModel.value = false;
|
||||
const closeUpdateModal = () => {
|
||||
playlistUpdateModal.value = false;
|
||||
playlistUpdateId.value = null;
|
||||
};
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
openUpdateModel,
|
||||
openUpdateModal,
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
<nav>
|
||||
<div class="left">
|
||||
<div class="logo" @click="router.push('/')">
|
||||
<img src="/images/logo/favicon.svg" alt="logo" />
|
||||
</div>
|
||||
<div :class="site.searchInputActive ? 'controls hidden' : 'controls'">
|
||||
<n-icon size="22" :component="Left" @click="router.go(-1)" />
|
||||
<n-icon size="22" :component="Right" @click="router.go(1)" />
|
||||
<img :src="logoUrl" alt="logo" />
|
||||
</div>
|
||||
<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="Right" @click="router.go(1)" />
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
<div class="center">
|
||||
<router-link class="link" to="/">首页</router-link>
|
||||
@@ -24,9 +26,16 @@
|
||||
</div>
|
||||
<div class="right">
|
||||
<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
|
||||
class="dropdown"
|
||||
placement="bottom-end"
|
||||
:show="showDropdown"
|
||||
:show-arrow="true"
|
||||
@@ -67,6 +76,10 @@ import {
|
||||
History,
|
||||
SunOne,
|
||||
Moon,
|
||||
HamburgerButton,
|
||||
HomeTwo,
|
||||
FindOne,
|
||||
Me,
|
||||
} from "@icon-park/vue-next";
|
||||
import { userStore, settingStore, siteStore } from "@/store";
|
||||
import { useRouter } from "vue-router";
|
||||
@@ -79,6 +92,7 @@ const site = siteStore();
|
||||
const setting = settingStore();
|
||||
const aboutSiteRef = ref(null);
|
||||
const timeOut = ref(null);
|
||||
const logoUrl = import.meta.env.VITE_SITE_LOGO;
|
||||
|
||||
// 下拉菜单显隐
|
||||
const showDropdown = ref(false);
|
||||
@@ -91,6 +105,19 @@ const closeDropdown = (event) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 图标渲染
|
||||
const renderIcon = (icon) => {
|
||||
return () => {
|
||||
return h(
|
||||
NIcon,
|
||||
{ style: { transform: "translateX(2px)" } },
|
||||
{
|
||||
default: () => icon,
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
// 用户数据模块
|
||||
const userDataRender = () => {
|
||||
return h(
|
||||
@@ -239,28 +266,12 @@ const dropdownOptions = ref([
|
||||
{
|
||||
label: "播放历史",
|
||||
key: "history",
|
||||
icon: () => {
|
||||
return h(
|
||||
NIcon,
|
||||
{ style: { transform: "translateX(2px)" } },
|
||||
{
|
||||
default: () => h(History),
|
||||
}
|
||||
);
|
||||
},
|
||||
icon: renderIcon(h(History)),
|
||||
},
|
||||
{
|
||||
label: "全局设置",
|
||||
key: "setting",
|
||||
icon: () => {
|
||||
return h(
|
||||
NIcon,
|
||||
{ style: { transform: "translateX(2px)" } },
|
||||
{
|
||||
default: () => h(SettingTwo),
|
||||
}
|
||||
);
|
||||
},
|
||||
icon: renderIcon(h(SettingTwo)),
|
||||
},
|
||||
{
|
||||
label: () => {
|
||||
@@ -286,19 +297,30 @@ const dropdownOptions = ref([
|
||||
{
|
||||
label: "关于本站",
|
||||
key: "about",
|
||||
icon: () => {
|
||||
return h(
|
||||
NIcon,
|
||||
{ style: { transform: "translateX(2px)" } },
|
||||
{
|
||||
default: () => h(Info),
|
||||
}
|
||||
);
|
||||
},
|
||||
icon: renderIcon(h(Info)),
|
||||
},
|
||||
]);
|
||||
|
||||
// 下拉框事件
|
||||
// 移动端菜单
|
||||
const mbMenuOptions = ref([
|
||||
{
|
||||
label: "首页",
|
||||
key: "/",
|
||||
icon: renderIcon(h(HomeTwo)),
|
||||
},
|
||||
{
|
||||
label: "发现",
|
||||
key: "/discover",
|
||||
icon: renderIcon(h(FindOne)),
|
||||
},
|
||||
{
|
||||
label: "我的",
|
||||
key: "/user",
|
||||
icon: renderIcon(h(Me)),
|
||||
},
|
||||
]);
|
||||
|
||||
// 下拉框点击事件
|
||||
const menuSelect = (key) => {
|
||||
router.push(key);
|
||||
};
|
||||
@@ -362,7 +384,6 @@ watch(
|
||||
|
||||
onMounted(() => {
|
||||
changeUserOptions(user.userLogin);
|
||||
console.log(router);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
@@ -380,6 +401,17 @@ nav {
|
||||
align-items: center;
|
||||
max-width: 1400px;
|
||||
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 {
|
||||
flex: 1;
|
||||
max-width: 300px;
|
||||
@@ -393,30 +425,35 @@ nav {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-right: 12px;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
&:hover {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
.controls {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
@media (max-width: 520px) {
|
||||
&.hidden{
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
.n-icon {
|
||||
margin: 0 4px;
|
||||
border-radius: 8px;
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
background-color: var(--n-border-color);
|
||||
@media (min-width: 640px) {
|
||||
&:hover {
|
||||
background-color: var(--n-border-color);
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
@@ -444,7 +481,7 @@ nav {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--main-color);
|
||||
color: var(--n-color);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
@@ -453,7 +490,7 @@ nav {
|
||||
|
||||
.router-link-active {
|
||||
background-color: var(--main-color);
|
||||
color: var(--n-color);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
}
|
||||
.right {
|
||||
@@ -463,6 +500,10 @@ nav {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
@media (max-width: 520px) {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
}
|
||||
.avatar {
|
||||
width: 30px;
|
||||
min-width: 30px;
|
||||
@@ -471,6 +512,13 @@ nav {
|
||||
box-shadow: 0 4px 12px -2px rgb(0 0 0 / 10%);
|
||||
cursor: pointer;
|
||||
}
|
||||
.mb-menu {
|
||||
margin-left: 12px;
|
||||
display: none;
|
||||
@media (max-width: 768px) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -62,6 +62,10 @@ const pageSizes = ref([
|
||||
label: "50条/页",
|
||||
value: 50,
|
||||
},
|
||||
{
|
||||
label: "100条/页",
|
||||
value: 100,
|
||||
},
|
||||
]);
|
||||
|
||||
// 每页个数数据变化
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<script setup>
|
||||
import { getNewAlbum } from "@/api/home";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getLongTime } from "@/utils/timeTools.js";
|
||||
import { getLongTime } from "@/utils/timeTools";
|
||||
import CoverLists from "@/components/DataList/CoverLists.vue";
|
||||
const router = useRouter();
|
||||
|
||||
|
||||
@@ -190,8 +190,10 @@ onMounted(() => {
|
||||
border-radius: 8px;
|
||||
transform: scale(1);
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
background-color: #ffffff30;
|
||||
@media (min-width: 640px) {
|
||||
&:hover {
|
||||
background-color: #ffffff30;
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
transform: scale(0.9);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<script setup>
|
||||
import { getPersonalized } from "@/api/home";
|
||||
import { useRouter } from "vue-router";
|
||||
import { formatNumber } from "@/utils/timeTools.js";
|
||||
import { formatNumber } from "@/utils/timeTools";
|
||||
import CoverLists from "@/components/DataList/CoverLists.vue";
|
||||
const router = useRouter();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<Transition name="up">
|
||||
<div
|
||||
v-show="music.showBigPlayer"
|
||||
v-if="music.showBigPlayer"
|
||||
class="bplayer"
|
||||
:style="[
|
||||
music.getPlaySongData && setting.backgroundImageShow === 'blur'
|
||||
@@ -17,17 +17,12 @@
|
||||
/>
|
||||
<div class="icon-menu">
|
||||
<div class="menu-left">
|
||||
<div class="icon">
|
||||
<div v-if="setting.showLyricSetting" class="icon">
|
||||
<n-icon
|
||||
class="setting"
|
||||
size="30"
|
||||
:component="SettingsRound"
|
||||
@click="
|
||||
() => {
|
||||
music.setBigPlayerState(false);
|
||||
router.push('/setting/player');
|
||||
}
|
||||
"
|
||||
@click="LyricSettingRef.openLyricSetting()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -134,9 +129,10 @@
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
<div class="canvas">
|
||||
<canvas v-if="setting.musicFrequency" class="avBars" ref="avBars" />
|
||||
</div>
|
||||
<!-- 音乐频谱 -->
|
||||
<!-- <Spectrum v-if="setting.musicFrequency" /> -->
|
||||
<!-- 歌词设置 -->
|
||||
<LyricSetting ref="LyricSettingRef" />
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
@@ -152,10 +148,12 @@ import {
|
||||
} from "@vicons/material";
|
||||
import { musicStore, settingStore, siteStore } from "@/store";
|
||||
import { useRouter } from "vue-router";
|
||||
import MusicFrequency from "@/utils/MusicFrequency.js";
|
||||
import { setSeek } from "@/utils/Player";
|
||||
import PlayerRecord from "./PlayerRecord.vue";
|
||||
import PlayerCover from "./PlayerCover.vue";
|
||||
import RollingLyrics from "./RollingLyrics.vue";
|
||||
// import Spectrum from "./Spectrum.vue";
|
||||
import LyricSetting from "@/components/DataModal/LyricSetting.vue";
|
||||
import screenfull from "screenfull";
|
||||
|
||||
const router = useRouter();
|
||||
@@ -166,13 +164,13 @@ const setting = settingStore();
|
||||
// 工具栏显隐
|
||||
const menuShow = ref(false);
|
||||
|
||||
// 音乐频谱
|
||||
const avBars = ref(null);
|
||||
const musicFrequency = ref(null);
|
||||
// 歌词设置弹窗
|
||||
const LyricSettingRef = ref(null);
|
||||
|
||||
// 歌词文本点击事件
|
||||
const lrcTextClick = (time) => {
|
||||
if ($player) $player.currentTime = time;
|
||||
if (typeof $player !== "undefined") setSeek($player, time);
|
||||
music.setPlayState(true);
|
||||
lrcMouseStatus.value = false;
|
||||
};
|
||||
|
||||
@@ -238,29 +236,16 @@ const changePwaColor = () => {
|
||||
if (music.showBigPlayer) {
|
||||
themeColorMeta.setAttribute("content", site.songPicColor);
|
||||
} else {
|
||||
if (setting.getSiteTheme == "light") {
|
||||
if (setting.getSiteTheme === "light") {
|
||||
themeColorMeta.setAttribute("content", "#ffffff");
|
||||
} else if (setting.getSiteTheme == "dark") {
|
||||
} else if (setting.getSiteTheme === "dark") {
|
||||
themeColorMeta.setAttribute("content", "#18181c");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
if (setting.musicFrequency) {
|
||||
$player.crossOrigin = "anonymous";
|
||||
musicFrequency.value = new MusicFrequency(
|
||||
avBars.value,
|
||||
$player,
|
||||
null,
|
||||
50,
|
||||
null,
|
||||
null,
|
||||
5
|
||||
);
|
||||
musicFrequency.value.drawSpectrum();
|
||||
}
|
||||
nextTick().then(() => {
|
||||
// 滚动歌词
|
||||
lyricsScroll(music.getPlaySongLyricIndex);
|
||||
});
|
||||
@@ -277,9 +262,9 @@ watch(
|
||||
changePwaColor();
|
||||
if (val) {
|
||||
console.log("开启播放器", music.getPlaySongLyricIndex);
|
||||
nextTick(() => {
|
||||
lyricsScroll(music.getPlaySongLyricIndex);
|
||||
nextTick().then(() => {
|
||||
music.showPlayList = false;
|
||||
lyricsScroll(music.getPlaySongLyricIndex);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -417,6 +402,12 @@ watch(
|
||||
.left {
|
||||
padding-right: 0;
|
||||
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) {
|
||||
.left {
|
||||
|
||||
@@ -11,6 +11,16 @@
|
||||
"
|
||||
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 class="control">
|
||||
<div class="data">
|
||||
@@ -61,11 +71,14 @@
|
||||
</div>
|
||||
<div class="time">
|
||||
<span>{{ music.getPlaySongTime.songTimePlayed }}</span>
|
||||
<n-slider
|
||||
v-model:value="music.getPlaySongTime.barMoveDistance"
|
||||
class="progress"
|
||||
:step="0.01"
|
||||
@update:value="songTimeSliderUpdate"
|
||||
<vue-slider
|
||||
v-model="music.getPlaySongTime.barMoveDistance"
|
||||
@drag-start="music.setPlayState(false)"
|
||||
@drag-end="sliderDragEnd"
|
||||
@click.stop="
|
||||
songTimeSliderUpdate(music.getPlaySongTime.barMoveDistance)
|
||||
"
|
||||
:tooltip="'none'"
|
||||
/>
|
||||
<span>{{ music.getPlaySongTime.songTimeDuration }}</span>
|
||||
</div>
|
||||
@@ -98,6 +111,7 @@
|
||||
v-else
|
||||
class="dislike"
|
||||
size="20"
|
||||
:style="!user.userLogin ? 'opacity: 0.2;pointer-events: none;' : null"
|
||||
:component="ThumbDownRound"
|
||||
@click="music.setFmDislike(music.getPersonalFmData.id)"
|
||||
/>
|
||||
@@ -143,16 +157,25 @@ import {
|
||||
import { PlayCycle, PlayOnce, ShuffleOne } from "@icon-park/vue-next";
|
||||
import { musicStore, userStore } from "@/store";
|
||||
import { useRouter } from "vue-router";
|
||||
import { setSeek } from "@/utils/Player";
|
||||
import AllArtists from "@/components/DataList/AllArtists.vue";
|
||||
import VueSlider from "vue-slider-component";
|
||||
import "vue-slider-component/theme/default.css";
|
||||
|
||||
const router = useRouter();
|
||||
const music = musicStore();
|
||||
const user = userStore();
|
||||
|
||||
// 歌曲进度条更新
|
||||
const sliderDragEnd = () => {
|
||||
songTimeSliderUpdate(music.getPlaySongTime.barMoveDistance);
|
||||
music.setPlayState(true);
|
||||
};
|
||||
const songTimeSliderUpdate = (val) => {
|
||||
if ($player && music.getPlaySongTime && music.getPlaySongTime.duration)
|
||||
$player.currentTime = (music.getPlaySongTime.duration / 100) * val;
|
||||
if (typeof $player !== "undefined" && music.getPlaySongTime?.duration) {
|
||||
const currentTime = (music.getPlaySongTime.duration / 100) * val;
|
||||
setSeek($player, currentTime);
|
||||
}
|
||||
};
|
||||
|
||||
// 页面跳转
|
||||
@@ -168,11 +191,11 @@ const routerJump = (url, query) => {
|
||||
<style lang="scss" scoped>
|
||||
.cover {
|
||||
.pic {
|
||||
position: relative;
|
||||
width: 50vh;
|
||||
height: 50vh;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 40px 14px rgb(0 0 0 / 20%);
|
||||
z-index: 1;
|
||||
// overflow: hidden;
|
||||
@media (max-width: 1200px) {
|
||||
width: 44vh;
|
||||
height: 44vh;
|
||||
@@ -184,6 +207,19 @@ const routerJump = (url, query) => {
|
||||
.album {
|
||||
width: 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 {
|
||||
@@ -268,13 +304,26 @@ const routerJump = (url, query) => {
|
||||
span {
|
||||
opacity: 0.8;
|
||||
}
|
||||
.progress {
|
||||
margin: 0 12px;
|
||||
--n-handle-size: 12px;
|
||||
--n-fill-color: #fff;
|
||||
--n-fill-color-hover: #fff;
|
||||
--n-rail-color: #ffffff20;
|
||||
--n-rail-color-hover: #ffffff30;
|
||||
.vue-slider {
|
||||
margin: 0 10px;
|
||||
width: 100% !important;
|
||||
transform: translateY(-1px);
|
||||
cursor: pointer;
|
||||
:deep(.vue-slider-rail) {
|
||||
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 {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
:style="{ fontSize: setting.lyricsFontSize + 'vh' }"
|
||||
/>
|
||||
</div>
|
||||
<!-- 普通歌词 -->
|
||||
<template v-if="!music.getPlaySongLyric.hasYrc || !setting.showYrc">
|
||||
<div
|
||||
v-for="(item, index) in music.getPlaySongLyric.lrc"
|
||||
@@ -61,9 +62,7 @@
|
||||
>
|
||||
<span
|
||||
v-show="
|
||||
music.getPlaySongLyric.hasLrcRoma &&
|
||||
setting.showRoma &&
|
||||
item.roma
|
||||
music.getPlaySongLyric.hasLrcRoma && setting.showRoma && item.roma
|
||||
"
|
||||
:style="{ fontSize: setting.lyricsFontSize - 1.5 + 'vh' }"
|
||||
class="lyric-roma"
|
||||
@@ -73,6 +72,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 逐字歌词 -->
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="(item, index) in music.getPlaySongLyric.yrc"
|
||||
@@ -124,9 +124,7 @@
|
||||
</span>
|
||||
<span
|
||||
v-show="
|
||||
music.getPlaySongLyric.hasYrcRoma &&
|
||||
setting.showRoma &&
|
||||
item.roma
|
||||
music.getPlaySongLyric.hasYrcRoma && setting.showRoma && item.roma
|
||||
"
|
||||
:style="{ fontSize: setting.lyricsFontSize - 1.5 + 'vh' }"
|
||||
class="lyric-roma"
|
||||
@@ -252,7 +250,7 @@ const lrcTextClick = (time) => {
|
||||
}
|
||||
.lrc,
|
||||
.yrc {
|
||||
opacity: 0.2;
|
||||
opacity: 0.3;
|
||||
transition: all 0.3s;
|
||||
margin-bottom: 0.8vh;
|
||||
padding: 1.8vh 4vh 1.8vh 3vh;
|
||||
@@ -274,7 +272,7 @@ const lrcTextClick = (time) => {
|
||||
transition: all var(--dur);
|
||||
color: #ffffff66;
|
||||
&.fill {
|
||||
text-shadow: 0px 0px 30px #ffffff40;
|
||||
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%;
|
||||
@@ -302,7 +300,7 @@ const lrcTextClick = (time) => {
|
||||
.lrc-text {
|
||||
transform: scale(1.05);
|
||||
.lyric {
|
||||
text-shadow: 0px 0px 30px #ffffff40;
|
||||
text-shadow: 0 0 40px rgb(255 255 255 / 40%);
|
||||
}
|
||||
}
|
||||
.yrc-text {
|
||||
|
||||
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,220 +1,221 @@
|
||||
<template>
|
||||
<n-card
|
||||
:class="
|
||||
music.getPlaylists[0] && music.showPlayBar ? 'player show' : 'player'
|
||||
"
|
||||
content-style="padding: 0"
|
||||
>
|
||||
<div class="slider">
|
||||
<span>{{ music.getPlaySongTime.songTimePlayed }}</span>
|
||||
<n-slider
|
||||
v-model:value="music.getPlaySongTime.barMoveDistance"
|
||||
class="progress"
|
||||
:step="0.01"
|
||||
:tooltip="false"
|
||||
@update:value="songTimeSliderUpdate"
|
||||
@click.stop
|
||||
/>
|
||||
<span>{{ music.getPlaySongTime.songTimeDuration }}</span>
|
||||
</div>
|
||||
<div class="all">
|
||||
<div class="data">
|
||||
<div class="pic" @click.stop="music.setBigPlayerState(true)">
|
||||
<img
|
||||
:src="
|
||||
music.getPlaySongData
|
||||
? music.getPlaySongData.album.picUrl.replace(
|
||||
/^http:/,
|
||||
'https:'
|
||||
) + '?param=50y50'
|
||||
: '/images/pic/default.png'
|
||||
"
|
||||
alt="pic"
|
||||
/>
|
||||
<n-icon class="open" size="30" :component="KeyboardArrowUpFilled" />
|
||||
</div>
|
||||
<div class="name">
|
||||
<div
|
||||
class="song text-hidden"
|
||||
@click.stop="router.push(`/song?id=${music.getPlaySongData.id}`)"
|
||||
>
|
||||
{{
|
||||
music.getPlaySongData ? music.getPlaySongData.name : "暂无歌曲"
|
||||
}}
|
||||
<Transition name="show">
|
||||
<n-card
|
||||
v-show="music.getPlaylists[0] && music.showPlayBar"
|
||||
class="player"
|
||||
content-style="padding: 0"
|
||||
>
|
||||
<div class="slider">
|
||||
<span>{{ music.getPlaySongTime.songTimePlayed }}</span>
|
||||
<vue-slider
|
||||
v-model="music.getPlaySongTime.barMoveDistance"
|
||||
@drag-start="music.setPlayState(false)"
|
||||
@drag-end="sliderDragEnd"
|
||||
@click.stop="
|
||||
songTimeSliderUpdate(music.getPlaySongTime.barMoveDistance)
|
||||
"
|
||||
: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>
|
||||
</div>
|
||||
<div class="all">
|
||||
<div class="data">
|
||||
<div class="pic" @click.stop="music.setBigPlayerState(true)">
|
||||
<img
|
||||
:src="
|
||||
music.getPlaySongData
|
||||
? music.getPlaySongData.album.picUrl.replace(
|
||||
/^http:/,
|
||||
'https:'
|
||||
) + '?param=50y50'
|
||||
: '/images/pic/default.png'
|
||||
"
|
||||
alt="pic"
|
||||
/>
|
||||
<n-icon class="open" size="30" :component="KeyboardArrowUpFilled" />
|
||||
</div>
|
||||
<div class="artisrOrLrc" v-if="music.getPlaySongData">
|
||||
<template v-if="setting.bottomLyricShow">
|
||||
<Transition mode="out-in">
|
||||
<AllArtists
|
||||
v-if="!music.getPlayState || !music.getPlaySongLyric.lrc[0]"
|
||||
class="text-hidden"
|
||||
:artistsData="music.getPlaySongData.artist"
|
||||
/>
|
||||
<n-text
|
||||
v-else-if="
|
||||
setting.showYrc &&
|
||||
music.getPlaySongLyricIndex != -1 &&
|
||||
music.getPlaySongLyric.hasYrc
|
||||
"
|
||||
class="lrc text-hidden"
|
||||
>
|
||||
<div class="name">
|
||||
<div
|
||||
class="song text-hidden"
|
||||
@click.stop="router.push(`/song?id=${music.getPlaySongData.id}`)"
|
||||
>
|
||||
{{
|
||||
music.getPlaySongData ? music.getPlaySongData.name : "暂无歌曲"
|
||||
}}
|
||||
</div>
|
||||
<div class="artisrOrLrc" v-if="music.getPlaySongData">
|
||||
<template v-if="setting.bottomLyricShow">
|
||||
<Transition mode="out-in">
|
||||
<AllArtists
|
||||
v-if="!music.getPlayState || !music.getPlaySongLyric.lrc[0]"
|
||||
class="text-hidden"
|
||||
:artistsData="music.getPlaySongData.artist"
|
||||
/>
|
||||
<n-text
|
||||
v-for="item in music.getPlaySongLyric.yrc[
|
||||
music.getPlaySongLyricIndex
|
||||
].content"
|
||||
:key="item"
|
||||
:depth="3"
|
||||
v-else-if="
|
||||
setting.showYrc &&
|
||||
music.getPlaySongLyricIndex != -1 &&
|
||||
music.getPlaySongLyric.hasYrc
|
||||
"
|
||||
class="lrc text-hidden"
|
||||
>
|
||||
{{ item.content }}
|
||||
<n-text
|
||||
v-for="item in music.getPlaySongLyric.yrc[
|
||||
music.getPlaySongLyricIndex
|
||||
].content"
|
||||
:key="item"
|
||||
:depth="3"
|
||||
>
|
||||
{{ item.content }}
|
||||
</n-text>
|
||||
</n-text>
|
||||
</n-text>
|
||||
<n-text
|
||||
v-else-if="
|
||||
music.getPlaySongLyricIndex != -1 &&
|
||||
music.getPlaySongLyric.lrc[0]
|
||||
"
|
||||
class="lrc text-hidden"
|
||||
:depth="3"
|
||||
v-html="
|
||||
music.getPlaySongLyric.lrc[music.getPlaySongLyricIndex]
|
||||
.content
|
||||
"
|
||||
/>
|
||||
<n-text
|
||||
v-else-if="
|
||||
music.getPlaySongLyricIndex != -1 &&
|
||||
music.getPlaySongLyric.lrc[0]
|
||||
"
|
||||
class="lrc text-hidden"
|
||||
:depth="3"
|
||||
v-html="
|
||||
music.getPlaySongLyric.lrc[music.getPlaySongLyricIndex]
|
||||
.content
|
||||
"
|
||||
/>
|
||||
<AllArtists
|
||||
v-else
|
||||
class="text-hidden"
|
||||
:artistsData="music.getPlaySongData.artist"
|
||||
/>
|
||||
</Transition>
|
||||
</template>
|
||||
<template v-else>
|
||||
<AllArtists
|
||||
v-else
|
||||
class="text-hidden"
|
||||
:artistsData="music.getPlaySongData.artist"
|
||||
/>
|
||||
</Transition>
|
||||
</template>
|
||||
<template v-else>
|
||||
<AllArtists
|
||||
class="text-hidden"
|
||||
:artistsData="music.getPlaySongData.artist"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<n-icon
|
||||
v-if="!music.getPersonalFmMode"
|
||||
title="上一曲"
|
||||
class="prev"
|
||||
size="30"
|
||||
:component="SkipPreviousRound"
|
||||
@click.stop="music.setPlaySongIndex('prev')"
|
||||
/>
|
||||
<n-icon
|
||||
v-else
|
||||
class="dislike"
|
||||
size="20"
|
||||
:component="ThumbDownRound"
|
||||
@click="music.setFmDislike(music.getPersonalFmData.id)"
|
||||
/>
|
||||
<div class="play-state">
|
||||
<n-icon
|
||||
size="46"
|
||||
:title="music.getPlayState ? '暂停' : '播放'"
|
||||
:component="
|
||||
music.getPlayState ? PauseCircleFilled : PlayCircleFilled
|
||||
"
|
||||
@click.stop="music.setPlayState(!music.getPlayState)"
|
||||
/>
|
||||
</div>
|
||||
<n-icon
|
||||
class="next"
|
||||
size="30"
|
||||
:component="SkipNextRound"
|
||||
@click.stop="music.setPlaySongIndex('next')"
|
||||
/>
|
||||
</div>
|
||||
<div :class="music.getPersonalFmMode ? 'menu fm' : 'menu'">
|
||||
<div class="like" v-if="music.getPlaySongData">
|
||||
<n-icon
|
||||
class="like-icon"
|
||||
size="24"
|
||||
:component="
|
||||
music.getSongIsLike(music.getPlaySongData.id)
|
||||
? FavoriteRound
|
||||
: FavoriteBorderRound
|
||||
"
|
||||
@click.stop="
|
||||
music.getSongIsLike(music.getPlaySongData.id)
|
||||
? music.changeLikeList(music.getPlaySongData.id, false)
|
||||
: music.changeLikeList(music.getPlaySongData.id, true)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="add-playlist">
|
||||
<n-icon
|
||||
class="add-icon"
|
||||
size="30"
|
||||
:component="PlaylistAddRound"
|
||||
@click.stop="
|
||||
addPlayListRef.openAddToPlaylist(music.getPlaySongData.id)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="pattern">
|
||||
<n-icon
|
||||
:component="
|
||||
persistData.playSongMode === 'normal'
|
||||
? PlayCycle
|
||||
: persistData.playSongMode === 'random'
|
||||
? ShuffleOne
|
||||
: PlayOnce
|
||||
"
|
||||
@click="music.setPlaySongMode()"
|
||||
/>
|
||||
</div>
|
||||
<div :class="music.showPlayList ? 'playlist open' : 'playlist'">
|
||||
<n-icon
|
||||
size="30"
|
||||
:component="PlaylistPlayRound"
|
||||
@click.stop="music.showPlayList = !music.showPlayList"
|
||||
/>
|
||||
</div>
|
||||
<div class="volume">
|
||||
<n-icon
|
||||
size="28"
|
||||
:component="
|
||||
persistData.playVolume == 0
|
||||
? VolumeOffRound
|
||||
: persistData.playVolume < 0.4
|
||||
? VolumeMuteRound
|
||||
: persistData.playVolume < 0.7
|
||||
? VolumeDownRound
|
||||
: VolumeUpRound
|
||||
"
|
||||
@click.stop="volumeMute"
|
||||
/>
|
||||
<n-slider
|
||||
class="volmePg"
|
||||
v-model:value="persistData.playVolume"
|
||||
:tooltip="false"
|
||||
:min="0"
|
||||
:max="1"
|
||||
:step="0.01"
|
||||
@click.stop
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<n-icon
|
||||
v-if="!music.getPersonalFmMode"
|
||||
title="上一曲"
|
||||
class="prev"
|
||||
size="30"
|
||||
:component="SkipPreviousRound"
|
||||
@click.stop="music.setPlaySongIndex('prev')"
|
||||
/>
|
||||
<n-icon
|
||||
v-else
|
||||
class="dislike"
|
||||
size="20"
|
||||
:component="ThumbDownRound"
|
||||
@click="music.setFmDislike(music.getPersonalFmData.id)"
|
||||
/>
|
||||
<div class="play-state">
|
||||
<n-icon
|
||||
size="46"
|
||||
:title="music.getPlayState ? '暂停' : '播放'"
|
||||
:component="
|
||||
music.getPlayState ? PauseCircleFilled : PlayCircleFilled
|
||||
"
|
||||
@click.stop="music.setPlayState(!music.getPlayState)"
|
||||
/>
|
||||
</div>
|
||||
<n-icon
|
||||
class="next"
|
||||
size="30"
|
||||
:component="SkipNextRound"
|
||||
@click.stop="music.setPlaySongIndex('next')"
|
||||
/>
|
||||
</div>
|
||||
<div :class="music.getPersonalFmMode ? 'menu fm' : 'menu'">
|
||||
<div class="like" v-if="music.getPlaySongData">
|
||||
<n-icon
|
||||
class="like-icon"
|
||||
size="24"
|
||||
:component="
|
||||
music.getSongIsLike(music.getPlaySongData.id)
|
||||
? FavoriteRound
|
||||
: FavoriteBorderRound
|
||||
"
|
||||
@click.stop="
|
||||
music.getSongIsLike(music.getPlaySongData.id)
|
||||
? music.changeLikeList(music.getPlaySongData.id, false)
|
||||
: music.changeLikeList(music.getPlaySongData.id, true)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="add-playlist">
|
||||
<n-icon
|
||||
class="add-icon"
|
||||
size="30"
|
||||
:component="PlaylistAddRound"
|
||||
@click.stop="
|
||||
addPlayListRef.openAddToPlaylist(music.getPlaySongData.id)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="pattern">
|
||||
<n-icon
|
||||
:component="
|
||||
persistData.playSongMode === 'normal'
|
||||
? PlayCycle
|
||||
: persistData.playSongMode === 'random'
|
||||
? ShuffleOne
|
||||
: PlayOnce
|
||||
"
|
||||
@click="music.setPlaySongMode()"
|
||||
/>
|
||||
</div>
|
||||
<div :class="music.showPlayList ? 'playlist open' : 'playlist'">
|
||||
<n-icon
|
||||
size="30"
|
||||
:component="PlaylistPlayRound"
|
||||
@click.stop="music.showPlayList = !music.showPlayList"
|
||||
/>
|
||||
</div>
|
||||
<div class="volume">
|
||||
<n-icon
|
||||
size="28"
|
||||
:component="
|
||||
persistData.playVolume == 0
|
||||
? VolumeOffRound
|
||||
: persistData.playVolume < 0.4
|
||||
? VolumeMuteRound
|
||||
: persistData.playVolume < 0.7
|
||||
? VolumeDownRound
|
||||
: VolumeUpRound
|
||||
"
|
||||
@click.stop="volumeMute"
|
||||
/>
|
||||
<n-slider
|
||||
class="volmePg"
|
||||
v-model:value="persistData.playVolume"
|
||||
:tooltip="false"
|
||||
:min="0"
|
||||
:max="1"
|
||||
:step="0.01"
|
||||
@click.stop
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 全局播放器 -->
|
||||
<audio
|
||||
ref="player"
|
||||
:autoplay="music.getPlayState"
|
||||
@timeupdate="songUpdate"
|
||||
@play="songPlay"
|
||||
@pause="songPause"
|
||||
@canplay="songCanplay"
|
||||
@loadeddata="songReady"
|
||||
@error="songError"
|
||||
@ended="music.setPlaySongIndex('next')"
|
||||
:src="music.getPlaySongLink"
|
||||
></audio>
|
||||
</n-card>
|
||||
</n-card>
|
||||
</Transition>
|
||||
<!-- 播放列表 -->
|
||||
<PlayListDrawer ref="PlayListDrawerRef" />
|
||||
<!-- 添加到歌单 -->
|
||||
@@ -229,12 +230,10 @@ import {
|
||||
getMusicUrl,
|
||||
getMusicNumUrl,
|
||||
getMusicNewLyric,
|
||||
songScrobble,
|
||||
} from "@/api/song";
|
||||
import { NIcon } from "naive-ui";
|
||||
import {
|
||||
KeyboardArrowUpFilled,
|
||||
MusicNoteFilled,
|
||||
PlayCircleFilled,
|
||||
PauseCircleFilled,
|
||||
SkipNextRound,
|
||||
@@ -251,27 +250,32 @@ import {
|
||||
} from "@vicons/material";
|
||||
import { PlayCycle, PlayOnce, ShuffleOne } from "@icon-park/vue-next";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { musicStore, settingStore, userStore, siteStore } 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 { debounce } from "throttle-debounce";
|
||||
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 ColorThief from "colorthief";
|
||||
import BigPlayer from "./BigPlayer.vue";
|
||||
import debounce from "@/utils/debounce";
|
||||
import "vue-slider-component/theme/default.css";
|
||||
|
||||
const router = useRouter();
|
||||
const setting = settingStore();
|
||||
const music = musicStore();
|
||||
const user = userStore();
|
||||
const site = siteStore();
|
||||
const { persistData } = storeToRefs(music);
|
||||
const addPlayListRef = ref(null);
|
||||
const PlayListDrawerRef = ref(null);
|
||||
|
||||
// 重试次数
|
||||
const testNumber = ref(0);
|
||||
|
||||
// UNM 是否存在
|
||||
const useUnmServerHas = import.meta.env.VITE_UNM_API ? true : false;
|
||||
|
||||
@@ -301,7 +305,9 @@ const getPlaySongData = (data, level = setting.songLevel) => {
|
||||
$message.info("当前歌曲为 VIP 专享,仅可试听");
|
||||
// 获取音乐地址
|
||||
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 {
|
||||
if (useUnmServerHas && setting.useUnmServer) {
|
||||
@@ -318,7 +324,7 @@ const getPlaySongData = (data, level = setting.songLevel) => {
|
||||
music.setPlaySongLyric(res);
|
||||
});
|
||||
} catch (err) {
|
||||
if (music.getPlaylists[0] && music.getPlayState && $player) {
|
||||
if (music.getPlaylists[0] && music.getPlayState) {
|
||||
console.log("当前歌曲所有音源匹配失败:" + err);
|
||||
$message.warning("当前歌曲所有音源匹配失败,跳至下一首");
|
||||
music.setPlaySongIndex("next");
|
||||
@@ -332,7 +338,7 @@ const getMusicNumUrlData = (id) => {
|
||||
.then((res) => {
|
||||
if (res.code === 200) {
|
||||
console.log("替换成功:" + res.data.url.replace(/^http:/, ""));
|
||||
music.setPlaySongLink(res.data.url.replace(/^http:/, ""));
|
||||
player.value = createSound(res.data.url.replace(/^http:/, ""));
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -342,171 +348,16 @@ const getMusicNumUrlData = (id) => {
|
||||
});
|
||||
};
|
||||
|
||||
// 歌曲进度更新事件
|
||||
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 songReady = () => {
|
||||
const songId = music.getPlaySongData?.id;
|
||||
const sourceId = music.getPlaySongData?.sourceId
|
||||
? music.getPlaySongData.sourceId
|
||||
: 0;
|
||||
console.log("首次缓冲完成:" + songId + " / 来源:" + sourceId);
|
||||
// 听歌打卡
|
||||
if (user.userLogin) {
|
||||
songScrobble(songId, sourceId).catch((err) => {
|
||||
console.error("歌曲打卡失败:" + err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 歌曲开始播放
|
||||
const songPlay = () => {
|
||||
testNumber.value = 0;
|
||||
if (!Object.keys(music.getPlaySongData).length) {
|
||||
$message.error("音乐数据获取失败");
|
||||
return false;
|
||||
}
|
||||
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);
|
||||
// mediaSession
|
||||
if (
|
||||
"mediaSession" in navigator &&
|
||||
Object.keys(music.getPlaySongData).length
|
||||
) {
|
||||
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=512x512",
|
||||
sizes: "512x512",
|
||||
},
|
||||
],
|
||||
});
|
||||
navigator.mediaSession.setActionHandler("nexttrack", () => {
|
||||
music.setPlaySongIndex("next");
|
||||
});
|
||||
navigator.mediaSession.setActionHandler("previoustrack", () => {
|
||||
music.setPlaySongIndex("prev");
|
||||
});
|
||||
}
|
||||
// 写入播放历史
|
||||
music.setPlayHistory(music.getPlaySongData);
|
||||
// 播放时页面标题
|
||||
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);
|
||||
// 更改页面标题
|
||||
$setSiteTitle();
|
||||
};
|
||||
|
||||
// 歌曲进度条更新
|
||||
const songTimeSliderUpdate = (val) => {
|
||||
if ($player && music.getPlaySongTime && music.getPlaySongTime.duration)
|
||||
$player.currentTime = (music.getPlaySongTime.duration / 100) * val;
|
||||
const sliderDragEnd = () => {
|
||||
songTimeSliderUpdate(music.getPlaySongTime.barMoveDistance);
|
||||
music.setPlayState(true);
|
||||
};
|
||||
|
||||
// 歌曲播放失败事件
|
||||
const songError = () => {
|
||||
console.error("歌曲播放失败");
|
||||
$message.error("歌曲播放失败");
|
||||
if (testNumber.value < 4) {
|
||||
if (music.getPlaylists[0]) getPlaySongData(music.getPlaySongData);
|
||||
testNumber.value++;
|
||||
} else {
|
||||
$message.error("歌曲重试次数过多,请刷新后重试");
|
||||
const songTimeSliderUpdate = (val) => {
|
||||
if (player.value && music.getPlaySongTime?.duration) {
|
||||
const currentTime = (music.getPlaySongTime.duration / 100) * val;
|
||||
setSeek(player.value, currentTime);
|
||||
}
|
||||
if (music.getPlayState) songInOrOut("play");
|
||||
};
|
||||
|
||||
// 静音事件
|
||||
@@ -519,8 +370,20 @@ const volumeMute = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 歌曲更换事件
|
||||
const songChange = debounce(500, (val) => {
|
||||
if (val === undefined) {
|
||||
window.document.title =
|
||||
sessionStorage.getItem("siteTitle") ?? import.meta.env.VITE_SITE_TITLE;
|
||||
}
|
||||
// 加载数据
|
||||
getPlaySongData(val);
|
||||
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)
|
||||
@@ -541,29 +404,21 @@ const getPicColor = (url) => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 挂载方法
|
||||
window.$getPlaySongData = getPlaySongData;
|
||||
// 获取音乐数据
|
||||
if (music.getPlaylists[0] && music.getPlaySongData) {
|
||||
getPlaySongData(music.getPlaySongData);
|
||||
getPicColor(music.getPlaySongData.album.picUrl);
|
||||
}
|
||||
// 挂载播放器
|
||||
window.$player = player.value;
|
||||
// 恢复上次播放进度
|
||||
if (music.getPlaySongTime && music.getPlaySongTime.currentTime) {
|
||||
$player.currentTime = music.getPlaySongTime.currentTime;
|
||||
}
|
||||
// 设置音量
|
||||
if ($player) $player.volume = persistData.value.playVolume;
|
||||
});
|
||||
|
||||
// 监听当前音乐数据变化
|
||||
watch(
|
||||
() => music.getPlaySongData,
|
||||
(val) => {
|
||||
debounce(() => {
|
||||
getPlaySongData(val);
|
||||
getPicColor(val?.album.picUrl);
|
||||
}, 500);
|
||||
music.setPlaySongTime({ currentTime: 0, duration: 0 });
|
||||
songChange(val);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -571,7 +426,7 @@ watch(
|
||||
watch(
|
||||
() => persistData.value.playVolume,
|
||||
(val) => {
|
||||
if ($player) $player.volume = val;
|
||||
if (player.value) setVolume(player.value, val);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -579,11 +434,13 @@ watch(
|
||||
watch(
|
||||
() => music.getPlayState,
|
||||
(val) => {
|
||||
nextTick(() => {
|
||||
if ($player) {
|
||||
val ? songInOrOut("play") : songInOrOut("pause");
|
||||
} else {
|
||||
$message.error("播放器初始化失败,请重试");
|
||||
nextTick().then(() => {
|
||||
if (player.value && !music.isLoadingSong) {
|
||||
fadePlayOrPause(
|
||||
player.value,
|
||||
val ? "play" : "pause",
|
||||
persistData.value.playVolume
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -591,16 +448,21 @@ watch(
|
||||
</script>
|
||||
|
||||
<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);
|
||||
}
|
||||
.player {
|
||||
height: 70px;
|
||||
position: fixed;
|
||||
bottom: -90px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
transition: all 0.3s;
|
||||
z-index: 2004;
|
||||
&.show {
|
||||
bottom: 0;
|
||||
}
|
||||
z-index: 2;
|
||||
.slider {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
@@ -608,19 +470,15 @@ watch(
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@media (max-width: 640px) {
|
||||
top: -6px;
|
||||
top: -8px;
|
||||
> {
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.progress {
|
||||
--n-handle-size: 12px;
|
||||
--n-rail-height: 3px;
|
||||
}
|
||||
> {
|
||||
span {
|
||||
font-size: 12px;
|
||||
@@ -632,6 +490,33 @@ watch(
|
||||
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 {
|
||||
@@ -777,9 +662,11 @@ watch(
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
background-color: var(--main-color);
|
||||
color: var(--n-color-embedded);
|
||||
@media (min-width: 640px) {
|
||||
&:hover {
|
||||
background-color: var(--main-color);
|
||||
color: var(--n-color-embedded);
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
|
||||
@@ -366,6 +366,12 @@ watch(
|
||||
@media (max-width: 450px) {
|
||||
width: 60vw;
|
||||
}
|
||||
@media (max-width: 380px) {
|
||||
width: 54vw;
|
||||
}
|
||||
@media (max-width: 320px) {
|
||||
width: 50vw;
|
||||
}
|
||||
}
|
||||
:deep(.n-input__prefix) {
|
||||
.n-icon {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
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 { getLikelist, setLikeSong } from "@/api/user";
|
||||
import { getPlayListCatlist } from "@/api/playlist";
|
||||
import { userStore, settingStore } from "@/store";
|
||||
import { NIcon } from "naive-ui";
|
||||
import { PlayCycle, PlayOnce, ShuffleOne } from "@icon-park/vue-next";
|
||||
import { soundStop, fadePlayOrPause } from "@/utils/Player";
|
||||
import parseLyric from "@/utils/parseLyric";
|
||||
|
||||
const useMusicDataStore = defineStore("musicData", {
|
||||
@@ -20,7 +22,7 @@ const useMusicDataStore = defineStore("musicData", {
|
||||
// 播放状态
|
||||
playState: false,
|
||||
// 当前歌曲播放链接
|
||||
playSongLink: null,
|
||||
// playSongLink: null,
|
||||
// 当前歌曲歌词数据
|
||||
playSongLyric: {
|
||||
lrc: [],
|
||||
@@ -37,6 +39,15 @@ const useMusicDataStore = defineStore("musicData", {
|
||||
catList: {},
|
||||
// 精品歌单分类
|
||||
highqualityCatList: [],
|
||||
// 音乐频谱数据
|
||||
spectrumsData: {
|
||||
data: [],
|
||||
audio: null,
|
||||
analyser: null,
|
||||
audioCtx: null,
|
||||
},
|
||||
// 是否正在加载数据
|
||||
isLoadingSong: false,
|
||||
// 持久化数据
|
||||
persistData: {
|
||||
// 搜索历史
|
||||
@@ -116,10 +127,6 @@ const useMusicDataStore = defineStore("musicData", {
|
||||
getPlayState(state) {
|
||||
return state.playState;
|
||||
},
|
||||
// 获取播放链接
|
||||
getPlaySongLink(state) {
|
||||
return state.playSongLink;
|
||||
},
|
||||
// 获取喜欢音乐列表
|
||||
getLikeList(state) {
|
||||
return state.persistData.likeList;
|
||||
@@ -142,8 +149,8 @@ const useMusicDataStore = defineStore("musicData", {
|
||||
setPersonalFmMode(value) {
|
||||
this.persistData.personalFmMode = value;
|
||||
if (value) {
|
||||
this.playSongLink = null;
|
||||
if (this.persistData.personalFmData.id) {
|
||||
soundStop($player);
|
||||
if (this.persistData.personalFmData?.id) {
|
||||
this.persistData.playlists = [];
|
||||
this.persistData.playlists.push(this.persistData.personalFmData);
|
||||
this.persistData.playSongIndex = 0;
|
||||
@@ -175,7 +182,7 @@ const useMusicDataStore = defineStore("musicData", {
|
||||
} else {
|
||||
this.persistData.personalFmData = fmData;
|
||||
if (this.persistData.personalFmMode) {
|
||||
this.playSongLink = null;
|
||||
soundStop($player);
|
||||
this.persistData.playlists = [];
|
||||
this.persistData.playlists.push(fmData);
|
||||
this.persistData.playSongIndex = 0;
|
||||
@@ -270,10 +277,6 @@ const useMusicDataStore = defineStore("musicData", {
|
||||
setPlayBarState(value) {
|
||||
this.showPlayBar = value;
|
||||
},
|
||||
// 更改歌曲播放链接
|
||||
setPlaySongLink(value) {
|
||||
this.playSongLink = value;
|
||||
},
|
||||
// 更改播放列表模式
|
||||
setPlayListMode(value) {
|
||||
this.persistData.playListMode = value;
|
||||
@@ -323,9 +326,13 @@ const useMusicDataStore = defineStore("musicData", {
|
||||
this.persistData.playSongTime.currentTime = value.currentTime;
|
||||
this.persistData.playSongTime.duration = value.duration;
|
||||
// 计算进度条应该移动的距离
|
||||
this.persistData.playSongTime.barMoveDistance = Number(
|
||||
(value.currentTime / (value.duration / 100)).toFixed(2)
|
||||
);
|
||||
if (value.duration === 0) {
|
||||
this.persistData.playSongTime.barMoveDistance = 0;
|
||||
} else {
|
||||
this.persistData.playSongTime.barMoveDistance = Number(
|
||||
(value.currentTime / (value.duration / 100)).toFixed(2)
|
||||
);
|
||||
}
|
||||
if (this.persistData.playSongTime.barMoveDistance) {
|
||||
// 歌曲播放进度转换
|
||||
this.persistData.playSongTime.songTimePlayed = getSongPlayingTime(
|
||||
@@ -363,7 +370,9 @@ const useMusicDataStore = defineStore("musicData", {
|
||||
},
|
||||
// 上下曲调整
|
||||
setPlaySongIndex(type) {
|
||||
this.playState = false;
|
||||
// this.playState = false;
|
||||
soundStop($player);
|
||||
this.isLoadingSong = true;
|
||||
if (this.persistData.personalFmMode) {
|
||||
this.setPersonalFmData();
|
||||
} else {
|
||||
@@ -376,8 +385,9 @@ const useMusicDataStore = defineStore("musicData", {
|
||||
this.persistData.playSongIndex = Math.floor(
|
||||
Math.random() * listLength
|
||||
);
|
||||
} else if (listMode === "single" && $player) {
|
||||
$player.currentTime = 0;
|
||||
} else if (listMode === "single" && typeof $player !== "undefined") {
|
||||
soundStop($player);
|
||||
fadePlayOrPause($player, "play", this.persistData.playVolume);
|
||||
} else {
|
||||
$message.error("播放出错,请刷新后重试");
|
||||
}
|
||||
@@ -386,12 +396,15 @@ const useMusicDataStore = defineStore("musicData", {
|
||||
this.persistData.playSongIndex = listLength - 1;
|
||||
} else if (this.persistData.playSongIndex >= listLength) {
|
||||
this.persistData.playSongIndex = 0;
|
||||
$player.currentTime = 0;
|
||||
soundStop($player);
|
||||
fadePlayOrPause($player, "play", this.persistData.playVolume);
|
||||
}
|
||||
if (listMode !== "single" && listLength > 1) {
|
||||
this.playSongLink = null;
|
||||
soundStop($player);
|
||||
}
|
||||
this.playState = true;
|
||||
nextTick().then(() => {
|
||||
this.setPlayState(true);
|
||||
});
|
||||
}
|
||||
},
|
||||
// 添加歌曲至播放列表
|
||||
@@ -406,7 +419,8 @@ const useMusicDataStore = defineStore("musicData", {
|
||||
this.persistData.playlists[this.persistData.playSongIndex]?.id
|
||||
) {
|
||||
console.log("播放歌曲与上一次不一致");
|
||||
this.playSongLink = null;
|
||||
soundStop($player);
|
||||
this.isLoadingSong = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("出现错误:" + error);
|
||||
@@ -417,7 +431,7 @@ const useMusicDataStore = defineStore("musicData", {
|
||||
this.persistData.playlists.push(value);
|
||||
this.persistData.playSongIndex = this.persistData.playlists.length - 1;
|
||||
}
|
||||
play ? (this.playState = true) : null;
|
||||
play ? this.setPlayState(true) : null;
|
||||
},
|
||||
// 在当前播放歌曲后添加
|
||||
addSongToNext(value) {
|
||||
@@ -450,15 +464,15 @@ const useMusicDataStore = defineStore("musicData", {
|
||||
if (index < this.persistData.playSongIndex) {
|
||||
this.persistData.playSongIndex--;
|
||||
} else if (index === this.persistData.playSongIndex) {
|
||||
// 如果删除的是当前播放歌曲,则将播放链接置为null
|
||||
this.playSongLink = null;
|
||||
// 如果删除的是当前播放歌曲,则重置播放器
|
||||
soundStop($player);
|
||||
}
|
||||
$message.success(name + " 已从播放列表中移除");
|
||||
this.persistData.playlists.splice(index, 1);
|
||||
// 检查当前播放歌曲的索引是否超出了列表范围
|
||||
if (this.persistData.playSongIndex >= this.persistData.playlists.length) {
|
||||
this.persistData.playSongIndex = 0;
|
||||
this.playSongLink = null;
|
||||
soundStop($player);
|
||||
}
|
||||
},
|
||||
// 获取歌单分类
|
||||
|
||||
@@ -48,6 +48,12 @@ const useSettingDataStore = defineStore("settingData", {
|
||||
backgroundImageShow: "blur",
|
||||
// 是否显示前奏等待
|
||||
countDownShow: true,
|
||||
// 是否显示歌词设置
|
||||
showLyricSetting: true,
|
||||
// 歌曲渐入渐出
|
||||
songVolumeFade: true,
|
||||
// 列表默认数量
|
||||
listNumber: 30,
|
||||
};
|
||||
},
|
||||
getters: {
|
||||
|
||||
@@ -4,7 +4,7 @@ const useSiteDataStore = defineStore("siteData", {
|
||||
state: () => {
|
||||
return {
|
||||
// 站点标题
|
||||
siteTitle: "SPlayer",
|
||||
siteTitle: import.meta.env.VITE_SITE_TITLE,
|
||||
// 封面主题色
|
||||
songPicColor: "rgb(128,128,128)",
|
||||
// 搜索框激活状态
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
getUserArtistlist,
|
||||
getUserAlbum,
|
||||
} from "@/api/user";
|
||||
import { formatNumber, getLongTime } from "@/utils/timeTools.js";
|
||||
import { formatNumber, getLongTime } from "@/utils/timeTools";
|
||||
|
||||
const useUserDataStore = defineStore("userData", {
|
||||
state: () => {
|
||||
@@ -105,7 +105,7 @@ const useUserDataStore = defineStore("userData", {
|
||||
userLogOut();
|
||||
},
|
||||
// 更改用户歌单
|
||||
async setUserPlayLists() {
|
||||
async setUserPlayLists(callback) {
|
||||
if (this.userLogin) {
|
||||
try {
|
||||
if (!Object.keys(this.userOtherData).length) {
|
||||
@@ -145,6 +145,9 @@ const useUserDataStore = defineStore("userData", {
|
||||
});
|
||||
}
|
||||
});
|
||||
if (typeof callback === "function") {
|
||||
callback();
|
||||
}
|
||||
this.userPlayLists.isLoading = false;
|
||||
} else {
|
||||
this.userPlayLists.isLoading = false;
|
||||
@@ -195,7 +198,7 @@ const useUserDataStore = defineStore("userData", {
|
||||
}
|
||||
},
|
||||
// 更改用户收藏专辑
|
||||
async setUserAlbumLists() {
|
||||
async setUserAlbumLists(callback) {
|
||||
if (this.userLogin) {
|
||||
try {
|
||||
let offset = 0;
|
||||
@@ -217,6 +220,9 @@ const useUserDataStore = defineStore("userData", {
|
||||
offset += 30;
|
||||
console.log(totalCount, offset, this.userAlbum.list);
|
||||
}
|
||||
if (typeof callback === "function") {
|
||||
callback();
|
||||
}
|
||||
this.userAlbum.isLoading = false;
|
||||
this.userAlbum.has = true;
|
||||
} catch (err) {
|
||||
|
||||
@@ -60,7 +60,7 @@ body,
|
||||
}
|
||||
}
|
||||
.n-modal-container {
|
||||
z-index: 2006 !important;
|
||||
// z-index: 2006 !important;
|
||||
.n-modal-body-wrapper {
|
||||
.n-modal-mask {
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
|
||||
@@ -77,7 +77,15 @@ class MusicFrequency {
|
||||
this.source.connect(this.analyser);
|
||||
this.analyser.connect(this.context.destination);
|
||||
}
|
||||
|
||||
// 断开音频元素和分析器之间的连接,释放音频上下文
|
||||
disconnect() {
|
||||
// 断开连接
|
||||
this.source.disconnect();
|
||||
this.analyser.disconnect();
|
||||
// 关闭音频上下文
|
||||
this.context.close();
|
||||
}
|
||||
// 绘制频谱
|
||||
drawSpectrum() {
|
||||
// 获取频域数据
|
||||
this.analyser.getByteFrequencyData(this.output);
|
||||
|
||||
294
src/utils/Player.js
Normal file
294
src/utils/Player.js
Normal file
@@ -0,0 +1,294 @@
|
||||
import { Howl, Howler } from "howler";
|
||||
import { songScrobble } from "@/api/song";
|
||||
import { musicStore } from "@/store";
|
||||
import { NIcon } from "naive-ui";
|
||||
import { MusicNoteFilled } from "@vicons/material";
|
||||
|
||||
// 歌曲信息更新定时器
|
||||
let timeupdateInterval = 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 isLogin = JSON.parse(localStorage.getItem("userData")).userLogin;
|
||||
console.log("首次缓冲完成:" + songId + " / 来源:" + sourceId);
|
||||
sound?.seek(music.persistData.playSongTime.currentTime);
|
||||
// 取消加载状态
|
||||
music.isLoadingSong = false;
|
||||
// 听歌打卡
|
||||
if (isLogin) {
|
||||
songScrobble(songId, sourceId).catch((err) => {
|
||||
console.error("歌曲打卡失败:" + err);
|
||||
});
|
||||
}
|
||||
});
|
||||
// 播放事件
|
||||
sound?.on("play", () => {
|
||||
if (!Object.keys(music.getPlaySongData).length) {
|
||||
$message.error("音乐数据获取失败");
|
||||
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("歌曲播放失败");
|
||||
console.error("歌曲播放失败");
|
||||
music.setPlayState(false);
|
||||
}
|
||||
if (testNumber < 4) {
|
||||
if (music.getPlaylists[0]) $getPlaySongData(music.getPlaySongData);
|
||||
testNumber++;
|
||||
} else {
|
||||
$message.error("歌曲重试次数过多,请刷新后重试", {
|
||||
closable: true,
|
||||
duration: 0,
|
||||
});
|
||||
}
|
||||
});
|
||||
sound?.on("playerror", () => {
|
||||
$message.error("歌曲播放出错");
|
||||
console.error("歌曲播放出错");
|
||||
music.setPlayState(false);
|
||||
});
|
||||
// 生成频谱
|
||||
// createSpectrums(sound, music);
|
||||
// 返回音频对象
|
||||
return (window.$player = sound);
|
||||
} catch (err) {
|
||||
$message.error("音乐播放器初始化失败");
|
||||
console.error("音乐播放器初始化失败:" + 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);
|
||||
});
|
||||
};
|
||||
@@ -41,7 +41,7 @@ axios.interceptors.response.use(
|
||||
const data = error.response.data;
|
||||
switch (error.response.status) {
|
||||
case 401:
|
||||
console.error("您未登录");
|
||||
console.error("无权限访问");
|
||||
break;
|
||||
case 301:
|
||||
console.error("请求发生重定向");
|
||||
|
||||
@@ -14,53 +14,85 @@
|
||||
/>
|
||||
<img src="/images/pic/album.png" class="album" alt="album" />
|
||||
</div>
|
||||
<div class="intr">
|
||||
<span class="name">歌单简介</span>
|
||||
<span class="desc text-hidden">
|
||||
{{ albumDetail.description }}
|
||||
</span>
|
||||
<n-button
|
||||
block
|
||||
strong
|
||||
secondary
|
||||
v-if="albumDetail?.description.length > 70"
|
||||
@click="albumDescShow = true"
|
||||
>
|
||||
全部简介
|
||||
</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 class="tag" v-if="albumDetail.tags">
|
||||
<n-tag
|
||||
class="tags"
|
||||
round
|
||||
:bordered="false"
|
||||
v-for="item in albumDetail.tags"
|
||||
:key="item"
|
||||
>
|
||||
{{ item }}
|
||||
</n-tag>
|
||||
<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">
|
||||
<span class="name">专辑简介</span>
|
||||
<span class="desc text-hidden">
|
||||
{{ albumDetail.description }}
|
||||
</span>
|
||||
<n-button
|
||||
class="all-desc"
|
||||
block
|
||||
strong
|
||||
secondary
|
||||
v-if="albumDetail?.description.length > 70"
|
||||
@click="albumDescShow = true"
|
||||
>
|
||||
全部简介
|
||||
</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>
|
||||
<n-space class="tag" v-if="albumDetail.tags">
|
||||
<n-tag
|
||||
class="tags"
|
||||
round
|
||||
:bordered="false"
|
||||
v-for="item in albumDetail.tags"
|
||||
:key="item"
|
||||
>
|
||||
{{ item }}
|
||||
</n-tag>
|
||||
</n-space>
|
||||
<n-space class="control">
|
||||
<n-button strong secondary round type="primary" @click="playAllSong">
|
||||
<template #icon>
|
||||
<n-icon :component="MusicList" />
|
||||
</template>
|
||||
播放
|
||||
</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 class="right">
|
||||
<div class="meta">
|
||||
<span class="name">{{ albumDetail.name }}</span>
|
||||
<span
|
||||
<n-text class="name">{{ albumDetail.name }}</n-text>
|
||||
<n-text
|
||||
class="creator"
|
||||
@click="router.push(`/artist/songs?id=${albumDetail.artist.id}`)"
|
||||
>
|
||||
{{ albumDetail.artist.name }}
|
||||
</span>
|
||||
</n-text>
|
||||
<div class="time">
|
||||
<div class="createTime">
|
||||
<span class="num">发行时间:</span>
|
||||
@@ -96,18 +128,88 @@
|
||||
</template>
|
||||
|
||||
<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 { getSongTime, getLongTime } from "@/utils/timeTools.js";
|
||||
import { getSongTime, getLongTime } from "@/utils/timeTools";
|
||||
import { MusicList, LinkTwo, More, Like, Unlike } from "@icon-park/vue-next";
|
||||
import { userStore, musicStore, settingStore } from "@/store";
|
||||
import DataLists from "@/components/DataList/DataLists.vue";
|
||||
const router = useRouter();
|
||||
|
||||
// 歌单数据
|
||||
const router = useRouter();
|
||||
const user = userStore();
|
||||
const music = musicStore();
|
||||
const setting = settingStore();
|
||||
|
||||
// 专辑数据
|
||||
const albumId = ref(router.currentRoute.value.query.id);
|
||||
const albumDetail = ref(null);
|
||||
const albumData = ref([]);
|
||||
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: "复制专辑链接",
|
||||
props: {
|
||||
onClick: () => {
|
||||
if (navigator.clipboard) {
|
||||
try {
|
||||
navigator.clipboard.writeText(
|
||||
`https://music.163.com/#/playlist?id=${albumId.value}`
|
||||
);
|
||||
$message.success("专辑链接复制成功");
|
||||
} catch (err) {
|
||||
$message.error("复制失败:", err);
|
||||
}
|
||||
} else {
|
||||
$message.error("您的浏览器暂不支持该操作");
|
||||
}
|
||||
},
|
||||
},
|
||||
icon: renderIcon(h(LinkTwo)),
|
||||
},
|
||||
{
|
||||
key: "like",
|
||||
label: isLikeOrDislike(albumId.value) ? "收藏专辑" : "取消收藏专辑",
|
||||
show: user.userLogin,
|
||||
props: {
|
||||
onClick: () => {
|
||||
toChangeLike(albumId.value);
|
||||
},
|
||||
},
|
||||
icon: renderIcon(h(isLikeOrDislike(albumId.value) ? Like : Unlike)),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
// 获取歌单信息
|
||||
const getAlbumData = (id) => {
|
||||
getAlbum(id).then((res) => {
|
||||
@@ -139,9 +241,75 @@ const getAlbumData = (id) => {
|
||||
});
|
||||
};
|
||||
|
||||
// 播放专辑所有歌曲
|
||||
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("播放全部歌曲失败:" + err);
|
||||
$message.error("播放全部歌曲失败,请重试");
|
||||
}
|
||||
};
|
||||
|
||||
// 收藏/取消收藏
|
||||
const toChangeLike = async (id) => {
|
||||
const type = isLikeOrDislike(id) ? 1 : 2;
|
||||
try {
|
||||
const res = await likeAlbum(type, id);
|
||||
if (res.code === 200) {
|
||||
$message.success(`专辑${type == 1 ? "收藏成功" : "取消收藏成功"}`);
|
||||
user.setUserAlbumLists(() => {
|
||||
setDropdownOptions();
|
||||
});
|
||||
} else {
|
||||
$message.error(`专辑${type == 1 ? "收藏失败" : "取消收藏失败"}`);
|
||||
}
|
||||
} catch (err) {
|
||||
$message.error(`专辑${type == 1 ? "收藏失败" : "取消收藏失败"}`);
|
||||
console.error(`专辑${type == 1 ? "收藏失败:" : "取消收藏失败:"}` + err);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (albumId.value) {
|
||||
getAlbumData(albumId.value);
|
||||
if (
|
||||
user.userLogin &&
|
||||
!user.getUserAlbumLists.has &&
|
||||
!user.getUserAlbumLists.isLoading
|
||||
) {
|
||||
user.setUserAlbumLists(() => {
|
||||
setDropdownOptions();
|
||||
});
|
||||
} else {
|
||||
setDropdownOptions();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -173,49 +341,84 @@ watch(
|
||||
align-items: flex-start;
|
||||
position: sticky;
|
||||
top: 24px;
|
||||
@media (max-width: 990px) {
|
||||
margin-right: 0;
|
||||
width: 30vw;
|
||||
}
|
||||
.cover {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
// box-shadow: 0 0 16px 0px rgb(0 0 0 / 20%);
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
.n-avatar {
|
||||
border-radius: 8px;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.album {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 4%;
|
||||
right: -20%;
|
||||
}
|
||||
}
|
||||
.intr {
|
||||
margin-top: 24px;
|
||||
width: 80%;
|
||||
padding-left: 4px;
|
||||
.name {
|
||||
display: block;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.desc {
|
||||
-webkit-line-clamp: 4;
|
||||
line-height: 26px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
.tag {
|
||||
margin-top: 20px;
|
||||
.meta {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
.tags {
|
||||
margin-right: 8px;
|
||||
font-size: 13px;
|
||||
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 {
|
||||
margin-top: 24px;
|
||||
width: 80%;
|
||||
padding-left: 4px;
|
||||
.name {
|
||||
display: block;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.desc {
|
||||
-webkit-line-clamp: 4;
|
||||
line-height: 26px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
.tag {
|
||||
margin-top: 20px;
|
||||
.tags {
|
||||
line-height: 0;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,7 +449,7 @@ watch(
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
@media (max-width: 768px) {
|
||||
@media (max-width: 1100px) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
@@ -272,21 +475,111 @@ watch(
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
.left {
|
||||
margin-bottom: 12px;
|
||||
position: static;
|
||||
width: 60vw;
|
||||
position: relative;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 30vw;
|
||||
max-width: none;
|
||||
.intr {
|
||||
display: 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 {
|
||||
margin-top: 0;
|
||||
padding-left: 0;
|
||||
.name,
|
||||
.all-desc {
|
||||
display: none;
|
||||
}
|
||||
.desc {
|
||||
-webkit-line-clamp: 3;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.control {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -60px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.right {
|
||||
margin-top: 80px;
|
||||
.meta {
|
||||
.name {
|
||||
font-size: 26px;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (max-width: 540px) {
|
||||
.left {
|
||||
.cover {
|
||||
margin-right: 44px;
|
||||
}
|
||||
.meta {
|
||||
.title {
|
||||
margin-bottom: 0;
|
||||
.name {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
.title {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<script setup>
|
||||
import { getArtistAblums } from "@/api/artist";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getLongTime } from "@/utils/timeTools.js";
|
||||
import { getLongTime } from "@/utils/timeTools";
|
||||
import CoverLists from "@/components/DataList/CoverLists.vue";
|
||||
import Pagination from "@/components/Pagination/index.vue";
|
||||
const router = useRouter();
|
||||
|
||||
@@ -30,10 +30,10 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getArtistAllSongs } from "@/api/artist";
|
||||
import { getArtistDetail, getArtistAllSongs } from "@/api/artist";
|
||||
import { getMusicDetail } from "@/api/song";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getSongTime } from "@/utils/timeTools.js";
|
||||
import { getSongTime } from "@/utils/timeTools";
|
||||
import DataLists from "@/components/DataList/DataLists.vue";
|
||||
import Pagination from "@/components/Pagination/index.vue";
|
||||
|
||||
@@ -51,41 +51,56 @@ const pageNumber = ref(
|
||||
: 1
|
||||
);
|
||||
|
||||
// 获取歌手名称
|
||||
const getArtistDetailData = (id) => {
|
||||
getArtistDetail(id).then((res) => {
|
||||
artistName.value = res.data.artist.name;
|
||||
});
|
||||
};
|
||||
|
||||
// 获取歌手信息
|
||||
const getArtistAllSongsData = (id, limit = 30, offset = 0, order = "hot") => {
|
||||
getArtistAllSongs(id, limit, offset, order).then((res) => {
|
||||
console.log(res);
|
||||
if (res.songs[0]) {
|
||||
// 数据总数
|
||||
totalCount.value = res.total;
|
||||
// 歌手名称
|
||||
artistName.value = res.songs[0].ar[0].name;
|
||||
// 列表数据
|
||||
const ids = res.songs.map((obj) => obj.id);
|
||||
getMusicDetail(ids.join(",")).then((res) => {
|
||||
console.log(res);
|
||||
artistData.value = [];
|
||||
res.songs.forEach((v, i) => {
|
||||
artistData.value.push({
|
||||
id: v.id,
|
||||
num: i + 1 + (pageNumber.value - 1) * pagelimit.value,
|
||||
name: v.name,
|
||||
artist: v.ar,
|
||||
album: v.al,
|
||||
alia: v.alia,
|
||||
time: getSongTime(v.dt),
|
||||
fee: v.fee,
|
||||
pc: v.pc ? v.pc : null,
|
||||
mv: v.mv ? v.mv : null,
|
||||
if (!id) return false;
|
||||
getArtistAllSongs(id, limit, offset, order)
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
// 获取歌手名称
|
||||
getArtistDetailData(id);
|
||||
// 全部歌曲数据
|
||||
if (res.songs[0]) {
|
||||
// 数据总数
|
||||
totalCount.value = res.total;
|
||||
// 列表数据
|
||||
const ids = res.songs.map((obj) => obj.id);
|
||||
getMusicDetail(ids.join(",")).then((res) => {
|
||||
console.log(res);
|
||||
artistData.value = [];
|
||||
res.songs.forEach((v, i) => {
|
||||
artistData.value.push({
|
||||
id: v.id,
|
||||
num: i + 1 + (pageNumber.value - 1) * pagelimit.value,
|
||||
name: v.name,
|
||||
artist: v.ar,
|
||||
album: v.al,
|
||||
alia: v.alia,
|
||||
time: getSongTime(v.dt),
|
||||
fee: v.fee,
|
||||
pc: v.pc ? v.pc : null,
|
||||
mv: v.mv ? v.mv : null,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$message.error("歌手全部歌曲为空");
|
||||
}
|
||||
// 请求后回顶并结束加载条
|
||||
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
|
||||
});
|
||||
} else {
|
||||
$message.error("歌手全部歌曲为空");
|
||||
}
|
||||
// 请求后回顶并结束加载条
|
||||
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
|
||||
})
|
||||
.catch((err) => {
|
||||
router.go(-1);
|
||||
console.error("歌手全部歌曲获取失败:" + err);
|
||||
$message.error("歌手全部歌曲获取失败");
|
||||
});
|
||||
};
|
||||
|
||||
// 监听路由参数变化
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<script setup>
|
||||
import { getArtistSongs } from "@/api/artist";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getSongTime } from "@/utils/timeTools.js";
|
||||
import { getSongTime } from "@/utils/timeTools";
|
||||
import DataLists from "@/components/DataList/DataLists.vue";
|
||||
const router = useRouter();
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<script setup>
|
||||
import { getArtistVideos } from "@/api/artist";
|
||||
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 Pagination from "@/components/Pagination/index.vue";
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
class="cat"
|
||||
icon-placement="right"
|
||||
round
|
||||
@click="catModelShow = true"
|
||||
@click="catModalShow = true"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon class="up" :component="ChevronRightRound" />
|
||||
@@ -17,7 +17,7 @@
|
||||
</n-button>
|
||||
<n-modal
|
||||
class="s-modal"
|
||||
v-model:show="catModelShow"
|
||||
v-model:show="catModalShow"
|
||||
preset="card"
|
||||
title="歌单分类"
|
||||
:bordered="false"
|
||||
@@ -127,7 +127,7 @@ import { ChevronRightRound, LocalFireDepartmentRound } from "@vicons/material";
|
||||
import { useRouter } from "vue-router";
|
||||
import { musicStore } from "@/store";
|
||||
import { getHighqualityPlaylist, getTopPlaylist } from "@/api/playlist";
|
||||
import { formatNumber } from "@/utils/timeTools.js";
|
||||
import { formatNumber } from "@/utils/timeTools";
|
||||
import CoverLists from "@/components/DataList/CoverLists.vue";
|
||||
import Pagination from "@/components/Pagination/index.vue";
|
||||
|
||||
@@ -135,7 +135,7 @@ const router = useRouter();
|
||||
const music = musicStore();
|
||||
|
||||
// 分类数据
|
||||
const catModelShow = ref(false);
|
||||
const catModalShow = ref(false);
|
||||
const catName = ref(
|
||||
router.currentRoute.value.query.cat
|
||||
? router.currentRoute.value.query.cat
|
||||
@@ -255,7 +255,7 @@ const changeTagName = (name) => {
|
||||
page: 1,
|
||||
},
|
||||
});
|
||||
catModelShow.value = false;
|
||||
catModalShow.value = false;
|
||||
};
|
||||
|
||||
// 排序方式变化
|
||||
@@ -300,6 +300,7 @@ watch(
|
||||
: false;
|
||||
if (val.name == "dsc-playlists") {
|
||||
if (hqPLayListOpen.value) {
|
||||
playlistsData.value = [];
|
||||
getHqPlaylistData(catName.value);
|
||||
} else {
|
||||
pageNumber.value = val.query.page ? Number(val.query.page) : 1;
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<script setup>
|
||||
import { getToplist } from "@/api/album";
|
||||
import { useRouter } from "vue-router";
|
||||
import { formatNumber } from "@/utils/timeTools.js";
|
||||
import { formatNumber } from "@/utils/timeTools";
|
||||
import CoverLists from "@/components/DataList/CoverLists.vue";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@@ -30,7 +30,7 @@ import PaPersonalFm from "@/components/Personalized/PaPersonalFm.vue";
|
||||
const setting = settingStore();
|
||||
|
||||
onMounted(() => {
|
||||
$setSiteTitle("SPlayer");
|
||||
$setSiteTitle(import.meta.env.VITE_SITE_TITLE);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ import {
|
||||
} from "@/api/login";
|
||||
import { useRouter } from "vue-router";
|
||||
import { PhoneAndroidRound, PasswordRound } from "@vicons/material";
|
||||
import { formRules } from "@/utils/formRules.js";
|
||||
import { formRules } from "@/utils/formRules";
|
||||
import QrcodeVue from "qrcode.vue";
|
||||
|
||||
const router = useRouter();
|
||||
@@ -152,7 +152,7 @@ const saveLoginData = (data) => {
|
||||
// 自动签到
|
||||
if ($signIn) $signIn();
|
||||
clearInterval(qrCheckInterval.value);
|
||||
router.go(-1);
|
||||
router.push("/user");
|
||||
} else {
|
||||
user.userLogOut();
|
||||
$message.error("登录出错,请重试");
|
||||
@@ -168,7 +168,7 @@ const getQrKeyData = () => {
|
||||
if (res.data.profile && window.localStorage.getItem("cookie")) {
|
||||
$message.info("已登录,请勿重复登录");
|
||||
user.userLogin = true;
|
||||
router.go(-1);
|
||||
router.push("/user");
|
||||
} else {
|
||||
user.userLogOut();
|
||||
clearInterval(qrCheckInterval.value);
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<script setup>
|
||||
import { getAlbumNew } from "@/api/album";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getLongTime } from "@/utils/timeTools.js";
|
||||
import { getLongTime } from "@/utils/timeTools";
|
||||
import CoverLists from "@/components/DataList/CoverLists.vue";
|
||||
import Pagination from "@/components/Pagination/index.vue";
|
||||
|
||||
|
||||
@@ -12,73 +12,80 @@
|
||||
"
|
||||
fallback-src="/images/pic/default.png"
|
||||
/>
|
||||
<img src="/images/pic/album.png" class="album" alt="album" />
|
||||
<n-avatar
|
||||
class="shadow"
|
||||
:src="
|
||||
playListDetail.coverImgUrl
|
||||
? playListDetail.coverImgUrl.replace(/^http:/, 'https:') +
|
||||
'?param=1024y1024'
|
||||
: null
|
||||
"
|
||||
fallback-src="/images/pic/default.png"
|
||||
/>
|
||||
</div>
|
||||
<div class="intr">
|
||||
<span class="name">歌单简介</span>
|
||||
<span class="desc text-hidden">
|
||||
{{
|
||||
playListDetail.description
|
||||
? playListDetail.description
|
||||
: "太懒了吧,连简介都不写"
|
||||
}}
|
||||
</span>
|
||||
<n-button
|
||||
block
|
||||
strong
|
||||
secondary
|
||||
v-if="playListDetail?.description?.length > 70"
|
||||
@click="playListDescShow = true"
|
||||
>
|
||||
全部简介
|
||||
</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>
|
||||
<n-space class="tag" v-if="playListDetail.tags">
|
||||
<n-tag
|
||||
class="tags"
|
||||
size="large"
|
||||
round
|
||||
:bordered="false"
|
||||
v-for="item in playListDetail.tags"
|
||||
:key="item"
|
||||
@click="router.push(`/discover/playlists?cat=${item}&page=1`)"
|
||||
>
|
||||
{{ item }}
|
||||
</n-tag>
|
||||
</n-space>
|
||||
<!-- <div class="control" v-if="true">
|
||||
<n-space>
|
||||
<n-button strong secondary round>
|
||||
<template #icon>
|
||||
<n-icon :component="EditNoteRound" />
|
||||
</template>
|
||||
编辑
|
||||
</n-button>
|
||||
<n-button strong secondary round type="primary">
|
||||
<template #icon>
|
||||
<n-icon :component="DeleteRound" />
|
||||
</template>
|
||||
<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 class="intr">
|
||||
<span class="name">歌单简介</span>
|
||||
<span class="desc text-hidden">
|
||||
{{
|
||||
playListDetail.description
|
||||
? playListDetail.description
|
||||
: "太懒了吧,连简介都不写"
|
||||
}}
|
||||
</span>
|
||||
<n-button
|
||||
class="all-desc"
|
||||
block
|
||||
strong
|
||||
secondary
|
||||
v-if="playListDetail?.description?.length > 70"
|
||||
@click="playListDescShow = true"
|
||||
>
|
||||
全部简介
|
||||
</n-button>
|
||||
</div>
|
||||
<n-space class="tag" v-if="playListDetail.tags">
|
||||
<n-tag
|
||||
class="tags"
|
||||
round
|
||||
:bordered="false"
|
||||
v-for="item in playListDetail.tags"
|
||||
:key="item"
|
||||
@click="router.push(`/discover/playlists?cat=${item}&page=1`)"
|
||||
>
|
||||
{{ item }}
|
||||
</n-tag>
|
||||
</n-space>
|
||||
</div> -->
|
||||
<n-space class="control">
|
||||
<n-button strong secondary round type="primary" @click="playAllSong">
|
||||
<template #icon>
|
||||
<n-icon :component="MusicList" />
|
||||
</template>
|
||||
播放
|
||||
</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 class="right">
|
||||
<div class="meta">
|
||||
<span class="name">{{ playListDetail.name }}</span>
|
||||
<span class="creator">{{ playListDetail.creator.nickname }}</span>
|
||||
<n-text class="name">{{ playListDetail.name }}</n-text>
|
||||
<n-text class="creator">{{ playListDetail.creator.nickname }}</n-text>
|
||||
<div class="time">
|
||||
<div class="createTime">
|
||||
<span class="num">创建时间:</span>
|
||||
@@ -99,6 +106,18 @@
|
||||
@pageSizeChange="pageSizeChange"
|
||||
@pageNumberChange="pageNumberChange"
|
||||
/>
|
||||
<!-- 歌单简介 -->
|
||||
<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>
|
||||
<div class="title" v-else-if="!playListId || !loadingState">
|
||||
@@ -124,17 +143,31 @@
|
||||
</template>
|
||||
|
||||
<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 { userStore, musicStore } from "@/store";
|
||||
import { getSongTime, getLongTime } from "@/utils/timeTools.js";
|
||||
// import { EditNoteRound, DeleteRound } from "@vicons/material";
|
||||
import { userStore, musicStore, settingStore } from "@/store";
|
||||
import { getSongTime, getLongTime } from "@/utils/timeTools";
|
||||
import {
|
||||
MusicList,
|
||||
LinkTwo,
|
||||
More,
|
||||
DeleteFour,
|
||||
Like,
|
||||
Unlike,
|
||||
} from "@icon-park/vue-next";
|
||||
import DataLists from "@/components/DataList/DataLists.vue";
|
||||
import Pagination from "@/components/Pagination/index.vue";
|
||||
|
||||
const router = useRouter();
|
||||
// const user = userStore();
|
||||
// const music = musicStore();
|
||||
const user = userStore();
|
||||
const music = musicStore();
|
||||
const setting = settingStore();
|
||||
|
||||
// 歌单数据
|
||||
const playListId = ref(router.currentRoute.value.query.id);
|
||||
@@ -150,6 +183,89 @@ const pageNumber = ref(
|
||||
);
|
||||
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: "复制歌单链接",
|
||||
props: {
|
||||
onClick: () => {
|
||||
if (navigator.clipboard) {
|
||||
try {
|
||||
navigator.clipboard.writeText(
|
||||
`https://music.163.com/#/playlist?id=${playListId.value}`
|
||||
);
|
||||
$message.success("歌单链接复制成功");
|
||||
} catch (err) {
|
||||
$message.error("复制失败:", err);
|
||||
}
|
||||
} else {
|
||||
$message.error("您的浏览器暂不支持该操作");
|
||||
}
|
||||
},
|
||||
},
|
||||
icon: renderIcon(h(LinkTwo)),
|
||||
},
|
||||
{
|
||||
key: "del",
|
||||
label: "删除歌单",
|
||||
show: user.userLogin && isCanDelete(playListId.value),
|
||||
props: {
|
||||
onClick: () => {
|
||||
toDelPlayList(playListDetail.value);
|
||||
},
|
||||
},
|
||||
icon: renderIcon(h(DeleteFour)),
|
||||
},
|
||||
{
|
||||
key: "like",
|
||||
label: isLikeOrDislike(playListId.value) ? "收藏歌单" : "取消收藏歌单",
|
||||
show: user.userLogin && !isCanDelete(playListId.value),
|
||||
props: {
|
||||
onClick: () => {
|
||||
toChangeLike(playListId.value);
|
||||
},
|
||||
},
|
||||
icon: renderIcon(h(isLikeOrDislike(playListId.value) ? Like : Unlike)),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
// 获取歌单信息
|
||||
const getPlayListDetailData = (id) => {
|
||||
getPlayListDetail(id)
|
||||
@@ -197,6 +313,85 @@ const getAllPlayListData = (id, limit = 30, offset = 0) => {
|
||||
});
|
||||
};
|
||||
|
||||
// 播放歌单所有歌曲
|
||||
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("播放全部歌曲失败:" + err);
|
||||
$message.error("播放全部歌曲失败,请重试");
|
||||
}
|
||||
};
|
||||
|
||||
// 删除歌单
|
||||
const toDelPlayList = (data) => {
|
||||
if (data.id === user.getUserPlayLists?.own[0].id) {
|
||||
$message.warning("默认歌单无法删除");
|
||||
return false;
|
||||
}
|
||||
$dialog.warning({
|
||||
class: "s-dialog",
|
||||
title: "删除歌单",
|
||||
content: "确认删除歌单 " + data.name + "?删除后将不可恢复!",
|
||||
positiveText: "删除",
|
||||
negativeText: "取消",
|
||||
onPositiveClick: () => {
|
||||
delPlayList(data.id).then((res) => {
|
||||
if (res.code === 200) {
|
||||
$message.success("删除成功");
|
||||
user.setUserPlayLists();
|
||||
router.push("/user/playlists");
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 收藏/取消收藏
|
||||
const toChangeLike = async (id) => {
|
||||
const type = isLikeOrDislike(id) ? 1 : 2;
|
||||
try {
|
||||
const res = await likePlaylist(type, id);
|
||||
if (res.code === 200) {
|
||||
$message.success(`歌单${type == 1 ? "收藏成功" : "取消收藏成功"}`);
|
||||
user.setUserPlayLists(() => {
|
||||
setDropdownOptions();
|
||||
});
|
||||
} else {
|
||||
$message.error(`歌单${type == 1 ? "收藏失败" : "取消收藏失败"}`);
|
||||
}
|
||||
} catch (err) {
|
||||
$message.error(`歌单${type == 1 ? "收藏失败" : "取消收藏失败"}`);
|
||||
console.error(`歌单${type == 1 ? "收藏失败:" : "取消收藏失败:"}` + err);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (playListId.value) {
|
||||
getPlayListDetailData(playListId.value);
|
||||
@@ -205,6 +400,17 @@ onMounted(() => {
|
||||
pagelimit.value,
|
||||
(pageNumber.value - 1) * pagelimit.value
|
||||
);
|
||||
if (
|
||||
user.userLogin &&
|
||||
!user.getUserPlayLists.has &&
|
||||
!user.getUserPlayLists.isLoading
|
||||
) {
|
||||
user.setUserPlayLists(() => {
|
||||
setDropdownOptions();
|
||||
});
|
||||
} else {
|
||||
setDropdownOptions();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -272,53 +478,94 @@ watch(
|
||||
align-items: flex-start;
|
||||
position: sticky;
|
||||
top: 24px;
|
||||
@media (max-width: 990px) {
|
||||
margin-right: 0;
|
||||
width: 30vw;
|
||||
}
|
||||
.cover {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
// box-shadow: 0 0 16px 0px rgb(0 0 0 / 20%);
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
.n-avatar {
|
||||
border-radius: 8px;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
}
|
||||
.album {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
.shadow {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 4%;
|
||||
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;
|
||||
}
|
||||
}
|
||||
.intr {
|
||||
margin-top: 24px;
|
||||
width: 80%;
|
||||
padding-left: 4px;
|
||||
.name {
|
||||
display: block;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.desc {
|
||||
-webkit-line-clamp: 4;
|
||||
line-height: 26px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
.tag {
|
||||
margin-top: 20px;
|
||||
.tags {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
background-color: var(--main-second-color);
|
||||
color: var(--main-color);
|
||||
.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;
|
||||
}
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
.creator {
|
||||
margin-top: 6px;
|
||||
font-size: 16px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
.intr {
|
||||
margin-top: 24px;
|
||||
width: 80%;
|
||||
padding-left: 4px;
|
||||
.name {
|
||||
display: block;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 12px;
|
||||
@media (max-width: 990px) {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
.desc {
|
||||
-webkit-line-clamp: 4;
|
||||
line-height: 26px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
.tag {
|
||||
margin-top: 20px;
|
||||
.tags {
|
||||
line-height: 0;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
.right {
|
||||
@@ -374,22 +621,110 @@ watch(
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
.left {
|
||||
margin-bottom: 12px;
|
||||
position: static;
|
||||
width: 60vw;
|
||||
position: relative;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 40vw;
|
||||
max-width: none;
|
||||
.intr,
|
||||
.tag {
|
||||
display: none;
|
||||
display: flex;
|
||||
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;
|
||||
}
|
||||
.desc {
|
||||
-webkit-line-clamp: 2;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.control {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -60px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.right {
|
||||
margin-top: 80px;
|
||||
.meta {
|
||||
.name {
|
||||
font-size: 26px;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (max-width: 540px) {
|
||||
.left {
|
||||
.cover {
|
||||
margin-right: 20px;
|
||||
}
|
||||
.meta {
|
||||
.title {
|
||||
.name {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
.title {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<script setup>
|
||||
import { getSearchData } from "@/api/search";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getLongTime } from "@/utils/timeTools.js";
|
||||
import { getLongTime } from "@/utils/timeTools";
|
||||
import CoverLists from "@/components/DataList/CoverLists.vue";
|
||||
import Pagination from "@/components/Pagination/index.vue";
|
||||
const router = useRouter();
|
||||
|
||||
@@ -74,7 +74,7 @@ const tabChange = (value) => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
$setSiteTitle(searchKeywords.value + "的搜索结果");
|
||||
if (searchKeywords.value) $setSiteTitle(searchKeywords.value + "的搜索结果");
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<script setup>
|
||||
import { getSearchData } from "@/api/search";
|
||||
import { useRouter } from "vue-router";
|
||||
import { formatNumber } from "@/utils/timeTools.js";
|
||||
import { formatNumber } from "@/utils/timeTools";
|
||||
import CoverLists from "@/components/DataList/CoverLists.vue";
|
||||
import Pagination from "@/components/Pagination/index.vue";
|
||||
const router = useRouter();
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
<script setup>
|
||||
import { getSearchData } from "@/api/search";
|
||||
import { getMusicDetail } from "@/api/song";
|
||||
// import { getMusicDetail } from "@/api/song";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getSongTime } from "@/utils/timeTools.js";
|
||||
import { getSongTime } from "@/utils/timeTools";
|
||||
import DataLists from "@/components/DataList/DataLists.vue";
|
||||
import Pagination from "@/components/Pagination/index.vue";
|
||||
const router = useRouter();
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<script setup>
|
||||
import { getSearchData } from "@/api/search";
|
||||
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 Pagination from "@/components/Pagination/index.vue";
|
||||
const router = useRouter();
|
||||
|
||||
@@ -12,7 +12,12 @@
|
||||
主题色选择
|
||||
<span class="tip">更换全站主题色,即时生效</span>
|
||||
</div>
|
||||
<n-button strong secondary @click="changeThemeColor(null, true)">
|
||||
<n-button
|
||||
v-if="themeType !== 'red'"
|
||||
strong
|
||||
secondary
|
||||
@click="changeThemeColor(null, true)"
|
||||
>
|
||||
恢复默认
|
||||
</n-button>
|
||||
</div>
|
||||
@@ -27,7 +32,7 @@
|
||||
v-for="item in themeColorData"
|
||||
:key="item"
|
||||
:style="{ '--color': item.primaryColor }"
|
||||
:class="item.label === setting.themeType ? 'item check' : 'item'"
|
||||
:class="item.label === themeType ? 'item check' : 'item'"
|
||||
@click="changeThemeColor(item)"
|
||||
>
|
||||
<n-text v-html="item.name" />
|
||||
@@ -75,6 +80,13 @@
|
||||
</div>
|
||||
<n-switch v-model:value="bottomLyricShow" :round="false" />
|
||||
</n-card>
|
||||
<n-card class="set-item">
|
||||
<div class="name">
|
||||
歌曲渐入渐出
|
||||
<span class="tip">是否在歌曲暂停 / 播放时渐入渐出</span>
|
||||
</div>
|
||||
<n-switch v-model:value="songVolumeFade" :round="false" />
|
||||
</n-card>
|
||||
<n-card class="set-item">
|
||||
<div class="name">
|
||||
歌曲音质
|
||||
@@ -86,6 +98,30 @@
|
||||
:options="songLevelOptions"
|
||||
/>
|
||||
</n-card>
|
||||
<n-card class="set-item">
|
||||
<div class="name">
|
||||
尝试替换无法播放的歌曲
|
||||
<span class="tip">
|
||||
{{
|
||||
useUnmServerShow
|
||||
? "是否使用 UNM 替换无法播放的歌曲链接"
|
||||
: "请配置 UNM-Server 后使用解灰功能"
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<n-switch
|
||||
v-model:value="useUnmServer"
|
||||
:round="false"
|
||||
:disabled="!useUnmServerShow"
|
||||
/>
|
||||
</n-card>
|
||||
<n-card class="set-item">
|
||||
<div class="name">
|
||||
播放页快捷设置
|
||||
<span class="tip">是否在播放页面显示快捷设置</span>
|
||||
</div>
|
||||
<n-switch v-model:value="showLyricSetting" :round="false" />
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -106,8 +142,14 @@ const {
|
||||
autoSignIn,
|
||||
searchHistory,
|
||||
themeType,
|
||||
showLyricSetting,
|
||||
songVolumeFade,
|
||||
useUnmServer,
|
||||
} = storeToRefs(setting);
|
||||
|
||||
// UNM 开关显示
|
||||
const useUnmServerShow = import.meta.env.VITE_UNM_API ? true : false;
|
||||
|
||||
// 深浅模式
|
||||
const darkOptions = [
|
||||
{
|
||||
@@ -180,12 +222,12 @@ const changeThemeColor = (data, reset = false) => {
|
||||
negativeText: "取消",
|
||||
onPositiveClick: () => {
|
||||
$message.success("主题色已重置");
|
||||
setting.themeType = "red";
|
||||
themeType.value = "red";
|
||||
},
|
||||
});
|
||||
} else {
|
||||
$message.success("主题色更换为" + data.name);
|
||||
setting.themeType = data.label;
|
||||
themeType.value = data.label;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="set-other">
|
||||
<n-card class="set-item">
|
||||
<div class="name">
|
||||
系统重置
|
||||
程序重置
|
||||
<span class="tip">若程序显示异常或出现问题时可尝试此操作</span>
|
||||
</div>
|
||||
<n-button strong secondary type="error" @click="resetApp">
|
||||
@@ -13,7 +13,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 系统重置
|
||||
// 程序重置
|
||||
const resetApp = () => {
|
||||
const cleanAll = () => {
|
||||
$message ? $message.success("重置成功") : alert("重置成功");
|
||||
@@ -22,9 +22,9 @@ const resetApp = () => {
|
||||
};
|
||||
$dialog.warning({
|
||||
class: "s-dialog",
|
||||
title: "系统重置",
|
||||
title: "程序重置",
|
||||
content: "确认重置为默认状态?你的登录状态以及自定义设置都将丢失!",
|
||||
positiveText: "重置",
|
||||
positiveText: "确认重置",
|
||||
negativeText: "取消",
|
||||
onPositiveClick: () => {
|
||||
$cleanAll ? $cleanAll() : cleanAll();
|
||||
|
||||
@@ -26,23 +26,6 @@
|
||||
:options="backgroundImageShowOptions"
|
||||
/>
|
||||
</n-card>
|
||||
<n-card class="set-item">
|
||||
<div class="name">
|
||||
替换无法播放的歌曲链接
|
||||
<span class="tip">
|
||||
{{
|
||||
useUnmServerShow
|
||||
? "是否使用 UNM 替换无法播放的歌曲链接"
|
||||
: "请配置 UNM-Server 后使用解灰功能"
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<n-switch
|
||||
v-model:value="useUnmServer"
|
||||
:round="false"
|
||||
:disabled="!useUnmServerShow"
|
||||
/>
|
||||
</n-card>
|
||||
<n-card class="set-item">
|
||||
<div class="name">
|
||||
显示歌词翻译
|
||||
@@ -145,7 +128,7 @@
|
||||
</div>
|
||||
<n-switch v-model:value="lyricsBlur" :round="false" />
|
||||
</n-card>
|
||||
<n-card class="set-item">
|
||||
<!-- <n-card class="set-item">
|
||||
<div class="name">
|
||||
显示音乐频谱
|
||||
<span class="tip">可能会导致一些意想不到的后果,实验性功能</span>
|
||||
@@ -155,7 +138,7 @@
|
||||
:round="false"
|
||||
@click="changeMusicFrequency"
|
||||
/>
|
||||
</n-card>
|
||||
</n-card> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -175,14 +158,10 @@ const {
|
||||
lrcMousePause,
|
||||
showYrc,
|
||||
showRoma,
|
||||
useUnmServer,
|
||||
backgroundImageShow,
|
||||
countDownShow,
|
||||
} = storeToRefs(setting);
|
||||
|
||||
// UNM 开关显示
|
||||
const useUnmServerShow = import.meta.env.VITE_UNM_API ? true : false;
|
||||
|
||||
// 歌词位置
|
||||
const lyricsPositionOptions = [
|
||||
{
|
||||
@@ -232,24 +211,24 @@ const backgroundImageShowOptions = [
|
||||
];
|
||||
|
||||
// 音乐频谱提醒
|
||||
const changeMusicFrequency = () => {
|
||||
if (musicFrequency.value) {
|
||||
$dialog.warning({
|
||||
class: "s-dialog",
|
||||
title: "实验性功能",
|
||||
content: "确认开启音乐频谱?将在重启应用后生效",
|
||||
positiveText: "开启",
|
||||
negativeText: "取消",
|
||||
onMaskClick: () => {
|
||||
musicFrequency.value = false;
|
||||
},
|
||||
onPositiveClick: () => {
|
||||
musicFrequency.value = true;
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
musicFrequency.value = false;
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
// const changeMusicFrequency = () => {
|
||||
// if (musicFrequency.value) {
|
||||
// $dialog.warning({
|
||||
// class: "s-dialog",
|
||||
// title: "实验性功能",
|
||||
// content: "确认开启音乐频谱?将在重启应用后生效",
|
||||
// positiveText: "开启",
|
||||
// negativeText: "取消",
|
||||
// onMaskClick: () => {
|
||||
// musicFrequency.value = false;
|
||||
// },
|
||||
// onPositiveClick: () => {
|
||||
// musicFrequency.value = true;
|
||||
// },
|
||||
// onNegativeClick: () => {
|
||||
// musicFrequency.value = false;
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
</script>
|
||||
|
||||
@@ -102,14 +102,14 @@
|
||||
import { getSimiPlayList, getMusicDetail } from "@/api/song";
|
||||
import { useRouter } from "vue-router";
|
||||
import { musicStore } from "@/store";
|
||||
import { getLongTime } from "@/utils/timeTools.js";
|
||||
import { getLongTime } from "@/utils/timeTools";
|
||||
import {
|
||||
PlayArrowRound,
|
||||
MessageFilled,
|
||||
VideocamRound,
|
||||
PlaylistAddRound,
|
||||
} from "@vicons/material";
|
||||
import { formatNumber } from "@/utils/timeTools.js";
|
||||
import { formatNumber } from "@/utils/timeTools";
|
||||
import AllArtists from "@/components/DataList/AllArtists.vue";
|
||||
import CoverLists from "@/components/DataList/CoverLists.vue";
|
||||
import AddPlaylist from "@/components/DataModal/AddPlaylist.vue";
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
import { getCloud, upCloudSong } from "@/api/user";
|
||||
import { useRouter } from "vue-router";
|
||||
import { settingStore } from "@/store";
|
||||
import { getSongTime } from "@/utils/timeTools.js";
|
||||
import { getSongTime } from "@/utils/timeTools";
|
||||
import { BackupRound } from "@vicons/material";
|
||||
import DataLists from "@/components/DataList/DataLists.vue";
|
||||
import Pagination from "@/components/Pagination/index.vue";
|
||||
|
||||
@@ -79,7 +79,7 @@ import { useRouter } from "vue-router";
|
||||
import { musicStore } from "@/store";
|
||||
import { getVideoDetail, getVideoUrl, getSimiVideo } from "@/api/video";
|
||||
import { getComment } from "@/api/comment";
|
||||
import { formatNumber, getSongTime } from "@/utils/timeTools.js";
|
||||
import { formatNumber, getSongTime } from "@/utils/timeTools";
|
||||
import {
|
||||
OndemandVideoFilled,
|
||||
ShareFilled,
|
||||
@@ -213,8 +213,6 @@ const pageNumberChange = (val) => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 隐藏控制条
|
||||
music.setPlayBarState(false);
|
||||
// 初始化播放器
|
||||
player.value = new Plyr(videoRef.value, playerOptions);
|
||||
// 获取视频数据
|
||||
@@ -224,6 +222,8 @@ onMounted(() => {
|
||||
// 播放器事件
|
||||
player.value.on("playing", () => {
|
||||
console.log("视频开始播放");
|
||||
// 隐藏控制条及暂停音乐
|
||||
music.setPlayBarState(false);
|
||||
music.setPlayState(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import AutoImport from "unplugin-auto-import/vite";
|
||||
import Components from "unplugin-vue-components/vite";
|
||||
import { NaiveUiResolver } from "unplugin-vue-components/resolvers";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
import { createHtmlPlugin } from "vite-plugin-html";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default ({ mode }) =>
|
||||
@@ -27,6 +28,20 @@ export default ({ mode }) =>
|
||||
Components({
|
||||
resolvers: [NaiveUiResolver()],
|
||||
}),
|
||||
createHtmlPlugin({
|
||||
minify: true,
|
||||
template: "index.html",
|
||||
inject: {
|
||||
data: {
|
||||
logo: loadEnv(mode, process.cwd()).VITE_SITE_LOGO,
|
||||
title: loadEnv(mode, process.cwd()).VITE_SITE_TITLE,
|
||||
author: loadEnv(mode, process.cwd()).VITE_SITE_ANTHOR,
|
||||
keywords: loadEnv(mode, process.cwd()).VITE_SITE_KEYWORDS,
|
||||
description: loadEnv(mode, process.cwd()).VITE_SITE_DES,
|
||||
tongji: loadEnv(mode, process.cwd()).VITE_SITE_BAIDUTONGJI,
|
||||
},
|
||||
},
|
||||
}),
|
||||
// PWA
|
||||
VitePWA({
|
||||
registerType: "autoUpdate",
|
||||
@@ -53,9 +68,9 @@ export default ({ mode }) =>
|
||||
],
|
||||
},
|
||||
manifest: {
|
||||
name: "SPlayer",
|
||||
short_name: "SPlayer",
|
||||
description: "一个简约的在线音乐播放器",
|
||||
name: loadEnv(mode, process.cwd()).VITE_SITE_TITLE,
|
||||
short_name: loadEnv(mode, process.cwd()).VITE_SITE_TITLE,
|
||||
description: loadEnv(mode, process.cwd()).VITE_SITE_DES,
|
||||
display: "standalone",
|
||||
start_url: "/",
|
||||
theme_color: "#fff",
|
||||
|
||||
Reference in New Issue
Block a user