feat: 移动端基础适配

- 修复未登录时无法使用本地歌曲
- 修复部分样式异常
This commit is contained in:
imsyy
2024-01-08 18:20:47 +08:00
parent af3931847e
commit 6a1e606d6d
62 changed files with 1560 additions and 466 deletions

View File

@@ -2,3 +2,5 @@ node_modules
dist
out
.gitignore
auto-imports.d.ts
components.d.ts

View File

@@ -1,6 +1,6 @@
name: 添加功能
description: 请填写希望添加的功能的具体信息
title: "添加功能"
title: 添加功能】请填写标题
labels: [add]
body:
- type: input

View File

@@ -1,5 +1,6 @@
name: 遇到问题
description: 关于使用过程中遇到的问题
title: 【遇到问题】请填写标题
labels: [bug]
body:
- type: input

View File

@@ -4,3 +4,5 @@ pnpm-lock.yaml
LICENSE.md
tsconfig.json
tsconfig.*.json
auto-imports.d.ts
components.d.ts

View File

@@ -1,4 +1,8 @@
# 是否使用单引号而不是双引号
singleQuote: false
# 是否在语句末尾使用分号
semi: true
# 每行的最大打印宽度
printWidth: 100
# 是否在对象和数组的末尾加上逗号
trailingComma: all

View File

@@ -19,9 +19,10 @@
> - 感谢您的尊重与理解
- 本项目采用 [Vue 3](https://cn.vuejs.org/) 全家桶和 [Naïve UI](https://www.naiveui.com/) 组件库及 [Electron](https://www.electronjs.org/zh/docs/latest/) 开发
- 支持网页端与客户端,由于设备有限,目前仅适配 `Win`,其他平台可自行构建
- ~~仅对移动端做了基础适配,**不保证功能全部可用**~~
- 欢迎各位大佬指点和 `Star` 哦 😍
- 支持网页端与客户端,由于设备有限,目前仅适配 `Win`,其他平台可自行解决兼容性后进行构建
- 仅对移动端做了基础适配,**不保证功能全部可用**
> 请注意,本程序不打算开发移动端,也不会对移动端进行完美适配,仅保证基础可用性
- 欢迎各位大佬 `Star` 😍
## 👀 Demo
@@ -33,7 +34,7 @@
- 支持手机号登录
- 自动进行每日签到及云贝签到
- 封面主题色自适应
- 本地歌曲管理及分类 ~~以及音乐标签编辑~~
- 本地歌曲管理及分类(建议先使用 [音乐标签](https://www.cnblogs.com/vinlxc/p/11347744.html) 进行匹配后再使用)
- **支持播放部分无版权歌曲(可能会与原曲不匹配,客户端独占功能)**
- 下载歌曲(最高支持 Hi-Res
- 新建歌单及歌单编辑

2
components.d.ts vendored
View File

@@ -40,6 +40,7 @@ declare module 'vue' {
NDropdown: typeof import('naive-ui')['NDropdown']
NEllipsis: typeof import('naive-ui')['NEllipsis']
NEmpty: typeof import('naive-ui')['NEmpty']
NFlex: typeof import('naive-ui')['NFlex']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NGi: typeof import('naive-ui')['NGi']
@@ -99,6 +100,7 @@ declare module 'vue' {
SearchInp: typeof import('./src/components/Search/SearchInp.vue')['default']
SearchSuggestions: typeof import('./src/components/Search/SearchSuggestions.vue')['default']
SongList: typeof import('./src/components/List/SongList.vue')['default']
SongListDrawer: typeof import('./src/components/List/SongListDrawer.vue')['default']
SongListDropdown: typeof import('./src/components/List/SongListDropdown.vue')['default']
SpecialCover: typeof import('./src/components/Cover/SpecialCover.vue')['default']
SpecialCoverCard: typeof import('./src/components/Cover/SpecialCoverCard.vue')['default']

View File

@@ -27,7 +27,7 @@
"@electron-toolkit/utils": "^2.0.1",
"@material/material-color-utilities": "^0.2.7",
"NeteaseCloudMusicApi": "^4.14.0",
"axios": "^1.6.3",
"axios": "^1.6.5",
"colorthief": "^2.4.0",
"electron-dl": "^3.5.1",
"electron-store": "^8.1.0",
@@ -52,21 +52,21 @@
"@vitejs/plugin-vue": "^4.6.2",
"@vue/eslint-config-prettier": "^8.0.0",
"ajv": "^8.12.0",
"electron": "^27.2.0",
"electron": "^27.2.1",
"electron-builder": "^24.9.1",
"electron-log": "^5.0.3",
"electron-vite": "^1.0.29",
"eslint": "^8.56.0",
"eslint-plugin-vue": "^9.19.2",
"naive-ui": "^2.36.0",
"naive-ui": "^2.37.0",
"prettier": "^3.1.1",
"sass": "^1.69.6",
"sass": "^1.69.7",
"terser": "^5.26.0",
"unplugin-auto-import": "^0.16.7",
"unplugin-vue-components": "^0.25.2",
"vite": "^4.5.1",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-pwa": "^0.17.4",
"vue": "^3.4.3"
"vue": "3.4.4"
}
}

261
pnpm-lock.yaml generated
View File

@@ -7,10 +7,10 @@ settings:
dependencies:
'@electron-toolkit/preload':
specifier: ^2.0.0
version: 2.0.0(electron@27.2.0)
version: 2.0.0(electron@27.2.1)
'@electron-toolkit/utils':
specifier: ^2.0.1
version: 2.0.1(electron@27.2.0)
version: 2.0.1(electron@27.2.1)
'@material/material-color-utilities':
specifier: ^0.2.7
version: 0.2.7
@@ -18,8 +18,8 @@ dependencies:
specifier: ^4.14.0
version: 4.14.0
axios:
specifier: ^1.6.3
version: 1.6.3
specifier: ^1.6.5
version: 1.6.5
colorthief:
specifier: ^2.4.0
version: 2.4.0
@@ -55,7 +55,7 @@ dependencies:
version: 0.2.6
pinia:
specifier: ^2.1.7
version: 2.1.7(vue@3.4.3)
version: 2.1.7(vue@3.4.4)
pinia-plugin-persistedstate:
specifier: ^3.2.1
version: 3.2.1(pinia@2.1.7)
@@ -67,7 +67,7 @@ dependencies:
version: 6.0.2
vue-router:
specifier: ^4.2.5
version: 4.2.5(vue@3.4.3)
version: 4.2.5(vue@3.4.4)
vue-slider-component:
specifier: 4.1.0-beta.7
version: 4.1.0-beta.7
@@ -81,7 +81,7 @@ devDependencies:
version: 1.6.1
'@vitejs/plugin-vue':
specifier: ^4.6.2
version: 4.6.2(vite@4.5.1)(vue@3.4.3)
version: 4.6.2(vite@4.5.1)(vue@3.4.4)
'@vue/eslint-config-prettier':
specifier: ^8.0.0
version: 8.0.0(eslint@8.56.0)(prettier@3.1.1)
@@ -89,8 +89,8 @@ devDependencies:
specifier: ^8.12.0
version: 8.12.0
electron:
specifier: ^27.2.0
version: 27.2.0
specifier: ^27.2.1
version: 27.2.1
electron-builder:
specifier: ^24.9.1
version: 24.9.1
@@ -107,14 +107,14 @@ devDependencies:
specifier: ^9.19.2
version: 9.19.2(eslint@8.56.0)
naive-ui:
specifier: ^2.36.0
version: 2.36.0(vue@3.4.3)
specifier: ^2.37.0
version: 2.37.0(vue@3.4.4)
prettier:
specifier: ^3.1.1
version: 3.1.1
sass:
specifier: ^1.69.6
version: 1.69.6
specifier: ^1.69.7
version: 1.69.7
terser:
specifier: ^5.26.0
version: 5.26.0
@@ -123,10 +123,10 @@ devDependencies:
version: 0.16.7(rollup@2.79.1)
unplugin-vue-components:
specifier: ^0.25.2
version: 0.25.2(rollup@2.79.1)(vue@3.4.3)
version: 0.25.2(rollup@2.79.1)(vue@3.4.4)
vite:
specifier: ^4.5.1
version: 4.5.1(sass@1.69.6)(terser@5.26.0)
version: 4.5.1(sass@1.69.7)(terser@5.26.0)
vite-plugin-compression:
specifier: ^0.5.1
version: 0.5.1(vite@4.5.1)
@@ -134,8 +134,8 @@ devDependencies:
specifier: ^0.17.4
version: 0.17.4(vite@4.5.1)(workbox-build@7.0.0)(workbox-window@7.0.0)
vue:
specifier: ^3.4.3
version: 3.4.3
specifier: 3.4.4
version: 3.4.4
packages:
@@ -1342,12 +1342,12 @@ packages:
css-render: 0.15.12
dev: true
/@css-render/vue3-ssr@0.15.12(vue@3.4.3):
/@css-render/vue3-ssr@0.15.12(vue@3.4.4):
resolution: {integrity: sha512-AQLGhhaE0F+rwybRCkKUdzBdTEM/5PZBYy+fSYe1T9z9+yxMuV/k7ZRqa4M69X+EI1W8pa4kc9Iq2VjQkZx4rg==}
peerDependencies:
vue: ^3.0.11
dependencies:
vue: 3.4.3
vue: 3.4.4
dev: true
/@develar/schema-utils@2.6.5:
@@ -1366,20 +1366,20 @@ packages:
eslint: 8.56.0
dev: true
/@electron-toolkit/preload@2.0.0(electron@27.2.0):
/@electron-toolkit/preload@2.0.0(electron@27.2.1):
resolution: {integrity: sha512-zpZDzbqJTZQC5d4LRs2EKruKWnqah+T75s+niBYFemYLtiW5TTZcWi3Q8UxHqnwTudDMuWJb233aaS2yjx3Xiw==}
peerDependencies:
electron: '>=13.0.0'
dependencies:
electron: 27.2.0
electron: 27.2.1
dev: false
/@electron-toolkit/utils@2.0.1(electron@27.2.0):
/@electron-toolkit/utils@2.0.1(electron@27.2.1):
resolution: {integrity: sha512-3nnjd3D1NIjxdzNrvR5fkJ3kbJNbRkpHppv0/pSbMX6I0DaBzpPXeSfDYuJJKzZrAc3CmGcJa0MU4+AjEOlT4g==}
peerDependencies:
electron: '>=13.0.0'
dependencies:
electron: 27.2.0
electron: 27.2.1
dev: false
/@electron/asar@3.2.8:
@@ -1930,7 +1930,7 @@ packages:
dependencies:
'@types/http-cache-semantics': 4.0.4
'@types/keyv': 3.1.4
'@types/node': 18.19.4
'@types/node': 18.19.5
'@types/responselike': 1.0.3
/@types/debug@4.1.12:
@@ -1950,7 +1950,7 @@ packages:
/@types/fs-extra@9.0.13:
resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
dependencies:
'@types/node': 20.10.6
'@types/node': 20.10.7
dev: true
/@types/http-cache-semantics@4.0.4:
@@ -1963,7 +1963,7 @@ packages:
/@types/keyv@3.1.4:
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
dependencies:
'@types/node': 18.19.4
'@types/node': 18.19.5
/@types/lodash-es@4.17.12:
resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
@@ -1979,13 +1979,13 @@ packages:
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
dev: true
/@types/node@18.19.4:
resolution: {integrity: sha512-xNzlUhzoHotIsnFoXmJB+yWmBvFZgKCI9TtPIEdYIMM1KWfwuY8zh7wvc1u1OAXlC7dlf6mZVx/s+Y5KfFz19A==}
/@types/node@18.19.5:
resolution: {integrity: sha512-22MG6T02Hos2JWfa1o5jsIByn+bc5iOt1IS4xyg6OG68Bu+wMonVZzdrgCw693++rpLE9RUT/Bx15BeDzO0j+g==}
dependencies:
undici-types: 5.26.5
/@types/node@20.10.6:
resolution: {integrity: sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==}
/@types/node@20.10.7:
resolution: {integrity: sha512-fRbIKb8C/Y2lXxB5eVMj4IU7xpdox0Lh8bUPEdtLysaylsml1hOOx1+STloRs/B9nf7C6kPRmmg/V7aQW7usNg==}
dependencies:
undici-types: 5.26.5
dev: true
@@ -1994,7 +1994,7 @@ packages:
resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==}
requiresBuild: true
dependencies:
'@types/node': 20.10.6
'@types/node': 20.10.7
xmlbuilder: 15.1.1
dev: true
optional: true
@@ -2002,13 +2002,13 @@ packages:
/@types/resolve@1.17.1:
resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==}
dependencies:
'@types/node': 20.10.6
'@types/node': 20.10.7
dev: true
/@types/responselike@1.0.3:
resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==}
dependencies:
'@types/node': 18.19.4
'@types/node': 18.19.5
/@types/trusted-types@2.0.7:
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
@@ -2024,57 +2024,57 @@ packages:
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
requiresBuild: true
dependencies:
'@types/node': 18.19.4
'@types/node': 18.19.5
optional: true
/@ungap/structured-clone@1.2.0:
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
dev: true
/@vitejs/plugin-vue@4.6.2(vite@4.5.1)(vue@3.4.3):
/@vitejs/plugin-vue@4.6.2(vite@4.5.1)(vue@3.4.4):
resolution: {integrity: sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
vite: ^4.0.0 || ^5.0.0
vue: ^3.2.25
dependencies:
vite: 4.5.1(sass@1.69.6)(terser@5.26.0)
vue: 3.4.3
vite: 4.5.1(sass@1.69.7)(terser@5.26.0)
vue: 3.4.4
dev: true
/@vue/compiler-core@3.4.3:
resolution: {integrity: sha512-u8jzgFg0EDtSrb/hG53Wwh1bAOQFtc1ZCegBpA/glyvTlgHl+tq13o1zvRfLbegYUw/E4mSTGOiCnAJ9SJ+lsg==}
/@vue/compiler-core@3.4.4:
resolution: {integrity: sha512-U5AdCN+6skzh2bSJrkMj2KZsVkUpgK8/XlxjSRYQZhNPcvt9/kmgIMpFEiTyK+Dz5E1J+8o8//BEIX+bakgVSw==}
dependencies:
'@babel/parser': 7.23.6
'@vue/shared': 3.4.3
'@vue/shared': 3.4.4
entities: 4.5.0
estree-walker: 2.0.2
source-map-js: 1.0.2
/@vue/compiler-dom@3.4.3:
resolution: {integrity: sha512-oGF1E9/htI6JWj/lTJgr6UgxNCtNHbM6xKVreBWeZL9QhRGABRVoWGAzxmtBfSOd+w0Zi5BY0Es/tlJrN6WgEg==}
/@vue/compiler-dom@3.4.4:
resolution: {integrity: sha512-iSwkdDULCN+Vr8z6uwdlL044GJ/nUmECxP9vu7MzEs4Qma0FwDLYvnvRcyO0ZITuu3Os4FptGUDnhi1kOLSaGw==}
dependencies:
'@vue/compiler-core': 3.4.3
'@vue/shared': 3.4.3
'@vue/compiler-core': 3.4.4
'@vue/shared': 3.4.4
/@vue/compiler-sfc@3.4.3:
resolution: {integrity: sha512-NuJqb5is9I4uzv316VRUDYgIlPZCG8D+ARt5P4t5UDShIHKL25J3TGZAUryY/Aiy0DsY7srJnZL5ryB6DD63Zw==}
/@vue/compiler-sfc@3.4.4:
resolution: {integrity: sha512-OTFcU6vUxUNHBcarzkp4g6d25nvcmDvFDzPRvSrIsByFFPRYN+y3b+j9HxYwt6nlWvGyFCe0roeJdJlfYxbCBg==}
dependencies:
'@babel/parser': 7.23.6
'@vue/compiler-core': 3.4.3
'@vue/compiler-dom': 3.4.3
'@vue/compiler-ssr': 3.4.3
'@vue/shared': 3.4.3
'@vue/compiler-core': 3.4.4
'@vue/compiler-dom': 3.4.4
'@vue/compiler-ssr': 3.4.4
'@vue/shared': 3.4.4
estree-walker: 2.0.2
magic-string: 0.30.5
postcss: 8.4.32
postcss: 8.4.33
source-map-js: 1.0.2
/@vue/compiler-ssr@3.4.3:
resolution: {integrity: sha512-wnYQtMBkeFSxgSSQbYGQeXPhQacQiog2c6AlvMldQH6DB+gSXK/0F6DVXAJfEiuBSgBhUc8dwrrG5JQcqwalsA==}
/@vue/compiler-ssr@3.4.4:
resolution: {integrity: sha512-1DU9DflSSQlx/M61GEBN+NbT/anUki2ooDo9IXfTckCeKA/2IKNhY8KbG3x6zkd3KGrxzteC7de6QL88vEb41Q==}
dependencies:
'@vue/compiler-dom': 3.4.3
'@vue/shared': 3.4.3
'@vue/compiler-dom': 3.4.4
'@vue/shared': 3.4.4
/@vue/devtools-api@6.5.1:
resolution: {integrity: sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==}
@@ -2094,35 +2094,35 @@ packages:
- '@types/eslint'
dev: true
/@vue/reactivity@3.4.3:
resolution: {integrity: sha512-q5f9HLDU+5aBKizXHAx0w4whkIANs1Muiq9R5YXm0HtorSlflqv9u/ohaMxuuhHWCji4xqpQ1eL04WvmAmGnFg==}
/@vue/reactivity@3.4.4:
resolution: {integrity: sha512-DFsuJBf6sfhd5SYzJmcBTUG9+EKqjF31Gsk1NJtnpJm9liSZ806XwGJUeNBVQIanax7ODV7Lmk/k17BgxXNuTg==}
dependencies:
'@vue/shared': 3.4.3
'@vue/shared': 3.4.4
/@vue/runtime-core@3.4.3:
resolution: {integrity: sha512-C1r6QhB1qY7D591RCSFhMULyzL9CuyrGc+3PpB0h7dU4Qqw6GNyo4BNFjHZVvsWncrUlKX3DIKg0Y7rNNr06NQ==}
/@vue/runtime-core@3.4.4:
resolution: {integrity: sha512-zWWwNQAj5JdxrmOA1xegJm+c4VtyIbDEKgQjSb4va5v7gGTCh0ZjvLI+htGFdVXaO9bs2J3C81p5p+6jrPK8Bw==}
dependencies:
'@vue/reactivity': 3.4.3
'@vue/shared': 3.4.3
'@vue/reactivity': 3.4.4
'@vue/shared': 3.4.4
/@vue/runtime-dom@3.4.3:
resolution: {integrity: sha512-wrsprg7An5Ec+EhPngWdPuzkp0BEUxAKaQtN9dPU/iZctPyD9aaXmVtehPJerdQxQale6gEnhpnfywNw3zOv2A==}
/@vue/runtime-dom@3.4.4:
resolution: {integrity: sha512-Nlh2ap1J/eJQ6R0g+AIRyGNwpTJQACN0dk8I8FRLH8Ev11DSvfcPOpn4+Kbg5xAMcuq0cHB8zFYxVrOgETrrvg==}
dependencies:
'@vue/runtime-core': 3.4.3
'@vue/shared': 3.4.3
'@vue/runtime-core': 3.4.4
'@vue/shared': 3.4.4
csstype: 3.1.3
/@vue/server-renderer@3.4.3(vue@3.4.3):
resolution: {integrity: sha512-BUxt8oVGMKKsqSkM1uU3d3Houyfy4WAc2SpSQRebNd+XJGATVkW/rO129jkyL+kpB/2VRKzE63zwf5RtJ3XuZw==}
/@vue/server-renderer@3.4.4(vue@3.4.4):
resolution: {integrity: sha512-+AjoiKcC41k7SMJBYkDO9xs79/Of8DiThS9mH5l2MK+EY0to3psI0k+sElvVqQvsoZTjHMEuMz0AEgvm2T+CwA==}
peerDependencies:
vue: 3.4.3
vue: 3.4.4
dependencies:
'@vue/compiler-ssr': 3.4.3
'@vue/shared': 3.4.3
vue: 3.4.3
'@vue/compiler-ssr': 3.4.4
'@vue/shared': 3.4.4
vue: 3.4.4
/@vue/shared@3.4.3:
resolution: {integrity: sha512-rIwlkkP1n4uKrRzivAKPZIEkHiuwY5mmhMJ2nZKCBLz8lTUlE73rQh4n1OnnMurXt1vcUNyH4ZPfdh8QweTjpQ==}
/@vue/shared@3.4.4:
resolution: {integrity: sha512-abSgiVRhfjfl3JALR/cSuBl74hGJ3SePgf1mKzodf1eMWLwHZbfEGxT2cNJSsNiw44jEgrO7bNkhchaWA7RwNw==}
/@xmldom/xmldom@0.8.10:
resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==}
@@ -2135,7 +2135,7 @@ packages:
engines: {node: '>=12'}
hasBin: true
dependencies:
axios: 1.6.3
axios: 1.6.5
crypto-js: 4.2.0
express: 4.18.2
express-fileupload: 1.4.3
@@ -2394,8 +2394,8 @@ packages:
resolution: {integrity: sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==}
dev: false
/axios@1.6.3:
resolution: {integrity: sha512-fWyNdeawGam70jXSVlKl+SUNVcL6j6W79CuSIPfi6HnDUmSCH6gyUys/HrqHeA/wU0Az41rRgean494d0Jb+ww==}
/axios@1.6.5:
resolution: {integrity: sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==}
dependencies:
follow-redirects: 1.15.4
form-data: 4.0.0
@@ -2529,8 +2529,8 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
dependencies:
caniuse-lite: 1.0.30001572
electron-to-chromium: 1.4.616
caniuse-lite: 1.0.30001576
electron-to-chromium: 1.4.623
node-releases: 2.0.14
update-browserslist-db: 1.0.13(browserslist@4.22.2)
dev: true
@@ -2643,8 +2643,8 @@ packages:
engines: {node: '>=6'}
dev: false
/caniuse-lite@1.0.30001572:
resolution: {integrity: sha512-1Pbh5FLmn5y4+QhNyJE9j3/7dK44dGB83/ZMjv/qJk86TvDbjk0LosiZo0i0WB0Vx607qMX9jYrn1VLHCkN4rw==}
/caniuse-lite@1.0.30001576:
resolution: {integrity: sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg==}
dev: true
/caseless@0.12.0:
@@ -3198,8 +3198,8 @@ packages:
type-fest: 2.19.0
dev: false
/electron-to-chromium@1.4.616:
resolution: {integrity: sha512-1n7zWYh8eS0L9Uy+GskE0lkBUNK83cXTVJI0pU3mGprFsbfSdAc15VTFbo+A+Bq4pwstmL30AVcEU3Fo463lNg==}
/electron-to-chromium@1.4.623:
resolution: {integrity: sha512-lKoz10iCYlP1WtRYdh5MvocQPWVRoI7ysp6qf18bmeBgR8abE6+I2CsfyNKztRDZvhdWc+krKT6wS7Neg8sw3A==}
dev: true
/electron-updater@6.1.7:
@@ -3234,19 +3234,19 @@ packages:
esbuild: 0.18.20
magic-string: 0.30.5
picocolors: 1.0.0
vite: 4.5.1(sass@1.69.6)(terser@5.26.0)
vite: 4.5.1(sass@1.69.7)(terser@5.26.0)
transitivePeerDependencies:
- supports-color
dev: true
/electron@27.2.0:
resolution: {integrity: sha512-no/iMICVLI/5G0IqgKFbB89HDN88DWwKeRO+dPfJPkpJISdEX8Cx/sMEOFuuRa4VNInNe5CKCqRWExK5z3AdcQ==}
/electron@27.2.1:
resolution: {integrity: sha512-bYUzyptYrUIFtPnyF2x6DnhF1E9FCthctjbNSKMqg7dG4NqSwyuZzEku3Wts55u8R+ddNFbLoLwRHHLvYTCQlA==}
engines: {node: '>= 12.20.55'}
hasBin: true
requiresBuild: true
dependencies:
'@electron/get': 2.0.3
'@types/node': 18.19.4
'@types/node': 18.19.5
extract-zip: 2.0.1
transitivePeerDependencies:
- supports-color
@@ -4556,7 +4556,7 @@ packages:
resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==}
engines: {node: '>= 10.13.0'}
dependencies:
'@types/node': 20.10.6
'@types/node': 20.10.7
merge-stream: 2.0.0
supports-color: 7.2.0
dev: true
@@ -4978,18 +4978,19 @@ packages:
- supports-color
dev: false
/naive-ui@2.36.0(vue@3.4.3):
resolution: {integrity: sha512-r1ydtEm1Ryf/aWpbLCf32mQAGK99jd1eXgpkCtIomcBRZeAtusfy6zCtIpCppoCuIKM3BW5DMafhVxilubk/lQ==}
/naive-ui@2.37.0(vue@3.4.4):
resolution: {integrity: sha512-TcuXM1zysnK6i/7o2ZqNjcLp3QMmcdSLWWiXcpEk+xdGpkJzs53/OXNpF4CoDM/npjha7qqtB8Pl17YPN5egFw==}
peerDependencies:
vue: ^3.0.0
dependencies:
'@css-render/plugin-bem': 0.15.12(css-render@0.15.12)
'@css-render/vue3-ssr': 0.15.12(vue@3.4.3)
'@css-render/vue3-ssr': 0.15.12(vue@3.4.4)
'@types/katex': 0.16.7
'@types/lodash': 4.14.202
'@types/lodash-es': 4.17.12
async-validator: 4.2.5
css-render: 0.15.12
csstype: 3.1.3
date-fns: 2.30.0
date-fns-tz: 2.0.0(date-fns@2.30.0)
evtd: 0.2.4
@@ -4998,10 +4999,10 @@ packages:
lodash-es: 4.17.21
seemly: 0.3.8
treemate: 0.3.11
vdirs: 0.1.8(vue@3.4.3)
vooks: 0.2.12(vue@3.4.3)
vue: 3.4.3
vueuc: 0.4.58(vue@3.4.3)
vdirs: 0.1.8(vue@3.4.4)
vooks: 0.2.12(vue@3.4.4)
vue: 3.4.4
vueuc: 0.4.58(vue@3.4.4)
dev: true
/nanoid@3.3.7:
@@ -5286,10 +5287,10 @@ packages:
peerDependencies:
pinia: ^2.0.0
dependencies:
pinia: 2.1.7(vue@3.4.3)
pinia: 2.1.7(vue@3.4.4)
dev: false
/pinia@2.1.7(vue@3.4.3):
/pinia@2.1.7(vue@3.4.4):
resolution: {integrity: sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==}
peerDependencies:
'@vue/composition-api': ^1.4.0
@@ -5302,8 +5303,8 @@ packages:
optional: true
dependencies:
'@vue/devtools-api': 6.5.1
vue: 3.4.3
vue-demi: 0.14.6(vue@3.4.3)
vue: 3.4.4
vue-demi: 0.14.6(vue@3.4.4)
dev: false
/pkg-types@1.0.3:
@@ -5358,8 +5359,8 @@ packages:
util-deprecate: 1.0.2
dev: true
/postcss@8.4.32:
resolution: {integrity: sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==}
/postcss@8.4.33:
resolution: {integrity: sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.3.7
@@ -5749,8 +5750,8 @@ packages:
truncate-utf8-bytes: 1.0.2
dev: true
/sass@1.69.6:
resolution: {integrity: sha512-qbRr3k9JGHWXCvZU77SD2OTwUlC+gNT+61JOLcmLm+XqH4h/5D+p4IIsxvpkB89S9AwJOyb5+rWNpIucaFxSFQ==}
/sass@1.69.7:
resolution: {integrity: sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==}
engines: {node: '>=14.0.0'}
hasBin: true
dependencies:
@@ -6473,7 +6474,7 @@ packages:
- rollup
dev: true
/unplugin-vue-components@0.25.2(rollup@2.79.1)(vue@3.4.3):
/unplugin-vue-components@0.25.2(rollup@2.79.1)(vue@3.4.4):
resolution: {integrity: sha512-OVmLFqILH6w+eM8fyt/d/eoJT9A6WO51NZLf1vC5c1FZ4rmq2bbGxTy8WP2Jm7xwFdukaIdv819+UI7RClPyCA==}
engines: {node: '>=14'}
peerDependencies:
@@ -6496,7 +6497,7 @@ packages:
minimatch: 9.0.3
resolve: 1.22.8
unplugin: 1.6.0
vue: 3.4.3
vue: 3.4.4
transitivePeerDependencies:
- rollup
- supports-color
@@ -6567,13 +6568,13 @@ packages:
engines: {node: '>= 0.8'}
dev: false
/vdirs@0.1.8(vue@3.4.3):
/vdirs@0.1.8(vue@3.4.4):
resolution: {integrity: sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==}
peerDependencies:
vue: ^3.0.11
dependencies:
evtd: 0.2.4
vue: 3.4.3
vue: 3.4.4
dev: true
/verror@1.10.0:
@@ -6604,7 +6605,7 @@ packages:
chalk: 4.1.2
debug: 4.3.4
fs-extra: 10.1.0
vite: 4.5.1(sass@1.69.6)(terser@5.26.0)
vite: 4.5.1(sass@1.69.7)(terser@5.26.0)
transitivePeerDependencies:
- supports-color
dev: true
@@ -6620,14 +6621,14 @@ packages:
debug: 4.3.4
fast-glob: 3.3.2
pretty-bytes: 6.1.1
vite: 4.5.1(sass@1.69.6)(terser@5.26.0)
vite: 4.5.1(sass@1.69.7)(terser@5.26.0)
workbox-build: 7.0.0
workbox-window: 7.0.0
transitivePeerDependencies:
- supports-color
dev: true
/vite@4.5.1(sass@1.69.6)(terser@5.26.0):
/vite@4.5.1(sass@1.69.7)(terser@5.26.0):
resolution: {integrity: sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
@@ -6656,24 +6657,24 @@ packages:
optional: true
dependencies:
esbuild: 0.18.20
postcss: 8.4.32
postcss: 8.4.33
rollup: 3.29.4
sass: 1.69.6
sass: 1.69.7
terser: 5.26.0
optionalDependencies:
fsevents: 2.3.3
dev: true
/vooks@0.2.12(vue@3.4.3):
/vooks@0.2.12(vue@3.4.4):
resolution: {integrity: sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==}
peerDependencies:
vue: ^3.0.0
dependencies:
evtd: 0.2.4
vue: 3.4.3
vue: 3.4.4
dev: true
/vue-demi@0.14.6(vue@3.4.3):
/vue-demi@0.14.6(vue@3.4.4):
resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==}
engines: {node: '>=12'}
hasBin: true
@@ -6685,7 +6686,7 @@ packages:
'@vue/composition-api':
optional: true
dependencies:
vue: 3.4.3
vue: 3.4.4
dev: false
/vue-eslint-parser@9.3.2(eslint@8.56.0):
@@ -6706,46 +6707,46 @@ packages:
- supports-color
dev: true
/vue-router@4.2.5(vue@3.4.3):
/vue-router@4.2.5(vue@3.4.4):
resolution: {integrity: sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==}
peerDependencies:
vue: ^3.2.0
dependencies:
'@vue/devtools-api': 6.5.1
vue: 3.4.3
vue: 3.4.4
dev: false
/vue-slider-component@4.1.0-beta.7:
resolution: {integrity: sha512-Qb7K920ZG7PoQswoF6Ias+i3W2rd3k4fpk04JUl82kEUcN86Yg6et7bVSKWt/7VpQe8a5IT3BqCKSCOZ7AJgCA==}
dev: false
/vue@3.4.3:
resolution: {integrity: sha512-GjN+culMAGv/mUbkIv8zMKItno8npcj5gWlXkSxf1SPTQf8eJ4A+YfHIvQFyL1IfuJcMl3soA7SmN1fRxbf/wA==}
/vue@3.4.4:
resolution: {integrity: sha512-suZXgDVT8lRNhKmxdkwOsR0oyUi8is7mtqI18qW97JLoyorEbE9B2Sb4Ws/mR/+0AgA/JUtsv1ytlRSH3/pDIA==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@vue/compiler-dom': 3.4.3
'@vue/compiler-sfc': 3.4.3
'@vue/runtime-dom': 3.4.3
'@vue/server-renderer': 3.4.3(vue@3.4.3)
'@vue/shared': 3.4.3
'@vue/compiler-dom': 3.4.4
'@vue/compiler-sfc': 3.4.4
'@vue/runtime-dom': 3.4.4
'@vue/server-renderer': 3.4.4(vue@3.4.4)
'@vue/shared': 3.4.4
/vueuc@0.4.58(vue@3.4.3):
/vueuc@0.4.58(vue@3.4.4):
resolution: {integrity: sha512-Wnj/N8WbPRSxSt+9ji1jtDHPzda5h2OH/0sFBhvdxDRuyCZbjGg3/cKMaKqEoe+dErTexG2R+i6Q8S/Toq1MYg==}
peerDependencies:
vue: ^3.0.11
dependencies:
'@css-render/vue3-ssr': 0.15.12(vue@3.4.3)
'@css-render/vue3-ssr': 0.15.12(vue@3.4.4)
'@juggle/resize-observer': 3.4.0
css-render: 0.15.12
evtd: 0.2.4
seemly: 0.3.8
vdirs: 0.1.8(vue@3.4.3)
vooks: 0.2.12(vue@3.4.3)
vue: 3.4.3
vdirs: 0.1.8(vue@3.4.4)
vooks: 0.2.12(vue@3.4.4)
vue: 3.4.4
dev: true
/webidl-conversions@4.0.2:

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
public/images/pic/video.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@@ -224,7 +224,7 @@ onUnmounted(() => {
.sider-all {
height: 100%;
}
@media (max-width: 720px) {
@media (max-width: 900px) {
display: none;
}
}

View File

@@ -97,3 +97,25 @@ export const likeMv = (t, mvid) => {
},
});
};
/**
* 全部 mv
* @param {string} area - 地区,可选值为全部,内地,港台,欧美,日本,韩国,不填则为全部
* @param {string} type - 类型,可选值为全部,官方版,原生,现场版,网易出品,不填则为全部
* @param {string} order - 排序,可选值为上升最快,最热,最新,不填则为上升最快
* @param {number} [limit=12] - 返回数量默认12
* @param {number} [offset=0] - 偏移数量默认0
*/
export const allMv = (area, type, order, limit = 12, offset = 0) => {
return axios({
method: "GET",
url: "/mv/all",
params: {
area,
type,
order,
limit,
offset,
},
});
};

View File

@@ -3,7 +3,7 @@
<div class="play-btn" @click.stop>
<n-button
:loading="playLoading"
color="#efefefde"
color="#efefef"
tag="div"
type="primary"
class="play"
@@ -30,13 +30,16 @@ import { useRouter } from "vue-router";
import { getAllPlayList } from "@/api/playlist";
import { getAlbumDetail } from "@/api/album";
import { getDjProgram } from "@/api/dj";
import { musicData, siteStatus } from "@/stores";
import { musicData, siteStatus, siteData } from "@/stores";
import { playOrPause, initPlayer } from "@/utils/Player";
import { isLogin } from "@/utils/auth";
import formatData from "@/utils/formatData";
const router = useRouter();
const data = siteData();
const music = musicData();
const status = siteStatus();
const { userLikeData } = storeToRefs(data);
const { playList, playSongData } = storeToRefs(music);
const { playIndex, playMode, playHeartbeatMode, playState } = storeToRefs(status);
@@ -58,7 +61,7 @@ const playListData = ref(null);
// 是否处于当前歌单
const isHasSongs = computed(() => {
if (!playListData.value) return -1;
if (!playListData.value || playListData.value === 400) return -1;
const songId = music.getPlaySongData?.id;
const existingIndex = playListData.value.findIndex((song) => song.id === songId);
return existingIndex;
@@ -71,8 +74,16 @@ const getPlaylistData = async () => {
// 按列表类别获取数据
switch (props.type) {
case "playlist": {
const result = await getAllPlayList(props.id, 500);
return formatData(result.songs, "song");
if (props.id === 1024) {
console.log("播放我喜欢的音乐");
const id = userLikeData.value.playlists?.[0]?.id || null;
if (!isLogin() || !id) return 400;
const result = await getAllPlayList(id, 500);
return formatData(result.songs, "song");
} else {
const result = await getAllPlayList(props.id, 500);
return formatData(result.songs, "song");
}
}
case "album": {
const result = await getAlbumDetail(props.id);
@@ -103,6 +114,10 @@ const playAllSongs = async () => {
playLoading.value = false;
return $message.error("获取播放列表时出现错误");
}
if (playListData.value === 400) {
playLoading.value = false;
return $message.error("请登录后使用");
}
console.log("不在歌单内");
// 更改模式和歌单
playHeartbeatMode.value = false;

View File

@@ -36,7 +36,17 @@
>
<template #placeholder>
<div :class="['cover-loading', type]">
<img class="loading-img" src="/images/pic/album.jpg?assest" alt="song" />
<img
class="loading-img"
:src="
type === 'mv'
? '/images/pic/video.png?assest'
: type === 'artist'
? '/images/pic/artist.jpg?assest'
: '/images/pic/album.jpg?assest'
"
alt="song"
/>
</div>
<!-- <div :class="['cover-loading', type]">
<n-spin size="small" />

View File

@@ -33,9 +33,7 @@
</template>
</n-image>
<!-- 播放按钮 -->
<n-icon v-if="showIcon" class="play" @click.stop>
<SvgIcon icon="play-circle" />
</n-icon>
<CoverPlayBtn v-if="showIcon" :id="data.id" class="play" />
<!-- 日期 -->
<div v-if="showDate" class="cover-date">
<n-icon class="date-icon">
@@ -145,15 +143,24 @@ const props = defineProps({
.play {
position: absolute;
opacity: 0;
font-size: 60px;
color: #ffffffe6;
transform: scale(0.8);
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition:
transform 0.3s,
opacity 0.3s;
cursor: pointer;
:deep(.play) {
--n-width: 50px;
--n-height: 50px;
--n-font-size: 20px;
.n-icon {
font-size: 60px !important;
}
}
&:hover {
transform: scale(1.2);
transform: scale(1.1);
}
&:active {
transform: scale(1);
@@ -212,7 +219,6 @@ const props = defineProps({
}
&:hover {
background-color: var(--n-close-color-hover);
transform: translate3d(-2px, 0, 0);
.cover-main-img {
filter: brightness(0.8);
}

View File

@@ -123,15 +123,18 @@
<script setup>
import { NText, NIcon } from "naive-ui";
import { storeToRefs } from "pinia";
import { musicData, siteStatus } from "@/stores";
import { musicData, siteStatus, siteSettings } from "@/stores";
import { initPlayer, fadePlayOrPause, changePlayIndex, soundStop } from "@/utils/Player";
import SvgIcon from "@/components/Global/SvgIcon";
import debounce from "@/utils/debounce";
const music = musicData();
const status = siteStatus();
const settings = siteSettings();
const { useMusicCache } = storeToRefs(settings);
const { playSongData, playList } = storeToRefs(music);
const { coverTheme, showFullPlayer, playListShow, playIndex, playMode } = storeToRefs(status);
const { coverTheme, showFullPlayer, playListShow, playIndex, playMode, playLoading } =
storeToRefs(status);
const playListRef = ref(null);
@@ -158,6 +161,11 @@ const playlistOpen = () => {
// 播放歌曲
const playSong = debounce(async (song, index) => {
// 若开启了缓存且正在加载
if (useMusicCache.value && playLoading.value) {
$message.warning("歌曲正在缓冲中,请稍后");
return false;
}
// 更改模式
if (playMode.value !== "dj") playMode.value = "normal";
// 更改播放索引
@@ -352,6 +360,7 @@ const removeSong = async (index) => {
.main-playlist {
width: 400px !important;
border-radius: 12px 0 0 12px;
transition: width 0.3s;
.n-drawer-header {
height: 70px;
box-sizing: border-box;
@@ -380,5 +389,9 @@ const removeSong = async (index) => {
}
}
}
@media (max-width: 700px) {
width: 100% !important;
border-radius: 0;
}
}
</style>

View File

@@ -4,16 +4,19 @@
<div v-if="data?.[0]?.id" class="song-list">
<div v-if="showTitle" class="song-list-header">
<n-text class="num" depth="3"> # </n-text>
<n-text :class="{ info: true, 'has-cover': data[0].cover && showCover }" depth="3">
<n-text :class="['info', { 'has-cover': data[0].cover && showCover }]" depth="3">
{{ type === "song" ? "歌曲" : "声音" }}
</n-text>
<n-text v-if="data[0].album && showAlbum" class="album" depth="3"> 专辑 </n-text>
<n-text v-if="data[0].updateTime && type === 'dj'" class="update" depth="3">
<n-text v-if="data[0].album && showAlbum" class="album hidden" depth="3"> 专辑 </n-text>
<n-text v-if="data[0].updateTime && type === 'dj'" class="update hidden" depth="3">
更新日期
</n-text>
<n-text v-if="data[0].playCount && type === 'dj'" class="count" depth="3"> 播放量 </n-text>
<n-text v-if="data[0].duration" class="duration" depth="3"> 时长 </n-text>
<n-text v-if="data[0].size" class="size" depth="3"> 大小 </n-text>
<n-text v-if="type !== 'dj'" class="control" />
<n-text v-if="data[0].playCount && type === 'dj'" class="count hidden" depth="3">
播放量
</n-text>
<n-text v-if="data[0].duration" class="duration hidden" depth="3"> 时长 </n-text>
<n-text v-if="data[0].size" class="size hidden" depth="3"> 大小 </n-text>
</div>
<n-card
v-for="(item, index) in data.slice(
@@ -31,6 +34,7 @@
}"
:class="music.getPlaySongData?.id === item?.id ? 'songs play' : 'songs'"
hoverable
@click="checkCanClick(data, item, songsIndex + index)"
@dblclick.stop="playSong(data, item, songsIndex + index)"
@contextmenu="
songListDropdownRef?.openDropdown($event, data, item, songsIndex + index, sourceId, type)
@@ -40,7 +44,7 @@
<n-text v-if="music.getPlaySongData?.id !== item?.id" class="num" depth="3">
{{ songsIndex + index + 1 }}
</n-text>
<n-icon v-else class="play" size="22">
<n-icon v-else class="num" size="22">
<SvgIcon icon="music-note" />
</n-icon>
<!-- 封面 -->
@@ -67,7 +71,10 @@
<div class="info">
<div class="title">
<!-- 名称 -->
<n-text class="name" depth="2">{{ item?.name || "未知曲目" }}</n-text>
<!-- @click.stop="type !== 'dj' && !item.path ? router.push(`/song?id=${item.id}`) : null" -->
<n-text class="name" depth="2">
{{ item?.name || "未知曲目" }}
</n-text>
<!-- 特权 -->
<n-tag
v-if="showPrivilege && item.fee === 1 && userData.detail?.profile?.vipType !== 11"
@@ -140,7 +147,7 @@
<template v-if="showAlbum && type !== 'dj'">
<n-text
v-if="item.album"
class="album"
class="album hidden"
@click.stop="
typeof item.album === 'object' ? router.push(`/album?id=${item.album.id}`) : null
"
@@ -148,7 +155,7 @@
>
{{ typeof item.album === "object" ? item.album?.name || "未知专辑" : item.album }}
</n-text>
<n-text v-else class="album">未知专辑</n-text>
<n-text v-else class="album hidden">未知专辑</n-text>
</template>
<!-- 操作 -->
<div v-if="type !== 'dj'" class="action">
@@ -168,20 +175,32 @@
"
/>
</n-icon>
<!-- 更多操作 -->
<n-icon
class="more mobile"
depth="3"
size="20"
@click.stop="
songListDrawerRef?.drawerOpen(data, item, songsIndex + index, sourceId, type)
"
@dblclick.stop
>
<SvgIcon icon="more" />
</n-icon>
</div>
<!-- 更新日期 -->
<n-text v-if="type === 'dj' && item.updateTime" class="update" depth="3">
<n-text v-if="type === 'dj' && item.updateTime" class="update hidden" depth="3">
{{ getTimestampTime(item.updateTime, false) }}
</n-text>
<!-- 播放量 -->
<n-text v-if="type === 'dj' && item.playCount" class="count" depth="3">
<n-text v-if="type === 'dj' && item.playCount" class="count hidden" depth="3">
{{ item.playCount }}次
</n-text>
<!-- 时长 -->
<n-text v-if="item.duration" class="duration" depth="3">{{ item.duration }}</n-text>
<n-text v-if="item.duration" class="duration hidden" depth="3">{{ item.duration }}</n-text>
<n-text v-else class="duration"> -- </n-text>
<!-- 大小 -->
<n-text v-if="item.size" class="size" depth="3">{{ item.size }}M</n-text>
<n-text v-if="item.size" class="size hidden" depth="3">{{ item.size }}M</n-text>
</n-card>
<!-- 分页 -->
<Pagination
@@ -191,7 +210,21 @@
@pageNumberChange="pageNumberChange"
/>
<!-- 右键菜单 -->
<SongListDropdown ref="songListDropdownRef" @playSong="playSong" />
<SongListDropdown
ref="songListDropdownRef"
@playSong="playSong"
@delCloudSong="delCloudSong"
@deletePlaylistSong="deletePlaylistSong"
@delLocalSong="delLocalSong"
/>
<!-- 移动端菜单 -->
<SongListDrawer
ref="songListDrawerRef"
@playSong="playSong"
@delCloudSong="delCloudSong"
@deletePlaylistSong="deletePlaylistSong"
@delLocalSong="delLocalSong"
/>
<!-- 定位歌曲 -->
<Transition name="shrink" mode="out-in">
<n-card
@@ -227,15 +260,17 @@
size="large"
/>
<!-- 加载动画 -->
<n-spin v-else class="loading" size="small">
<template #description> 加载中 </template>
</n-spin>
<div v-else class="loading">
<n-skeleton :repeat="10" text />
</div>
</Transition>
</template>
<script setup>
import { storeToRefs } from "pinia";
import { useRouter } from "vue-router";
import { setCloudDel } from "@/api/cloud";
import { addSongToPlayList } from "@/api/playlist";
import { siteData, siteSettings, musicData, siteStatus } from "@/stores";
import { initPlayer, fadePlayOrPause, addSongToNext } from "@/utils/Player";
import { getTimestampTime } from "@/utils/timeTools";
@@ -246,9 +281,9 @@ const dataStore = siteData();
const status = siteStatus();
const settings = siteSettings();
const { userData } = storeToRefs(dataStore);
const { playIndex, playMode, playHeartbeatMode } = storeToRefs(status);
const { loadSize, playSearch } = storeToRefs(settings);
const { loadSize, playSearch, useMusicCache } = storeToRefs(settings);
const { playList, playSongData, playSongSource } = storeToRefs(music);
const { playIndex, playMode, playHeartbeatMode, playLoading } = storeToRefs(status);
// eslint-disable-next-line no-unused-vars
const props = defineProps({
@@ -297,7 +332,8 @@ const props = defineProps({
// 分页数据
const pageNumber = ref(1);
// 右键菜单
// 子组件
const songListDrawerRef = ref(null);
const songListDropdownRef = ref(null);
// 当前索引
@@ -324,6 +360,11 @@ const checkHasPlaying = (isScoll = null) => {
// 播放歌曲
const playSong = async (data, song, index) => {
console.log(data, song, index);
// 若开启了缓存且正在加载
if (useMusicCache.value && playLoading.value) {
$message.warning("歌曲正在缓冲中,请稍后");
return false;
}
// 更改模式
playMode.value = props.type === "song" ? "normal" : "dj";
// 检查当前页面
@@ -370,6 +411,71 @@ const pageNumberChange = (page) => {
});
};
// 检查是否可执行双击
const checkCanClick = (data, item, index) => {
if (window.innerWidth > 700) return false;
playSong(data, item, index);
};
// 云盘歌曲删除
const delCloudSong = (data, song, index) => {
console.log(data, song, index);
$dialog.warning({
title: "确认删除",
content: `确认从云盘中删除 ${song.name}?该操作无法撤销!`,
positiveText: "删除",
negativeText: "取消",
onPositiveClick: async () => {
const result = await setCloudDel(song.id);
if (result.code == 200) {
data.splice(index, 1);
$message.success("删除成功");
} else {
$message.error("删除失败,请重试");
}
},
});
};
// 歌单歌曲删除
const deletePlaylistSong = (pid, song, data, index) => {
$dialog.warning({
title: "确认删除",
content: `确认从歌单中移除 ${song.name}?该操作无法撤销!`,
positiveText: "删除",
negativeText: "取消",
onPositiveClick: async () => {
const result = await addSongToPlayList(pid, song?.id, "del");
if (result.status === 200) {
data.length === 1 ? data.splice(0, 1, "empty") : data.splice(index, 1);
$message.success("歌曲删除成功");
} else {
$message.error("歌曲删除失败,请重试");
}
},
});
};
// 本地歌曲删除
const delLocalSong = (data, song, index) => {
$dialog.warning({
title: "确认删除",
content: `确认从本地磁盘中删除 ${song.name}?该操作无法撤销!`,
positiveText: "删除",
negativeText: "取消",
onPositiveClick: async () => {
console.log(data, song, index);
const result = await electron.ipcRenderer.invoke("deleteFile", song?.path);
if (result) {
data.length === 1 ? data.splice(0, 1, "empty") : data.splice(index, 1);
$message.success("歌曲删除成功");
} else {
$message.error("歌曲删除失败,请重试");
}
},
});
};
// 监听歌曲变化
watch(
() => music.getPlaySongData?.id,
@@ -409,16 +515,20 @@ onBeforeUnmount(() => {
.has-cover {
margin-right: 66px;
}
.control {
width: 40px;
}
.update {
width: 80px;
text-align: center;
margin-right: auto;
}
.count {
width: 120px;
text-align: center;
}
.duration {
width: 40px;
width: 50px;
text-align: center;
}
.size {
@@ -459,8 +569,7 @@ onBeforeUnmount(() => {
}
}
}
.num,
.play {
.num {
width: 30px;
height: 30px;
min-width: 30px;
@@ -564,6 +673,9 @@ onBeforeUnmount(() => {
transform: scale(1);
}
}
.more {
display: none;
}
}
.update {
width: 80px;
@@ -574,7 +686,7 @@ onBeforeUnmount(() => {
text-align: center;
}
.duration {
width: 40px;
width: 50px;
text-align: center;
}
.size {
@@ -636,12 +748,49 @@ onBeforeUnmount(() => {
transform: scale(0.9);
}
}
@media (max-width: 700px) {
.song-list-header,
.songs {
.hidden {
display: none;
}
}
.songs {
.num {
font-size: 12px;
width: 28px;
height: 28px;
min-width: 28px;
}
.info {
.title {
.name {
font-size: 15px;
}
}
.artist {
font-size: 12px;
}
}
.action {
width: 60px;
justify-content: flex-end;
.more {
display: inline-block;
margin-left: 12px;
}
}
}
}
}
.loading {
margin: 60px 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
:deep(.n-skeleton) {
&:nth-of-type(1) {
margin-top: 0;
}
height: 80px;
margin-top: 12px;
border-radius: 8px;
}
}
</style>

View File

@@ -0,0 +1,323 @@
<!-- 歌曲列表 - 移动端菜单 -->
<template>
<n-drawer
v-model:show="drawerShow"
:auto-focus="false"
height="calc(100vh - 200px)"
placement="bottom"
class="song-list-drawer"
@after-leave="drawerShow = false"
@mask-click="drawerShow = false"
>
<n-drawer-content :native-scrollbar="false" :body-content-style="{ padding: 0 }" closable>
<template #header>
<div v-if="!songData?.path" class="song-data">
<n-image
:src="songData?.coverSize?.s || songData?.cover"
class="cover"
preview-disabled
/>
<div class="song-detail">
<n-text class="name">{{ songData?.name || "未知曲目" }}</n-text>
<template v-if="songType === 'song'">
<div v-if="songData?.artists && Array.isArray(songData.artists)" class="all-ar">
<n-text v-for="ar in songData.artists" :key="ar.id" class="ar" depth="3">
{{ ar.name }}
</n-text>
</div>
<div v-else class="all-ar">
<n-text class="ar" depth="3">
{{ songData.artists || "未知艺术家" }}
</n-text>
</div>
</template>
<n-text v-else class="ar">
{{ songData?.artists || "未知艺术家" }}
</n-text>
</div>
</div>
<n-text v-else>更多操作</n-text>
</template>
<div class="all-menu">
<div
class="menu-item"
@click="
() => {
drawerShow = false;
emit('playSong', playlistData, songData, songIndex);
}
"
>
<n-icon size="22">
<SvgIcon icon="play" />
</n-icon>
<n-text class="name"> 立即播放 </n-text>
</div>
<div
v-if="isSong && playMode !== 'dj' && music.getPlaySongData?.id !== songData.id && !isFm"
class="menu-item"
@click="
() => {
drawerShow = false;
playMode = 'song';
addSongToNext(songData);
}
"
>
<n-icon size="22">
<SvgIcon icon="play-next" />
</n-icon>
<n-text class="name"> 下一首播放 </n-text>
</div>
<div
v-if="isSong && !isLocalSong"
class="menu-item"
@click="
() => {
drawerShow = false;
addPlaylistRef?.openAddToPlaylist(songData?.id);
}
"
>
<n-icon size="22">
<SvgIcon icon="playlist-add" />
</n-icon>
<n-text class="name"> 添加到歌单 </n-text>
</div>
<div
v-if="isSong && !isLocalSong"
class="menu-item"
@click="
() => {
drawerShow = false;
router.push({
path: '/comment',
query: {
id: songData.id,
},
});
}
"
>
<n-icon size="20">
<SvgIcon icon="comment-text" />
</n-icon>
<n-text class="name"> 查看评论 </n-text>
</div>
<div
v-if="isSong && isHasMv"
class="menu-item"
@click="
() => {
drawerShow = false;
router.push({
path: '/videos-player',
query: {
id: songData.mv,
},
});
}
"
>
<n-icon size="22">
<SvgIcon icon="video" />
</n-icon>
<n-text class="name"> 观看 MV </n-text>
</div>
<div
v-if="!isCloud && isUserPlaylist"
class="menu-item"
@click="
() => {
drawerShow = false;
emit('deletePlaylistSong', playlistData, songData, songIndex);
}
"
>
<n-icon size="22">
<SvgIcon icon="delete" />
</n-icon>
<n-text class="name"> 从歌单中删除 </n-text>
</div>
<div
v-if="isCloud"
class="menu-item"
@click="
() => {
drawerShow = false;
emit('delCloudSong', playlistData, songData, songIndex);
}
"
>
<n-icon size="22">
<SvgIcon icon="delete" />
</n-icon>
<n-text class="name"> 从云盘中删除 </n-text>
</div>
<div
v-if="isCloud"
class="menu-item"
@click="
() => {
drawerShow = false;
cloudSongMatchRef?.openCloudSongMatch(songData, songIndex);
}
"
>
<n-icon size="22">
<SvgIcon icon="edit" />
</n-icon>
<n-text class="name"> 云盘歌曲纠正 </n-text>
</div>
<div
v-if="isSong && !isLocalSong"
class="menu-item"
@click="
() => {
drawerShow = false;
downloadSongRef?.openDownloadModal(songData);
}
"
>
<n-icon size="22">
<SvgIcon icon="download" />
</n-icon>
<n-text class="name"> 下载歌曲 </n-text>
</div>
</div>
</n-drawer-content>
</n-drawer>
<!-- 添加到歌单 -->
<AddPlaylist ref="addPlaylistRef" />
<!-- 下载歌曲 -->
<DownloadSong ref="downloadSongRef" />
<!-- 云盘歌曲纠正 -->
<CloudSongMatch ref="cloudSongMatchRef" />
</template>
<script setup>
import { storeToRefs } from "pinia";
import { useRouter } from "vue-router";
import { addSongToNext } from "@/utils/Player";
import { musicData, siteData, siteStatus } from "@/stores";
const router = useRouter();
const data = siteData();
const music = musicData();
const status = siteStatus();
const { playMode } = storeToRefs(status);
const { userData, userLikeData } = storeToRefs(data);
const emit = defineEmits(["playSong", "delCloudSong", "deletePlaylistSong", "delLocalSong"]);
// 子组件
const addPlaylistRef = ref(null);
const downloadSongRef = ref(null);
const cloudSongMatchRef = ref(null);
// 菜单数据
const drawerShow = ref(false);
const songType = ref("song");
const songData = ref(null);
const songIndex = ref(null);
const songSourceId = ref(null);
const playlistData = ref(null);
// 歌曲状态
const isFm = computed(() => playMode.value === "fm");
const isSong = computed(() => songType.value === "song");
const isLocalSong = computed(() => !!songData.value?.path);
const isHasMv = computed(() => !!songData.value?.mv && songData.value.mv !== 0);
const isCloud = computed(() => router.currentRoute.value.name === "cloud");
const isUserPlaylist = computed(() => {
// 用户 id
const userId = userData.value?.userId;
// 用户歌单
const userPlaylistsData = userLikeData.value.playlists?.filter(
(playlist) => playlist.userId === userId,
);
return songSourceId.value !== 0 && userPlaylistsData.some((pl) => pl.id == songSourceId.value);
});
// 开启菜单
const drawerOpen = (data, song, index, sourceId, type) => {
console.log(song, type);
drawerShow.value = true;
songData.value = song;
songType.value = type;
songIndex.value = index;
songSourceId.value = sourceId;
playlistData.value = data;
};
defineExpose({
drawerOpen,
});
</script>
<style lang="scss" scoped>
.song-data {
display: flex;
flex-direction: row;
align-items: center;
.cover {
margin-right: 12px;
width: 50px;
height: 50px;
border-radius: 8px;
}
.song-detail {
.name {
font-size: 16px;
margin-bottom: 8px;
}
.all-ar {
margin-top: 4px;
font-size: 13px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
word-break: break-all;
.ar {
display: inline-flex;
&::after {
content: "/";
margin: 0 4px;
}
&:last-child {
&::after {
display: none;
}
}
}
}
}
}
.all-menu {
.menu-item {
padding: 12px 24px;
display: flex;
align-items: center;
flex-direction: row;
transition: background-color 0.3s;
cursor: pointer;
.n-icon {
margin-right: 8px;
}
.name {
transform: translateY(1px);
display: flex;
flex-direction: row;
}
&:hover {
background-color: var(--n-close-color-hover);
}
}
}
</style>
<style lang="scss">
.song-list-drawer {
border-radius: 8px 8px 0 0;
}
</style>

View File

@@ -27,18 +27,15 @@ import { storeToRefs } from "pinia";
import { musicData, siteData, siteStatus } from "@/stores";
import { useRouter } from "vue-router";
import { addSongToNext } from "@/utils/Player";
import { setCloudDel } from "@/api/cloud";
import { addSongToPlayList } from "@/api/playlist";
import { copyData } from "@/utils/helper";
import SvgIcon from "@/components/Global/SvgIcon";
const emit = defineEmits(["playSong"]);
const emit = defineEmits(["playSong", "delCloudSong", "deletePlaylistSong", "delLocalSong"]);
const data = siteData();
const music = musicData();
const router = useRouter();
const status = siteStatus();
const { playMode } = storeToRefs(status);
const { playSongData } = storeToRefs(music);
const { userData, userLikeData } = storeToRefs(data);
// 右键菜单数据
@@ -74,7 +71,11 @@ const renderSong = (song, isSong) => {
className: "song-data",
},
[
h(NImage, { src: song?.coverSize?.s || song?.cover, class: "cover" }),
h(NImage, {
src: song?.coverSize?.s || song?.cover,
class: "cover",
previewDisabled: true,
}),
h("div", { class: "song-detail" }, [
h(NText, { class: "name" }, () => [song?.name || "未知曲目"]),
isSong
@@ -111,8 +112,8 @@ const openDropdown = (e, data, song, index, sourceId, type) => {
// 当前状态
const isFm = playMode.value === "fm";
const isSong = type === "song";
const isLocalSong = song?.path ? true : false;
const isHasMv = song.mv && song.mv !== 0 ? true : false;
const isLocalSong = !!song?.path;
const isHasMv = !!song?.mv && song.mv !== 0;
const isCloud = router.currentRoute.value.name === "cloud";
const isUserPlaylist = sourceId !== 0 && userPlaylistsData.some((pl) => pl.id == sourceId);
// 生成菜单
@@ -143,7 +144,7 @@ const openDropdown = (e, data, song, index, sourceId, type) => {
{
key: "next-play",
label: "下一首播放",
show: isSong && playMode.value !== "dj" && playSongData.value?.id !== song.id && !isFm,
show: isSong && playMode.value !== "dj" && music.getPlaySongData?.id !== song.id && !isFm,
props: {
onClick: () => {
playMode.value = "song";
@@ -239,7 +240,7 @@ const openDropdown = (e, data, song, index, sourceId, type) => {
show: !isCloud && isUserPlaylist,
props: {
onClick: () => {
deletePlaylistSong(sourceId, song, data, index);
emit("deletePlaylistSong", data, song, index);
},
},
icon: renderIcon("delete"),
@@ -261,7 +262,7 @@ const openDropdown = (e, data, song, index, sourceId, type) => {
show: isCloud,
props: {
onClick: () => {
delCloudSong(data, song, index);
emit("delCloudSong", data, song, index);
},
},
icon: renderIcon("delete"),
@@ -280,10 +281,10 @@ const openDropdown = (e, data, song, index, sourceId, type) => {
{
key: "delete",
label: "从本地磁盘中删除",
show: isLocalSong && playSongData.value?.id !== song.id,
show: isLocalSong && music.getPlaySongData?.id !== song.id,
props: {
onClick: () => {
delLocalSong(data, song, index);
emit("delLocalSong", data, song, index);
},
},
icon: renderIcon("delete"),
@@ -330,65 +331,6 @@ const openDropdown = (e, data, song, index, sourceId, type) => {
}
};
// 云盘歌曲删除
const delCloudSong = (data, song, index) => {
console.log(data, song, index);
$dialog.warning({
title: "确认删除",
content: `确认从云盘中删除 ${song.name}?该操作无法撤销!`,
positiveText: "删除",
negativeText: "取消",
onPositiveClick: async () => {
const result = await setCloudDel(song.id);
if (result.code == 200) {
data.splice(index, 1);
$message.success("删除成功");
} else {
$message.error("删除失败,请重试");
}
},
});
};
// 歌单歌曲删除
const deletePlaylistSong = (pid, song, data, index) => {
$dialog.warning({
title: "确认删除",
content: `确认从歌单中移除 ${song.name}?该操作无法撤销!`,
positiveText: "删除",
negativeText: "取消",
onPositiveClick: async () => {
const result = await addSongToPlayList(pid, song?.id, "del");
if (result.status === 200) {
data.length === 1 ? data.splice(0, 1, "empty") : data.splice(index, 1);
$message.success("歌曲删除成功");
} else {
$message.error("歌曲删除失败,请重试");
}
},
});
};
// 本地歌曲删除
const delLocalSong = (data, song, index) => {
$dialog.warning({
title: "确认删除",
content: `确认从本地磁盘中删除 ${song.name}?该操作无法撤销!`,
positiveText: "删除",
negativeText: "取消",
onPositiveClick: async () => {
console.log(data, song, index);
const result = await electron.ipcRenderer.invoke("deleteFile", song?.path);
if (result) {
data.length === 1 ? data.splice(0, 1, "empty") : data.splice(index, 1);
$message.success("歌曲删除成功");
} else {
$message.error("歌曲删除失败,请重试");
}
},
});
};
defineExpose({
openDropdown,
});

View File

@@ -78,7 +78,7 @@
</n-card>
</Transition>
<template #footer>
<n-space justify="end">
<n-flex justify="end">
<n-button @click="closeCloudSongMatch"> 取消 </n-button>
<n-button
:disabled="!cloudMatchValue.asid"
@@ -87,7 +87,7 @@
>
纠正
</n-button>
</n-space>
</n-flex>
</template>
</n-modal>
</template>

View File

@@ -21,7 +21,7 @@
<!-- 隐私歌单 -->
<n-checkbox v-model:checked="createPrivacy"> 设为隐私歌单 </n-checkbox>
<template #footer>
<n-space justify="end">
<n-flex justify="end">
<n-button @click="closeCreatePlaylist"> 取消 </n-button>
<n-button
:disabled="!createName"
@@ -30,7 +30,7 @@
>
新建
</n-button>
</n-space>
</n-flex>
</template>
</n-modal>
</template>

View File

@@ -17,7 +17,7 @@
当前为云盘歌曲下载的文件均为最高音质
</n-alert>
<n-radio-group v-model:value="downloadChoose" class="download-group" name="downloadGroup">
<n-space vertical>
<n-flex vertical>
<n-radio
v-for="item in downloadLevel"
:key="item"
@@ -32,13 +32,13 @@
</n-text>
</div>
</n-radio>
</n-space>
</n-flex>
</n-radio-group>
</div>
<n-text v-else>歌曲信息获取中</n-text>
</Transition>
<template #footer>
<n-space justify="end">
<n-flex justify="end">
<n-button @click="closeDownloadModal"> 关闭 </n-button>
<n-button
:disabled="!downloadChoose"
@@ -48,7 +48,7 @@
>
下载
</n-button>
</n-space>
</n-flex>
</template>
</n-modal>
</template>

View File

@@ -2,13 +2,12 @@
<template>
<n-modal
v-model:show="loginModalShow"
style="width: 400px"
class="login"
:auto-focus="false"
:mask-closable="false"
:bordered="false"
:close-on-esc="false"
:closable="false"
style="width: 400px"
preset="card"
transform-origin="center"
>

View File

@@ -40,10 +40,10 @@
</n-form-item>
</n-form>
<template #footer>
<n-space justify="end">
<n-flex justify="end">
<n-button @click="closeUpdateModal"> 取消 </n-button>
<n-button type="primary" @click="toUpdatePlayList"> 编辑 </n-button>
</n-space>
</n-flex>
</template>
</n-modal>
</template>

View File

@@ -30,7 +30,7 @@
</n-text>
</Transition>
</div>
<div class="navigation">
<n-flex :class="['navigation', { hidden: searchInputFocus }]" :size="6">
<n-button :focusable="false" class="nav-icon" quaternary @click="router.go(-1)">
<template #icon>
<n-icon>
@@ -45,7 +45,7 @@
</n-icon>
</template>
</n-button>
</div>
</n-flex>
<!-- 搜索框 -->
<SearchInp />
<!-- GitHub -->
@@ -69,7 +69,6 @@
<div class="right">
<!-- 全局菜单 -->
<n-dropdown
v-if="!showSider"
:show="mainMenuShow"
:show-arrow="true"
:options="mainMenuOptions"
@@ -78,7 +77,7 @@
>
<n-button
:style="{ pointerEvents: mainMenuShow ? 'none' : 'auto' }"
class="main-menu"
:class="['main-menu', { show: !showSider }]"
secondary
strong
round
@@ -111,7 +110,7 @@ import packageJson from "@/../package.json";
const router = useRouter();
const status = siteStatus();
const settings = siteSettings();
const { asideMenuCollapsed } = storeToRefs(status);
const { asideMenuCollapsed, searchInputFocus } = storeToRefs(status);
const { showGithub, showSider, themeAutoCover } = storeToRefs(settings);
// 站点信息
@@ -192,18 +191,32 @@ const mainMenuOptions = computed(() => [
}
}
.navigation {
margin-right: 12px;
display: flex;
flex-direction: row;
justify-content: flex-start;
height: 34px;
width: 86px;
min-width: 86px;
transition:
width 0.3s,
min-width 0.3s,
opacity 0.3s;
overflow: hidden;
-webkit-app-region: no-drag;
.nav-icon {
border-radius: 8px;
padding: 0 8px;
&:first-child {
margin-right: 6px;
}
.n-icon {
font-size: 24px;
}
}
@media (max-width: 700px) {
&.hidden {
opacity: 0;
width: 0px;
min-width: 0px;
}
}
}
.github {
margin-left: 12px;
@@ -212,7 +225,15 @@ const mainMenuOptions = computed(() => [
.main-menu {
-webkit-app-region: no-drag;
margin-right: 12px;
display: none;
&.show {
display: flex;
}
@media (max-width: 900px) {
display: flex;
}
}
&.no-sider {
max-width: 1400px;
margin: 0 auto;
@@ -226,5 +247,25 @@ const mainMenuOptions = computed(() => [
margin-right: 12px;
}
}
@media (max-width: 900px) {
.left {
.logo {
width: auto;
padding-left: 0;
margin-right: 12px;
.site-name {
display: none;
}
}
}
}
@media (max-width: 700px) {
.left {
width: 100%;
}
.github {
display: none;
}
}
}
</style>

View File

@@ -219,6 +219,15 @@ const userMenuSelect = (key) => {
margin-left: 2px;
}
}
@media (max-width: 700px) {
padding: 0;
.avatar {
margin: 0;
}
.user-data {
display: none;
}
}
}
</style>

View File

@@ -91,6 +91,14 @@ const pointOpacity = (index) => {
margin-right: 12px;
border-radius: 50%;
background-color: var(--cover-main-color);
@media (max-width: 900px) {
width: 24px;
height: 24px;
}
@media (max-width: 700px) {
width: 20px;
height: 20px;
}
}
}
@keyframes breathe {

View File

@@ -11,7 +11,7 @@
}"
class="full-player"
@mousemove="controlShowChange"
@mouseleave="playerControlShow = false"
@mouseleave="closePlayerControlShow"
>
<!-- 遮罩 -->
<Transition name="fade" mode="out-in">
@@ -55,7 +55,7 @@
</div>
<div class="right">
<!-- 全屏切换 -->
<n-icon @click.stop="screenfullChange">
<n-icon class="hidden" @click.stop="screenfullChange">
<SvgIcon
:icon="screenfullStatus ? 'fullscreen-exit-rounded' : 'fullscreen-rounded'"
/>
@@ -77,7 +77,7 @@
<!-- 封面 -->
<PlayerCover />
<!-- 信息 -->
<div v-if="playCoverType === 'cover' || !isHasLrc" :class="['data', playCoverType]">
<div v-show="playCoverType === 'cover' || !isHasLrc" :class="['data', playCoverType]">
<div class="desc">
<div class="title">
<span class="name">{{ music.getPlaySongData.name || "未知曲目" }}</span>
@@ -272,9 +272,16 @@ const screenfullChange = () => {
}
};
// 关闭控制中心
const closePlayerControlShow = () => {
if (window.innerWidth <= 700) return false;
playerControlShow.value = false;
};
// 控制中心显隐
const controlShowChange = throttle(() => {
playerControlShow.value = true;
if (window.innerWidth <= 700) return false;
if (controlTimeOut.value) {
clearTimeout(controlTimeOut.value);
}
@@ -655,6 +662,41 @@ onUnmounted(() => {
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
}
@media (max-width: 700px) {
.menu {
.hidden {
display: none;
}
}
.main-player {
.content {
width: 100%;
.data {
display: block !important;
&.record {
margin-top: 0;
}
}
&.no-lrc {
transform: translateX(0);
}
}
.right {
display: none;
.data {
.name {
font-size: 24px;
.name-alias {
font-size: 16px;
}
}
.other {
font-size: 14px;
}
}
}
}
}
}
// 局外样式
.title-tip {

View File

@@ -493,8 +493,8 @@ onMounted(() => {
}
&.record,
&.pure {
height: calc(100vh - 340px);
margin-bottom: 20px;
height: calc(100vh - 300px);
margin-bottom: 40px;
.lrc-line {
margin-bottom: -12px;
transform: scale(0.76);
@@ -503,5 +503,21 @@ onMounted(() => {
}
}
}
@media (max-width: 700px) {
:deep(.n-scrollbar-content) {
padding: 0 20px !important;
}
.lrc-line {
.lrc-content {
font-size: 6.5vw !important;
}
.lrc-fy {
font-size: 4.5vw !important;
}
.lrc-roma {
font-size: 4vw !important;
}
}
}
}
</style>

View File

@@ -209,7 +209,7 @@
<Transition name="fade" mode="out-in">
<div :key="playMode" class="menu">
<!-- 时间进度 -->
<div class="time">
<div class="time hidden">
<n-text class="played" depth="3">{{ playTimeData.played }}</n-text>
<n-text depth="3">{{ playTimeData.durationTime }}</n-text>
</div>
@@ -221,7 +221,7 @@
trigger="hover"
@select="playModeChange"
>
<div class="mode" @click.stop @dblclick.stop>
<div class="mode hidden" @click.stop @dblclick.stop>
<n-icon size="22">
<SvgIcon
:icon="
@@ -241,7 +241,7 @@
<!-- 倍速 -->
<n-popover :show-arrow="false" trigger="hover" placement="top-end" raw>
<template #trigger>
<div class="speed" @click.stop="(playRate = 1), setRate(1)" @dblclick.stop>
<div class="speed hidden" @click.stop="(playRate = 1), setRate(1)" @dblclick.stop>
<n-icon v-if="playRate === 1" size="22">
<SvgIcon icon="speed-rounded" />
</n-icon>
@@ -269,7 +269,12 @@
<!-- 音量 -->
<n-popover trigger="hover" :show-arrow="false" raw>
<template #trigger>
<n-icon class="volume" size="22" @click.stop="setVolumeMute" @wheel="changeVolume">
<n-icon
class="volume hidden"
size="22"
@click.stop="setVolumeMute"
@wheel="changeVolume"
>
<SvgIcon v-if="playVolume === 0" icon="no-sound-rounded" />
<SvgIcon
v-else-if="playVolume > 0 && playVolume < 0.4"
@@ -288,7 +293,7 @@
padding: '10px 0',
width: '50px',
}"
class="slider-content"
class="slider-content hidden"
@wheel="changeVolume"
>
<n-slider
@@ -643,12 +648,6 @@ watch(
}
}
&.record {
width: 60px;
height: 60px;
max-height: 60px;
min-height: 60px;
max-width: 60px;
min-width: 60px;
.cover-img {
display: flex;
align-items: center;
@@ -860,6 +859,31 @@ watch(
}
}
}
@media (max-width: 900px) {
.menu {
.time {
display: none;
}
}
}
@media (max-width: 700px) {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.control {
margin-left: auto;
.play-prev,
.play-next {
display: none;
}
}
.menu {
.hidden {
display: none;
}
}
}
}
&.show-bar {
bottom: 0;

View File

@@ -11,7 +11,6 @@
<!-- 喜欢歌曲 -->
<n-icon
v-if="!music.getPlaySongData.path"
class="favorite"
size="24"
@click.stop="
data.changeLikeList(
@@ -32,7 +31,7 @@
<!-- 添加到歌单 -->
<n-icon
v-if="!music.getPlaySongData.path"
class="favorite"
class="hidden"
size="24"
@click.stop="addPlaylistRef?.openAddToPlaylist(music.getPlaySongData?.id)"
>
@@ -41,7 +40,7 @@
<!-- 下载 -->
<n-icon
v-if="!music.getPlaySongData.path"
class="favorite"
class="hidden"
size="24"
@click.stop="downloadSongRef?.openDownloadModal(music.getPlaySongData)"
>
@@ -113,7 +112,7 @@
<!-- MV -->
<n-icon
v-if="music.getPlaySongData.mv"
class="favorite"
class="hidden"
size="22"
@click.stop="
(showFullPlayer = false), router.push(`/videos-player?id=${music.getPlaySongData.mv}`)
@@ -124,6 +123,7 @@
<!-- 评论 -->
<n-icon
v-if="!music.getPlaySongData?.path"
class="hidden"
size="22"
@click.stop="
(showFullPlayer = false), router.push(`/comment?id=${music.getPlaySongData?.id}`)
@@ -132,7 +132,7 @@
<SvgIcon icon="comment-text" />
</n-icon>
<!-- 播放模式 -->
<n-icon v-if="playMode === 'normal'" size="22" @click.stop="togglePlayMode">
<n-icon v-if="playMode === 'normal'" class="hidden" size="22" @click.stop="togglePlayMode">
<SvgIcon
:icon="
playHeartbeatMode
@@ -381,5 +381,21 @@ const controlMove = (e) => {
opacity: 1;
}
}
@media (max-width: 700px) {
display: flex;
align-items: center;
flex-direction: row;
justify-content: space-between;
.left,
.right {
opacity: 1;
.hidden {
display: none;
}
}
.center {
width: 100%;
}
}
}
</style>

View File

@@ -206,5 +206,18 @@ const { playState } = storeToRefs(status);
display: none;
}
}
@media (max-width: 700px) {
&.record {
.pointer {
width: 12vh;
top: -6vh;
}
.cover-img {
width: 40vh;
height: 40vh;
min-width: 40vh;
}
}
}
}
</style>

View File

@@ -2,25 +2,26 @@
<template>
<Transition name="fadeDown" mode="out-in" @before-enter="getSearchHotData">
<n-card
v-if="status.searchInputFocus && !searchValue && (data.searchHistory[0] || searchHotData[0])"
v-if="searchInputFocus && !searchValue && (searchHistory[0] || searchHotData[0])"
class="search-suggestions"
content-style="padding: 0"
@click="emit('closeSearch')"
>
<n-scrollbar class="scrollbar">
<!-- 历史记录 -->
<div v-if="settings.searchHistory && data.searchHistory[0]" class="history">
<div v-if="showSearchHistory && searchHistory[0]" class="history">
<div class="title">
<n-icon>
<SvgIcon icon="history" />
</n-icon>
<n-text>搜索历史</n-text>
<n-icon class="history-delete" depth="3" @click="delSearchHistory">
<n-icon class="history-delete" depth="3" @click.stop="delSearchHistory">
<SvgIcon icon="delete" />
</n-icon>
</div>
<n-space>
<n-flex>
<n-tag
v-for="(item, index) in data.searchHistory"
v-for="(item, index) in searchHistory"
:key="index"
:bordered="false"
round
@@ -28,7 +29,7 @@
>
{{ item }}
</n-tag>
</n-space>
</n-flex>
</div>
<!-- 热搜榜 -->
<div v-if="searchHotData[0]" class="hot-list">
@@ -70,6 +71,7 @@
</template>
<script setup>
import { storeToRefs } from "pinia";
import { siteData, siteStatus, siteSettings } from "@/stores";
import { getSearchHot } from "@/api/search";
import { getCacheData } from "@/utils/helper";
@@ -77,7 +79,10 @@ import { getCacheData } from "@/utils/helper";
const data = siteData();
const status = siteStatus();
const settings = siteSettings();
const emit = defineEmits(["toSearch", "delSearchHistory"]);
const { searchHistory } = storeToRefs(data);
const { searchInputFocus } = storeToRefs(status);
const { showSearchHistory } = storeToRefs(settings);
const emit = defineEmits(["toSearch", "closeSearch"]);
// 搜索内容
// eslint-disable-next-line no-unused-vars
@@ -117,7 +122,7 @@ const delSearchHistory = () => {
positiveText: "确认",
negativeText: "取消",
onPositiveClick: () => {
data.searchHistory = [];
searchHistory.value = [];
},
});
};
@@ -176,7 +181,7 @@ onBeforeMount(() => {
}
}
}
.n-space {
.n-flex {
margin-top: 8px;
.n-tag {
font-size: 13px;
@@ -267,5 +272,14 @@ onBeforeMount(() => {
}
}
}
@media (max-width: 512px) {
position: fixed;
top: 58px;
border-radius: 0px;
width: 100%;
:deep(.scrollbar) {
max-height: calc(100vh - 58px);
}
}
}
</style>

View File

@@ -4,13 +4,13 @@
<n-input
ref="searchInpRef"
v-model:value="searchInputValue"
:class="status.searchInputFocus ? 'input focus' : 'input'"
:class="searchInputFocus ? 'input focus' : 'input'"
:input-props="{ autoComplete: false }"
:allow-input="noSideSpace"
placeholder="搜索音乐 / 视频"
round
clearable
@focus="searchInputFocus"
@focus="searchInputToFocus"
@keyup.enter="toSearch(searchInputValue)"
@click.stop
>
@@ -22,16 +22,20 @@
</n-input>
<!-- 搜索框遮罩 -->
<Transition name="fade" mode="out-in">
<div
v-show="status.searchInputFocus"
class="search-mask"
@click.stop="status.searchInputFocus = false"
/>
<div v-show="searchInputFocus" class="search-mask" @click.stop="searchInputFocus = false" />
</Transition>
<!-- 热搜榜及历史 -->
<SearchHot :searchValue="searchInputValue?.trim()" @toSearch="toSearch" />
<SearchHot
:searchValue="searchInputValue?.trim()"
@toSearch="toSearch"
@closeSearch="closeSearch"
/>
<!-- 搜索建议 -->
<SearchSuggestions :searchValue="searchInputValue?.trim()" @toSearch="toSearch" />
<SearchSuggestions
:searchValue="searchInputValue?.trim()"
@toSearch="toSearch"
@closeSearch="closeSearch"
/>
</div>
</template>
@@ -47,7 +51,9 @@ const router = useRouter();
const music = musicData();
const status = siteStatus();
const data = siteData();
const { searchHistory } = storeToRefs(data);
const { playSongData } = storeToRefs(music);
const { searchInputFocus } = storeToRefs(status);
const searchInpRef = ref(null);
const searchInputValue = ref("");
@@ -56,24 +62,31 @@ const searchInputValue = ref("");
const noSideSpace = (value) => !value.startsWith(" ");
// 搜索框 focus
const searchInputFocus = () => {
const searchInputToFocus = () => {
searchInpRef.value?.focus();
status.searchInputFocus = true;
searchInputFocus.value = true;
};
// 添加搜索历史
const setSearchHistory = (name) => {
if (!name || !name?.trim()) return false;
const index = data.searchHistory.indexOf(name);
const index = searchHistory.value.indexOf(name);
if (index !== -1) {
data.searchHistory.splice(index, 1);
searchHistory.value.splice(index, 1);
}
data.searchHistory.unshift(name);
if (data.searchHistory.length > 30) {
data.searchHistory.pop();
searchHistory.value.unshift(name);
if (searchHistory.value.length > 30) {
searchHistory.value.pop();
}
};
// 关闭搜索
const closeSearch = () => {
// 取消聚焦状态
status.searchInputFocus = false;
searchInpRef.value?.blur();
};
// 直接播放单曲
const toPlaySong = async (id) => {
try {
@@ -96,8 +109,7 @@ const toPlaySong = async (id) => {
const toSearch = (val, type = "song") => {
if (!val) return false;
// 取消聚焦状态
status.searchInputFocus = false;
searchInpRef.value?.blur();
closeSearch();
// 触发测试
if (Number(val) === 114514) return router.push("/test");
// 判断类型
@@ -174,5 +186,21 @@ const toSearch = (val, type = "song") => {
backdrop-filter: blur(20px);
-webkit-app-region: no-drag;
}
@media (max-width: 700px) {
width: 100%;
margin-right: 12px;
.input {
width: 100%;
&.focus {
width: 100%;
}
}
}
@media (max-width: 512px) {
.search-mask {
background-color: transparent;
backdrop-filter: blur(0);
}
}
}
</style>

View File

@@ -7,13 +7,14 @@
@after-leave="changeSuggestionsHeights"
>
<n-card
v-if="status.searchInputFocus && searchValue"
v-if="searchInputFocus && searchValue"
class="search-suggestions"
content-style="padding: 0"
:style="{
height: `${suggestionsHeights}px`,
border: suggestionsHeights === 0 ? 'none' : null,
}"
@click="emit('closeSearch')"
>
<n-scrollbar class="scrollbar">
<!-- 直接搜索 -->
@@ -61,12 +62,14 @@
</template>
<script setup>
import { storeToRefs } from "pinia";
import { siteStatus } from "@/stores";
import { getSearchSuggest } from "@/api/search";
import debounce from "@/utils/debounce";
const status = siteStatus();
const emit = defineEmits(["toSearch"]);
const { searchInputFocus } = storeToRefs(status);
const emit = defineEmits(["toSearch", "closeSearch"]);
// 搜索内容
const props = defineProps({
@@ -233,5 +236,17 @@ watch(
}
}
}
@media (max-width: 512px) {
position: fixed;
top: 58px;
border-radius: 0px;
width: 100%;
max-height: calc(100vh - 58px);
min-height: calc(100vh - 58px);
:deep(.scrollbar) {
max-height: calc(100vh - 58px);
min-height: calc(100vh - 58px);
}
}
}
</style>

View File

@@ -47,15 +47,15 @@
<n-text class="close-tip">确认关闭软件吗</n-text>
<n-checkbox v-model:checked="closeTipCheckbox"> 记住且不再询问 </n-checkbox>
<template #footer>
<n-space justify="space-between">
<n-flex justify="space-between">
<n-button strong secondary @click="closeCloseTip('cancel')"> 取消 </n-button>
<n-space class="type">
<n-flex class="type">
<n-button strong secondary @click="closeCloseTip('close')"> 退出 </n-button>
<n-button type="primary" strong secondary @click="closeCloseTip('hide')">
最小化
</n-button>
</n-space>
</n-space>
</n-flex>
</n-flex>
</template>
</n-modal>
</div>

View File

@@ -37,6 +37,18 @@ router.beforeEach((to, from, next) => {
}
if (typeof $changeLogin !== "undefined") $changeLogin();
}
}
// 是否为本地功能
else if (to.meta.needLocal) {
if (checkPlatform.electron()) {
next();
} else {
$message.error("客户端独占功能");
if (typeof $loadingBar !== "undefined" && !checkPlatform.electron()) {
$loadingBar.error();
}
next("/403");
}
} else {
next();
}

View File

@@ -112,6 +112,15 @@ const routes = [
},
component: () => import("@/views/Comment.vue"),
},
// 歌曲详情
{
path: "/song",
name: "song",
meta: {
title: "歌曲详情",
},
component: () => import("@/views/Song.vue"),
},
// 最近播放
{
path: "/history",
@@ -244,7 +253,7 @@ const routes = [
name: "local",
meta: {
title: "本地歌曲",
needLogin: true,
needLocal: true,
show: checkPlatform.electron(),
},
component: () => import("@/views/Local/index.vue"),

View File

@@ -8,7 +8,7 @@ const useSiteSettingsStore = defineStore("siteSettings", {
closeTip: true, // 关闭软件提醒弹窗
closeType: "hide", // 关闭方式 close 直接关闭 / hide 最小化到任务栏
showTaskbarProgress: false, // 显示歌曲任务栏进度
searchHistory: true, // 搜索历史
showSearchHistory: true, // 搜索历史
autoSignIn: true, // 自动签到
showGithub: true,
showSider: true, // 显示侧边栏

View File

@@ -19,7 +19,7 @@ const useSiteStatusStore = defineStore("siteStatus", {
// 全屏播放器状态
showFullPlayer: false,
// 播放器功能显示
playerControlShow: false,
playerControlShow: true,
controlTimeOut: null,
// 实时播放进度
playSeek: 0,

View File

@@ -97,6 +97,7 @@ body,
.n-tabs {
--n-tab-border-radius: 6px !important;
.n-tabs-rail {
position: relative;
.n-tabs-tab-wrapper {
.n-tabs-tab {
&:hover {

View File

@@ -24,13 +24,15 @@ let spectrumsData = {
analyser: null,
audioCtx: null,
};
// 默认标题
let defaultTitle = document.title;
/**
* 初始化播放器
*/
export const initPlayer = async (playNow = false) => {
try {
// 停止播放当前歌曲
// 停止播放
soundStop();
// 获取基础数据
const music = musicData();
@@ -211,17 +213,19 @@ export const createPlayer = async (src, autoPlay = true) => {
const music = musicData();
const status = siteStatus();
const settings = siteSettings();
const { playMode } = status;
const { playSongSource, playList } = music;
const { showSpectrums, memorySeek, useMusicCache } = settings;
// 当前播放歌曲数据
const playSongData = music.getPlaySongData;
// 获取播放链接
const blobUrl = useMusicCache ? await getBlobUrlFromUrl(src) : src;
console.log("播放地址:", blobUrl);
// 获取播放链接(非电台及云盘歌曲)
const songUrl =
useMusicCache && playMode !== "dj" && !playSongData.pc ? await getBlobUrlFromUrl(src) : src;
console.log("播放地址:", songUrl);
// 初始化播放器
if (player) soundStop();
player = new Howl({
src: [blobUrl],
src: [songUrl],
format: ["mp3", "flac", "dolby", "webm"],
html5: true,
preload: "metadata",
@@ -255,14 +259,7 @@ export const createPlayer = async (src, autoPlay = true) => {
status.playLoading = false;
// 发送歌曲名
if (checkPlatform.electron()) {
const songName = playSongData.name || "未知曲目";
const songArtist =
status.playMode === "dj"
? "电台节目"
: Array.isArray(playSongData.artists)
? playSongData.artists.map((ar) => ar.name).join(" / ")
: playSongData.artists || "未知歌手";
electron.ipcRenderer.send("songNameChange", songName + " - " + songArtist);
electron.ipcRenderer.send("songNameChange", getPlaySongName());
}
// 听歌打卡
if (isLogin() && !playSongData?.path) {
@@ -284,6 +281,8 @@ export const createPlayer = async (src, autoPlay = true) => {
if (checkPlatform.electron()) {
electron.ipcRenderer.send("songStateChange", true);
}
// 更改页面标题
if (!checkPlatform.electron()) document.title = getPlaySongName();
});
// 暂停播放
player?.on("pause", () => {
@@ -295,6 +294,8 @@ export const createPlayer = async (src, autoPlay = true) => {
if (checkPlatform.electron()) {
electron.ipcRenderer.send("songStateChange", false);
}
// 更改页面标题
if (!checkPlatform.electron()) document.title = defaultTitle || "SPlayer";
});
// 结束播放
player?.on("end", () => {
@@ -759,6 +760,25 @@ const updateSpectrums = (analyser, dataArray) => {
});
};
/**
* 获取当前播放歌曲名
*/
const getPlaySongName = () => {
// pinia
const status = siteStatus();
const music = musicData();
const playSongData = music.getPlaySongData;
// 返回歌曲数据
const songName = playSongData.name || "未知曲目";
const songArtist =
status.playMode === "dj"
? "电台节目"
: Array.isArray(playSongData.artists)
? playSongData.artists.map((ar) => ar.name).join(" / ")
: playSongData.artists || "未知歌手";
return songName + " - " + songArtist;
};
/*
* 清除定时器
*/

View File

@@ -10,17 +10,7 @@
</div>
</n-h4>
<!-- 列表 -->
<Transition name="fade" mode="out-in">
<SongList
v-if="artistHotSongs"
:data="artistHotSongs"
:showPagination="false"
:showTitle="false"
/>
<div v-else class="loading">
<n-skeleton :repeat="10" text />
</div>
</Transition>
<SongList :data="artistHotSongs" :showPagination="false" :showTitle="false" />
</div>
</template>
@@ -90,13 +80,5 @@ onBeforeMount(async () => {
font-size: 14px;
}
}
:deep(.n-skeleton) {
&:nth-of-type(1) {
margin-top: 0;
}
height: 70px;
margin-top: 12px;
border-radius: 8px;
}
}
</style>

View File

@@ -38,7 +38,7 @@
{{ artistData.identify }}
</n-text>
<!-- 数量 -->
<n-space class="num">
<n-flex class="num">
<div
v-if="artistData.size?.music"
class="num-item"
@@ -69,7 +69,7 @@
</n-icon>
<n-text depth="3">{{ artistData.size.mv }}</n-text>
</div>
</n-space>
</n-flex>
<!-- 简介 -->
<n-ellipsis
v-if="artistData?.description"
@@ -82,7 +82,7 @@
</n-ellipsis>
<n-text v-else class="description">竟然没有简介</n-text>
<!-- 功能区 -->
<n-space class="menu" justify="space-between">
<n-flex class="menu" justify="space-between">
<n-button size="large" round strong secondary @click="likeOrDislike(artistId)">
<template #icon>
<n-icon>
@@ -91,7 +91,7 @@
</template>
{{ isLikeOrDislike(artistId) ? "关注歌手" : "取消关注" }}
</n-button>
</n-space>
</n-flex>
</div>
</div>
<div v-else class="detail">
@@ -326,6 +326,33 @@ onBeforeMount(async () => {
.tabs {
margin-bottom: 20px;
}
@media (max-width: 700px) {
.detail {
display: flex;
flex-direction: column;
align-items: center;
.cover {
width: 200px;
height: 200px;
min-width: 200px;
margin: 0;
}
.data {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 20px;
.name {
font-size: 26px;
}
.identify {
font-size: 16px;
margin-left: 0;
}
}
}
}
}
.title {
display: flex;

View File

@@ -15,8 +15,8 @@
</div>
</n-progress>
<!-- 功能区 -->
<n-space class="menu" justify="space-between">
<n-space class="left">
<n-flex class="menu" justify="space-between">
<n-flex class="left">
<n-button type="primary" class="play" circle strong secondary @click="playAllSongs">
<template #icon>
<n-icon size="32">
@@ -34,8 +34,8 @@
</n-button>
<!-- 歌曲上传弹窗 -->
<UpCloudSong ref="upCloudSongRef" @getUserCloudData="getUserCloudData" />
</n-space>
<n-space class="right">
</n-flex>
<n-flex class="right">
<!-- 模糊搜索 -->
<Transition name="fade" mode="out-in">
<n-input
@@ -54,8 +54,8 @@
</template>
</n-input>
</Transition>
</n-space>
</n-space>
</n-flex>
</n-flex>
<!-- 列表 -->
<Transition name="fade" mode="out-in">
<div v-if="userCloudData !== 'empty'" class="list">

View File

@@ -12,7 +12,7 @@
</Transition>
</div>
<!-- 操作 -->
<n-space class="control">
<n-flex class="control">
<n-button size="large" tag="div" round strong secondary @click="playAllSongs">
<template #icon>
<n-icon>
@@ -30,7 +30,7 @@
</template>
</n-button>
</n-dropdown>
</n-space>
</n-flex>
</div>
<!-- 列表 -->
<SongList :data="dailySongsData.data" />

View File

@@ -3,7 +3,7 @@
<div class="dsc-artists">
<div class="menu">
<!-- 字母分类 -->
<n-space class="initial">
<n-flex class="initial">
<n-tag
v-for="item in artistInitials"
:key="item"
@@ -14,9 +14,9 @@
>
{{ item.value }}
</n-tag>
</n-space>
</n-flex>
<!-- 地区分类 -->
<n-space class="category">
<n-flex class="category">
<n-tag
v-for="(item, index) in artistTypeNames"
:key="item"
@@ -31,10 +31,10 @@
>
{{ item }}
</n-tag>
</n-space>
</n-flex>
</div>
<MainCover :data="artistsData" columns="3 s:4 m:5 l:6" type="artist" />
<n-space v-if="arHasMore" justify="center">
<n-flex v-if="arHasMore" justify="center">
<n-button
:loading="arIsLoading"
class="load-more"
@@ -46,7 +46,7 @@
>
加载更多
</n-button>
</n-space>
</n-flex>
</div>
</template>

View File

@@ -1,8 +1,8 @@
<!-- 发现 - 最新音乐 -->
<template>
<div class="dsc-new">
<n-space class="menu" justify="space-between">
<n-space class="type">
<n-flex class="menu" justify="space-between">
<n-flex class="type">
<n-tag
v-for="(item, index) in newTypeNames"
:key="index"
@@ -14,8 +14,8 @@
>
{{ item }}
</n-tag>
</n-space>
<n-space class="area">
</n-flex>
<n-flex class="area">
<n-tag
v-for="(item, index) in newAreaNames"
:key="index"
@@ -27,8 +27,8 @@
>
{{ item.value }}
</n-tag>
</n-space>
</n-space>
</n-flex>
</n-flex>
<!-- 列表 -->
<Transition name="fade" mode="out-in">
<div v-if="newTypeChoose === 0" class="new-album">

View File

@@ -2,7 +2,7 @@
<template>
<div class="dsc-playlists">
<!-- 菜单 -->
<n-space class="menu" align="center" justify="space-between">
<n-flex class="menu" align="center" justify="space-between">
<!-- 分类选择 -->
<n-button
class="cat"
@@ -20,12 +20,12 @@
{{ catName }}
</n-button>
<Transition name="fade" mode="out-in">
<n-space v-if="getHaveHqPlaylists(data.plCatList.hqCatList, catName)" align="center">
<n-flex v-if="getHaveHqPlaylists(data.plCatList.hqCatList, catName)" align="center">
<n-text>精品歌单</n-text>
<n-switch v-model:value="hqPlOpen" :round="false" @update:value="hqPlOpenChange" />
</n-space>
</n-flex>
</Transition>
</n-space>
</n-flex>
<!-- 列表 -->
<MainCover :data="allPlData" />
<!-- 分页 -->
@@ -37,7 +37,7 @@
/>
<!-- 加载更多 -->
<Transition name="fade" mode="out-in">
<n-space justify="center">
<n-flex justify="center">
<n-button
v-if="hqPlOpen && plHasMore"
:loading="plHasLoading"
@@ -55,7 +55,7 @@
>
加载更多
</n-button>
</n-space>
</n-flex>
</Transition>
<!-- 分类切换 -->
<n-modal v-model:show="catChangeShow" :bordered="false" preset="card">
@@ -80,7 +80,7 @@
<template #prefix>
<n-text class="type"> {{ cat }} </n-text>
</template>
<n-space>
<n-flex>
<n-tag
v-for="item in data.plCatList.catList.filter((v) => v.category == key)"
:key="item"
@@ -98,7 +98,7 @@
</n-icon>
</template>
</n-tag>
</n-space>
</n-flex>
</n-list-item>
</n-list>
</div>

View File

@@ -7,7 +7,7 @@
</n-h1>
<Transition name="fade" mode="out-in">
<div v-if="historyPlaylist?.length" class="list">
<n-space class="menu">
<n-flex class="menu">
<n-button round strong secondary @click="cleanHistory">
<template #icon>
<n-icon>
@@ -16,7 +16,7 @@
</template>
清空列表
</n-button>
</n-space>
</n-flex>
<SongList :data="historyPlaylist" :showCover="false" />
<n-divider class="tip" dashed>
<n-text :depth="3"> 最多展示 500 条播放历史 </n-text>

View File

@@ -22,11 +22,7 @@
</n-gi>
<!-- 喜欢的音乐 -->
<n-gi>
<SpecialCover
:data="likeSongsCoverData"
:showIcon="false"
@click="jumpPage('like-songs')"
/>
<SpecialCover :data="likeSongsCoverData" @click="jumpPage('like-songs')" />
</n-gi>
</n-grid>
<PrivateFm class="rec-fm" />
@@ -60,7 +56,8 @@ import {
getTopArtists,
getNewAlbum,
} from "@/api/recommend";
import { getDjPersonalRec } from "@/api/dj";
import { allMv } from "@/api/video";
import { getDjRecommend } from "@/api/dj";
import { siteData, siteSettings } from "@/stores";
import { getCacheData } from "@/utils/helper";
import { isLogin } from "@/utils/auth";
@@ -91,6 +88,7 @@ const dailySongsCoverData = computed(() => {
// 喜欢的音乐
const likeSongsCoverData = computed(() => {
const likeSongsCover = {
id: 1024,
name: "喜欢的音乐",
desc: "发现你独特的音乐品味",
};
@@ -129,12 +127,12 @@ const recommendData = ref({
mv: {
name: "推荐 MV",
type: "mv",
columns: "1 s:2 m:3 l:4 xl:5",
loadingNum: 2,
columns: "2 s:2 m:3 l:4 xl:5",
loadingNum: 12,
data: [],
},
dj: {
name: "专属播客",
name: "推荐播客",
type: "dj",
loadingNum: 6,
data: [],
@@ -162,9 +160,9 @@ const getRecommendData = async () => {
// 歌手
getCacheData("recAr", 5, getTopArtists),
// MV
getCacheData("recMv", 5, getPersonalized, "mv"),
getCacheData("recMv", 5, allMv),
// 电台
getCacheData("recDj", 5, getDjPersonalRec),
getCacheData("recDj", 5, getDjRecommend),
// 专辑
getCacheData("recAl", 5, getNewAlbum),
]);
@@ -182,9 +180,9 @@ const getRecommendData = async () => {
artistRes.status === "fulfilled" &&
(recommendData.value.artist.data = formatData(artistRes.value.artists, "artist"));
mvRes.status === "fulfilled" &&
(recommendData.value.mv.data = formatData(mvRes.value.result, "mv"));
(recommendData.value.mv.data = formatData(mvRes.value.data, "mv"));
djRes.status === "fulfilled" &&
(recommendData.value.dj.data = formatData(djRes.value.data, "dj"));
(recommendData.value.dj.data = formatData(djRes.value.djRadios, "dj"));
albumRes.status === "fulfilled" &&
(recommendData.value.album.data = formatData(albumRes.value.albums, "album"));
// 检查是否有任何请求失败
@@ -273,6 +271,14 @@ onBeforeMount(() => {
margin-left: 20px;
max-width: calc(50% - 10px);
}
@media (max-width: 700px) {
flex-direction: column;
.rec-fm {
margin-left: 0;
margin-top: 20px;
max-width: 100%;
}
}
}
}
</style>

View File

@@ -101,6 +101,7 @@ watch(
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
margin-bottom: 20px;
.num-item {
display: flex;

View File

@@ -3,7 +3,7 @@
<Transition name="fade" mode="out-in">
<div v-if="userLikeData.playlists?.length" class="pl-list">
<!-- 分类 -->
<n-space class="type">
<n-flex class="type">
<n-tag
v-for="(item, index) in ['我创建的', '我收藏的']"
:key="index"
@@ -14,7 +14,7 @@
>
{{ item }}
</n-tag>
</n-space>
</n-flex>
<!-- 列表 -->
<Transition name="fade" mode="out-in">
<div v-if="plTypeChoose === 0" class="list">

View File

@@ -42,7 +42,7 @@
</n-text>
</div>
<!-- 标签 -->
<n-space v-if="albumDetail?.tags" class="tags">
<n-flex v-if="albumDetail?.tags" class="tags">
<n-tag
v-for="(item, index) in albumDetail.tags"
:key="index"
@@ -58,9 +58,9 @@
>
{{ item }}
</n-tag>
</n-space>
</n-flex>
<!-- 数量 -->
<n-space class="num">
<n-flex class="num">
<div v-if="albumDetail.count" class="num-item">
<n-icon depth="3" size="18">
<SvgIcon icon="music-note" />
@@ -79,7 +79,7 @@
</n-icon>
<n-text depth="3">{{ getTimestampTime(albumDetail.publishTime) }} 发布</n-text>
</div>
</n-space>
</n-flex>
<!-- 简介 -->
<n-ellipsis
v-if="albumDetail.description"
@@ -101,8 +101,8 @@
</div>
</Transition>
<!-- 功能区 -->
<n-space class="menu" justify="space-between">
<n-space class="left">
<n-flex class="menu" justify="space-between">
<n-flex class="left">
<n-button
:disabled="albumData === 'empty'"
type="primary"
@@ -119,7 +119,15 @@
</n-icon>
</template>
</n-button>
<n-button size="large" tag="div" round strong secondary @click="likeOrDislike(albumId)">
<n-button
class="like"
size="large"
tag="div"
round
strong
secondary
@click="likeOrDislike(albumId)"
>
<template #icon>
<n-icon>
<SvgIcon
@@ -130,7 +138,7 @@
{{ isLikeOrDislike(albumId) ? "收藏专辑" : "取消收藏" }}
</n-button>
<n-dropdown :options="moreOptions" trigger="hover" placement="bottom-start">
<n-button size="large" tag="div" circle strong secondary>
<n-button class="more" size="large" tag="div" circle strong secondary>
<template #icon>
<n-icon>
<SvgIcon icon="format-list-bulleted" />
@@ -138,8 +146,8 @@
</template>
</n-button>
</n-dropdown>
</n-space>
<n-space class="right">
</n-flex>
<n-flex class="right">
<!-- 模糊搜索 -->
<Transition name="fade" mode="out-in">
<n-input
@@ -158,8 +166,8 @@
</template>
</n-input>
</Transition>
</n-space>
</n-space>
</n-flex>
</n-flex>
<!-- 列表 -->
<Transition name="fade" mode="out-in">
<SongList v-if="!searchValue" :data="albumData" :sourceId="albumId" :showAlbum="false" />
@@ -194,6 +202,7 @@ import { NIcon } from "naive-ui";
import { useRouter } from "vue-router";
import { storeToRefs } from "pinia";
import { musicData, siteData, siteStatus } from "@/stores";
import { getSongDetail } from "@/api/song";
import { getAlbumDetail, likeAlbum } from "@/api/album";
import { formatNumber, fuzzySearch } from "@/utils/helper";
import { getTimestampTime } from "@/utils/timeTools";
@@ -255,7 +264,9 @@ const getAlbumAllData = async (id, justDetail = false) => {
// 是否终止
if (justDetail) return true;
// 全部歌曲
albumData.value = formatData(detail.songs, "song");
const ids = detail.songs.map((song) => song.id).join(",");
const songsDetail = await getSongDetail(ids);
albumData.value = formatData(songsDetail.songs, "song");
};
// 播放专辑全部歌曲
@@ -391,6 +402,7 @@ onBeforeMount(() => {
.name {
font-size: 30px;
font-weight: bold;
-webkit-line-clamp: 2;
}
.alia {
margin-top: 4px;
@@ -470,9 +482,11 @@ onBeforeMount(() => {
}
}
.menu {
flex-wrap: nowrap;
align-items: center;
margin: 26px 0;
.left {
flex-wrap: nowrap;
align-items: center;
.play {
--n-width: 46px;
@@ -495,6 +509,84 @@ onBeforeMount(() => {
}
}
}
@media (max-width: 700px) {
.detail {
.cover {
width: 140px;
height: 140px;
min-width: 140px;
}
.data {
.name {
font-size: 20px;
margin-bottom: 4px;
}
.alia {
font-size: 16px;
}
.creator {
.n-avatar {
width: 20px;
height: 20px;
margin-right: 6px;
}
.nickname {
font-size: 12px;
}
.create-time {
margin-left: 6px;
font-size: 12px;
}
}
.tags {
.pl-tags {
font-size: 12px;
padding: 0 12px;
}
}
.num,
.description {
display: none !important;
}
}
}
.menu {
margin: 20px 0;
.left {
.play {
--n-width: 40px;
--n-height: 40px;
.n-icon {
font-size: 22px !important;
}
}
.like {
--n-height: 36px;
--n-font-size: 13px;
--n-padding: 0 16px;
--n-icon-size: 18px;
:deep(.n-button__icon) {
margin: 0;
}
:deep(.n-button__content) {
display: none;
}
}
.more {
--n-height: 36px;
--n-font-size: 13px;
--n-icon-size: 18px;
}
}
.right {
.search {
height: 36px;
width: 130px;
font-size: 13px;
}
}
}
}
}
.title {
display: flex;

View File

@@ -72,7 +72,7 @@
</n-ellipsis>
<n-text v-else class="description">太懒了吧连简介都没写</n-text>
<!-- 数量 -->
<n-space class="num">
<n-flex class="num">
<div v-if="djDetail?.count" class="num-item">
<n-icon depth="3" size="18">
<SvgIcon icon="music-note" />
@@ -85,7 +85,7 @@
</n-icon>
<n-text depth="3">{{ getTimestampTime(djDetail.updateTime) }} 更新</n-text>
</div>
</n-space>
</n-flex>
</div>
</div>
<div v-else class="detail">
@@ -96,8 +96,8 @@
</div>
</Transition>
<!-- 功能区 -->
<n-space class="menu" justify="space-between">
<n-space class="left">
<n-flex class="menu" justify="space-between">
<n-flex class="left">
<n-button
:disabled="djData === 'empty'"
type="primary"
@@ -114,7 +114,15 @@
</n-icon>
</template>
</n-button>
<n-button size="large" tag="div" round strong secondary @click="likeOrDislike(djId)">
<n-button
class="like"
size="large"
tag="div"
round
strong
secondary
@click="likeOrDislike(djId)"
>
<template #icon>
<n-icon>
<SvgIcon
@@ -125,7 +133,7 @@
{{ isLikeOrDislike(djId) ? "订阅电台" : "取消订阅" }}
</n-button>
<n-dropdown :options="moreOptions" trigger="hover" placement="bottom-start">
<n-button size="large" tag="div" circle strong secondary>
<n-button class="more" size="large" tag="div" circle strong secondary>
<template #icon>
<n-icon>
<SvgIcon icon="format-list-bulleted" />
@@ -133,8 +141,8 @@
</template>
</n-button>
</n-dropdown>
</n-space>
<n-space class="right">
</n-flex>
<n-flex class="right">
<!-- 模糊搜索 -->
<Transition name="fade" mode="out-in">
<n-input
@@ -153,8 +161,8 @@
</template>
</n-input>
</Transition>
</n-space>
</n-space>
</n-flex>
</n-flex>
<!-- 列表 -->
<Transition name="fade" mode="out-in">
<div v-if="djData !== 'empty'" class="list">
@@ -433,6 +441,7 @@ onMounted(async () => {
font-size: 30px;
font-weight: bold;
margin-bottom: 12px;
-webkit-line-clamp: 2;
}
.creator {
display: flex;
@@ -529,6 +538,81 @@ onMounted(async () => {
}
}
}
@media (max-width: 700px) {
.detail {
.cover {
width: 140px;
height: 140px;
min-width: 140px;
}
.data {
.name {
font-size: 20px;
margin-bottom: 4px;
}
.creator {
.n-avatar {
width: 20px;
height: 20px;
margin-right: 6px;
}
.nickname {
font-size: 12px;
}
.create-time {
margin-left: 6px;
font-size: 12px;
}
}
.tags {
.pl-tags {
font-size: 12px;
padding: 0 12px;
}
}
.num,
.description {
display: none !important;
}
}
}
.menu {
margin: 20px 0;
.left {
.play {
--n-width: 40px;
--n-height: 40px;
.n-icon {
font-size: 22px !important;
}
}
.like {
--n-height: 36px;
--n-font-size: 13px;
--n-padding: 0 16px;
--n-icon-size: 18px;
:deep(.n-button__icon) {
margin: 0;
}
:deep(.n-button__content) {
display: none;
}
}
.more {
--n-height: 36px;
--n-font-size: 13px;
--n-icon-size: 18px;
}
}
.right {
.search {
height: 36px;
width: 130px;
font-size: 13px;
}
}
}
}
}
.title {
display: flex;

View File

@@ -48,7 +48,7 @@
</n-text>
</div>
<!-- 标签 -->
<n-space v-if="playListDetail?.tags" class="tags">
<n-flex v-if="playListDetail?.tags" class="tags">
<n-tag
v-for="(item, index) in playListDetail.tags"
:key="index"
@@ -64,9 +64,9 @@
>
{{ item }}
</n-tag>
</n-space>
</n-flex>
<!-- 数量 -->
<n-space class="num">
<n-flex class="num">
<div v-if="playListDetail.count" class="num-item">
<n-icon depth="3" size="18">
<SvgIcon icon="music-note" />
@@ -85,7 +85,7 @@
</n-icon>
<n-text depth="3">{{ getTimestampTime(playListDetail.updateTime) }}</n-text>
</div>
</n-space>
</n-flex>
<!-- 简介 -->
<n-ellipsis
v-if="playListDetail.description"
@@ -108,8 +108,8 @@
</Transition>
<!-- 功能区 -->
<Transition name="fade" mode="out-in">
<n-space :key="isUserPLayList" class="menu" justify="space-between">
<n-space class="left">
<n-flex :key="isUserPLayList" class="menu" justify="space-between">
<n-flex class="left">
<n-button
:disabled="playListData === null || playListData === 'empty' || loadingMsg !== null"
type="primary"
@@ -128,6 +128,7 @@
</n-button>
<n-button
v-if="!isUserPLayList"
class="like"
size="large"
tag="div"
round
@@ -148,6 +149,7 @@
</n-button>
<n-button
v-else
class="like"
size="large"
tag="div"
round
@@ -163,7 +165,7 @@
编辑歌单
</n-button>
<n-dropdown :options="moreOptions" trigger="hover" placement="bottom-start">
<n-button size="large" tag="div" circle strong secondary>
<n-button class="more" size="large" tag="div" circle strong secondary>
<template #icon>
<n-icon>
<SvgIcon icon="format-list-bulleted" />
@@ -171,8 +173,8 @@
</template>
</n-button>
</n-dropdown>
</n-space>
<n-space class="right">
</n-flex>
<n-flex class="right">
<!-- 模糊搜索 -->
<Transition name="fade" mode="out-in">
<n-input
@@ -191,8 +193,8 @@
</template>
</n-input>
</Transition>
</n-space>
</n-space>
</n-flex>
</n-flex>
</Transition>
<!-- 列表 -->
<Transition name="fade" mode="out-in">
@@ -566,6 +568,7 @@ onBeforeUnmount(() => {
font-size: 30px;
font-weight: bold;
margin-bottom: 12px;
-webkit-line-clamp: 2;
}
.creator {
display: flex;
@@ -640,9 +643,11 @@ onBeforeUnmount(() => {
}
}
.menu {
flex-wrap: nowrap;
align-items: center;
margin: 26px 0;
.left {
flex-wrap: nowrap;
align-items: center;
.play {
--n-width: 46px;
@@ -665,6 +670,82 @@ onBeforeUnmount(() => {
}
}
}
@media (max-width: 700px) {
.detail {
.cover {
width: 140px;
height: 140px;
min-width: 140px;
}
.data {
.name {
font-size: 20px;
margin-bottom: 4px;
}
.creator {
.n-avatar {
width: 20px;
height: 20px;
margin-right: 6px;
}
.nickname {
font-size: 12px;
}
.create-time {
margin-left: 6px;
font-size: 12px;
}
}
.tags {
.pl-tags {
font-size: 12px;
padding: 0 12px;
}
}
.num,
.description {
display: none !important;
}
}
}
.menu {
margin: 20px 0;
.left {
.play {
--n-width: 40px;
--n-height: 40px;
.n-icon {
font-size: 22px !important;
}
}
.like {
--n-height: 36px;
--n-font-size: 13px;
--n-padding: 0 16px;
--n-icon-size: 18px;
:deep(.n-button__icon) {
margin: 0;
}
:deep(.n-button__content) {
display: none;
}
}
.more {
--n-height: 36px;
--n-font-size: 13px;
--n-icon-size: 18px;
}
}
.right {
.search {
height: 36px;
width: 130px;
font-size: 13px;
}
}
}
}
}
.title {
display: flex;

View File

@@ -3,7 +3,7 @@
<div class="local">
<n-h1 class="title">本地歌曲</n-h1>
<!-- 数据统计 -->
<n-space class="num">
<n-flex class="num">
<!-- 总数 -->
<div class="num-item">
<n-icon size="18">
@@ -25,7 +25,7 @@
/>
GB
</div>
</n-space>
</n-flex>
<!-- 标签页 -->
<n-tabs v-model:value="tabValue" class="tabs" type="segment" @update:value="tabChange">
<n-tab name="local-songs"> 歌曲 </n-tab>
@@ -153,7 +153,7 @@
</n-list-item>
</n-list>
<template #footer>
<n-space justify="center">
<n-flex justify="center">
<n-button class="add-path" strong secondary @click="changeLocalPath">
<template #icon>
<n-icon>
@@ -162,7 +162,7 @@
</template>
添加文件夹
</n-button>
</n-space>
</n-flex>
</template>
</n-modal>
</div>

View File

@@ -6,7 +6,7 @@
<Transition name="fade" mode="out-in">
<div v-if="videoData" class="detail">
<n-text class="title">{{ videoData.name }}</n-text>
<n-space class="detail-tag">
<n-flex class="detail-tag">
<!-- 播放量 -->
<div v-if="videoData?.playCount" class="tag-item">
<n-icon depth="3" size="18">
@@ -33,7 +33,7 @@
</n-icon>
<n-text depth="3">{{ videoData.publishTime }}</n-text>
</div>
</n-space>
</n-flex>
</div>
<div v-else class="detail">
<n-skeleton :repeat="2" text round />
@@ -111,7 +111,7 @@
{{ videoData.desc }}
</n-ellipsis>
<n-text v-else>该视频暂无简介</n-text>
<n-space v-if="videoData?.videoGroup" class="video-tag">
<n-flex v-if="videoData?.videoGroup" class="video-tag">
<n-tag
v-for="(item, index) in videoData?.videoGroup"
:key="index"
@@ -121,7 +121,7 @@
>
{{ item.name }}
</n-tag>
</n-space>
</n-flex>
<n-divider id="to-comments" />
</div>
<div v-else class="content">
@@ -636,5 +636,25 @@ onBeforeUnmount(() => {
margin-top: 36px;
}
}
@media (max-width: 700px) {
.player {
width: 100%;
.detail {
height: 60px;
display: flex;
flex-direction: column;
justify-content: space-around;
margin-bottom: 20px;
.title {
display: inline-block;
margin: 0;
-webkit-line-clamp: 2;
}
}
}
.video-more {
display: none;
}
}
}
</style>

View File

@@ -7,7 +7,7 @@
<n-text depth="3">的相关搜索</n-text>
</div>
<!-- 标签页 -->
<n-tabs v-model:value="tabValue" class="tabs" type="line" @update:value="tabChange">
<n-tabs v-model:value="tabValue" class="tabs" type="segment" @update:value="tabChange">
<n-tab name="sea-songs"> 单曲 </n-tab>
<n-tab name="sea-artists"> 歌手 </n-tab>
<n-tab name="sea-albums"> 专辑 </n-tab>

View File

@@ -17,7 +17,7 @@
<n-tabs
ref="setTabsRef"
v-model:value="setTabsValue"
type="line"
type="segment"
@update:value="settingTabChange"
>
<n-tab name="setTab1"> 常规 </n-tab>
@@ -85,7 +85,7 @@
</n-card>
<n-card class="set-item">
<div class="name">显示搜索历史</div>
<n-switch v-model:value="searchHistory" :round="false" />
<n-switch v-model:value="showSearchHistory" :round="false" />
</n-card>
<n-card class="set-item">
<div class="name">
@@ -499,7 +499,7 @@
默认下载文件夹
<n-text class="tip">{{ downloadPath || "不设置则会每次选择保存位置" }}</n-text>
</div>
<n-space>
<n-flex>
<Transition name="fade" mode="out-in">
<n-button
v-if="downloadPath"
@@ -514,7 +514,7 @@
<n-button :disabled="!checkPlatform.electron()" strong secondary @click="choosePath">
更改
</n-button>
</n-space>
</n-flex>
</n-card>
</div>
<!-- 其他 -->
@@ -599,7 +599,7 @@ const {
lrcMousePause,
lyricsFontSize,
lyricsBlur,
searchHistory,
showSearchHistory,
autoSignIn,
bottomLyricShow,
downloadPath,

View File

@@ -1,4 +1,45 @@
<!-- 单曲页面 -->
<template>
<div class="song">单曲页面 - 待完成</div>
<div class="song">
单曲页面 - 待完成
{{ songDetail }}
</div>
</template>
<script setup>
import { useRouter } from "vue-router";
import { getSongDetail } from "@/api/song";
import formatData from "@/utils/formatData";
const router = useRouter();
// 歌曲信息
const songId = ref(router.currentRoute.value.query.id);
const songDetail = ref(null);
// 检查是否具有视频 id
const isHasSongId = (id) => {
if (!id) {
$message.error("参数不完整");
return router.go(-1);
}
};
// 获取歌曲详情
const getSongDetailData = async (id) => {
try {
const detail = await getSongDetail(id);
const data = formatData(detail?.songs?.[0], "song");
songDetail.value = data?.[0] ?? null;
} catch (error) {
console.error("获取歌曲详情失败:", error);
}
};
onMounted(() => {
// 若无 id
isHasSongId(songId.value);
// 获取歌曲详情
getSongDetailData(songId.value);
});
</script>