Merge branch 'dev'

This commit is contained in:
lyswhut
2022-02-26 12:22:00 +08:00
102 changed files with 2372 additions and 1006 deletions
-1
View File
@@ -2,7 +2,6 @@ module.exports = {
upgrade: true, upgrade: true,
reject: [ reject: [
'electron', 'electron',
'electron-builder',
'chalk', 'chalk',
], ],
// target: 'newest', // target: 'newest',
+32
View File
@@ -6,6 +6,38 @@ Project versioning adheres to [Semantic Versioning](http://semver.org/).
Commit convention is based on [Conventional Commits](http://conventionalcommits.org). Commit convention is based on [Conventional Commits](http://conventionalcommits.org).
Change log format is based on [Keep a Changelog](http://keepachangelog.com/). Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
## [1.18.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.17.1...v1.18.0) - 2022-02-26
### 新增
- 新增“双击列表里的歌曲时自动切换到当前列表播放”设置,此功能仅对歌单、排行榜有效,默认关闭
- 新增打开收藏的在线列表的对应平台详情页功能,可以在我的列表-列表右键菜单中使用
- 新增定时暂停播放功能,由于此功能大多数人可能不常用,所以将其放在设置-基本设置中
- 新增任务栏缩略图工具栏控制按钮(此功能仅在Windows平台可用),按钮分别为收藏/取消收藏(将歌曲添加到“我的收藏”列表)、上一曲、播放/暂停、下一曲
- 新增设置-基本设置-软件字体设置,此设置可用于设置主界面的字体(已知的问题:Windows 7 下可能会出现字体列表为空的情况,这是当前系统的 Powershell 版本小于5.1导致的,请自行尝试看常见解决)
- 新增Scheme URL对音乐搜索的调用支持,详情看常见问题-Scheme URL支持
- 新增Scheme URL以url传参的方式调用,详情看常见问题-Scheme URL支持
- 自定义源新增更新弹窗方法,同时自定义源管理新增是否允许源显示更新弹窗设置(出于防止滥用考虑),当源作者想要通知用户源已更新时,可以调用此方法弹窗告诉用户,调用说明看常见问题-自定义源部分
### 优化
- 过滤tx源某些不支持播放的歌曲,解决播放此类内容会导致意外的问题
- 把歌曲的热门评论与最新评论拆分成两个列表显示
### 修复
- 修复排行榜名字右击菜单的播放功能在播放非激活的列表时的列表获取问题
- 修复修改列表名时无法使用`Ctrl`键的问题
- 修复wy源某些歌曲获取歌词翻译的问题处理
- 修复下载功能的歌词换源时会进入死循环的问题
- 修复某些歌曲无法下载的问题
- 修复windows平台下软件目录存在`portable`文件夹时,仍会创建`C:\Users\<user>\AppData\Roaming\lx-music-desktop\Dictionaries\en-US-9-0.bdic`文件的问题,现在不会再创建文件,但仍会创建空目录(Electron的问题,目前暂无解决方法)
- 修复播放器的停止逻辑问题
### 其他
- 更新electron到v13.6.9
## [1.17.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.17.0...v1.17.1) - 2022-01-28 ## [1.17.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.17.0...v1.17.1) - 2022-01-28
### 优化 ### 优化
+32 -1
View File
@@ -6,6 +6,15 @@
洛雪音乐的最初定位不是作为播放器开发的,它主要用于**查找歌曲**,软件的播放功能仅用于试听,不建议用作为常用播放器使用。 洛雪音乐的最初定位不是作为播放器开发的,它主要用于**查找歌曲**,软件的播放功能仅用于试听,不建议用作为常用播放器使用。
## LX Music中的音乐播放列表机制
1. 默认情况下,播放搜索列表、歌单列表、排行榜列表的歌曲时会自动将该歌曲添加到“我的列表”的试听列表后再播放,手动将歌曲添加到试听列表,再去试听列表找到这首歌点播放是等价的
2. 如果你想要播放多首歌曲,需要使用多选功能(若不知道如何多选请看常见问题)多选后,将歌曲这些歌曲添加到“我的列表”播放,或使用稍后播放功能播放
3. 第2条适用于搜索列表、歌单列表、排行榜列表、我的列表中的歌曲
4. 对于歌单详情列表,除了可以使用第2条的方式播放外,你可以点击详情页上面的播放按钮临时播放当前歌单,或点击收藏将当前歌单收藏到“我的列表”后再去播放
5. 对于排行榜详情列表,除了可以使用第2条的方式播放外,你可以在右击排行榜名字后弹出的菜单中,播放或收藏整个排行榜,这与第四条的歌单中的播放、与收藏按钮功能一致
6. v1.18.0及之后新增了“双击列表里的歌曲时自动切换到当前列表播放”设置,默认关闭,此功能仅对歌单、排行榜有效
## 歌曲无法试听与下载 ## 歌曲无法试听与下载
### 所有歌曲都提示 `请求异常😮,可以多试几次,若还是不行就换一首吧。。。` ### 所有歌曲都提示 `请求异常😮,可以多试几次,若还是不行就换一首吧。。。`
@@ -101,6 +110,9 @@
需要注意的是:这将会覆盖本地的目标列表,歌曲将被替换成最新的在线列表。 需要注意的是:这将会覆盖本地的目标列表,歌曲将被替换成最新的在线列表。
## 调整我的列表的列表顺序
按住Ctrl键(Mac上对应Command键)的时候将进入“拖动模式”,此时可以拖动列表的位置来调整顺序。
## 同步功能的使用(实验性,首次使用前建议先备份一次列表) ## 同步功能的使用(实验性,首次使用前建议先备份一次列表)
@@ -305,16 +317,34 @@ Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看上面的
- URL统一以`lxmusic://`开头 - URL统一以`lxmusic://`开头
- 此技术目前只支持 Windows、Mac系统 - 此技术目前只支持 Windows、Mac系统
- URL传参以经过URL编码的JSON数据传参,例:`lxmusic://music/play?data=xxxx`,其中`xxxx`为经过URL编码后的JSON数据
- 若无特别说明,源的可用值为:`kw/kg/tx/wy/mg` - 若无特别说明,源的可用值为:`kw/kg/tx/wy/mg`
- 若无特别说明,音质的可用值为:`128k/320k/flac/flac32bit` - 若无特别说明,音质的可用值为:`128k/320k/flac/flac32bit`
目前支持两种传参方式:
- 通过`data`传参,以经过URL编码的JSON数据传参,例:`lxmusic://music/play?data=xxxx`,其中`xxxx`为经过URL编码后的JSON数据,支持复杂的参数调用
- 通过`URL`传参,适用于简单传参的调用,不需要转成JSON格式,例:`lxmusic://music/search/xxxx`,但仍然需要对数据进行URL编码,只适应于简单参数调用(v1.18.0新增)
### `data`方式传参
以经过URL编码的JSON数据传参,例:`lxmusic://music/play?data=xxxx`,其中`xxxx`为经过URL编码后的JSON数据,JSON数据内容取决于下表的参数部分
| 描述 | URL | 参数 | 描述 | URL | 参数
| --- | --- | --- | --- | --- | ---
| 打开歌单 | `songlist/open` | `source<String>`(源,必须)<br>`id<String/Number>`(歌单ID,可选)<br>`url<String>`(歌单URL,可选)其中ID与URL必需传一个 | 打开歌单 | `songlist/open` | `source<String>`(源,必须)<br>`id<String/Number>`(歌单ID,可选)<br>`url<String>`(歌单URL,可选)其中ID与URL必需传一个
| 播放歌单 | `songlist/play` | `source<String>`(源,必须)<br>`id<String/Number>`(歌单ID,可选)<br>`url<String>`(歌单URL,可选)其中`id`与`url`必需传一个<br>`index<Number>`(播放第几首歌,可选,从0开始) | 播放歌单 | `songlist/play` | `source<String>`(源,必须)<br>`id<String/Number>`(歌单ID,可选)<br>`url<String>`(歌单URL,可选)其中`id`与`url`必需传一个<br>`index<Number>`(播放第几首歌,可选,从0开始)
| 搜索歌曲 | `music/search` | `keywords<String/Number>`(要搜索的内容,必须)<br>`source<String>`(源,可选)
| 播放歌曲 | `music/play` | `name<String>`(歌曲名,必传)<br>`singer<String>`(艺术家名,必传)<br>`source<String>`(源,必传)<br>`songmid<String/Number>`(歌曲ID,必传)<br>`img<String>`(歌曲图片链接,选传)<br>`albumId<String/Number>`(歌曲专辑ID,选传)<br>`interval<String>`(格式化后的歌曲时长,选传,例:`03:55`)<br>`albumName<String>`(歌曲专辑名称,选传)<br>`types<Object>`(歌曲可用音质数组,必传,<br>数组格式:`[{"type": "<音质>", size: "<格式化后的文件大小,选传>", hash: "<kg源必传>"}]`,<br>例:`[{"type": "128k", size: "3.56M"}, {"type": "320k", size: null}]`)<br><br>以下为平台特定参数:<br>`hash<String>`(歌曲hash,kg源必传)<br>`strMediaMid<String>`(歌曲strMediaMid,tx源必传)<br>`albumMid<String>`(歌曲albumMid,tx源专用,选传)<br>`copyrightId<String>`(歌曲copyrightId,mg源必传)<br>`lrcUrl<String>`(歌曲lrcUrl,mg源专用,选传) | 播放歌曲 | `music/play` | `name<String>`(歌曲名,必传)<br>`singer<String>`(艺术家名,必传)<br>`source<String>`(源,必传)<br>`songmid<String/Number>`(歌曲ID,必传)<br>`img<String>`(歌曲图片链接,选传)<br>`albumId<String/Number>`(歌曲专辑ID,选传)<br>`interval<String>`(格式化后的歌曲时长,选传,例:`03:55`)<br>`albumName<String>`(歌曲专辑名称,选传)<br>`types<Object>`(歌曲可用音质数组,必传,<br>数组格式:`[{"type": "<音质>", size: "<格式化后的文件大小,选传>", hash: "<kg源必传>"}]`,<br>例:`[{"type": "128k", size: "3.56M"}, {"type": "320k", size: null}]`)<br><br>以下为平台特定参数:<br>`hash<String>`(歌曲hash,kg源必传)<br>`strMediaMid<String>`(歌曲strMediaMid,tx源必传)<br>`albumMid<String>`(歌曲albumMid,tx源专用,选传)<br>`copyrightId<String>`(歌曲copyrightId,mg源必传)<br>`lrcUrl<String>`(歌曲lrcUrl,mg源专用,选传)
### `URL`方式传参
由于URL传参只适用于简单传参场景,所以目前只支持以下功能的调用:
| 描述 | URL | 参数
| --- | --- | ---
| 搜索歌曲 | `music/search/{source}/{keywords}` | `source`(源,可选)<br>`keywords`(要搜索的内容,必须)<br>例:`music/search/kw/xxx`、`music/search/xxx`
| 打开歌单 | `songlist/open/{source}/{id/url}` | `source`(源,必须)<br>`id/url`(歌单ID或歌单URL,必须)<br>例:`songlist/open/kw/123456`
## 自定义源脚本编写说明 ## 自定义源脚本编写说明
文件请使用UTF-8编码格式编写,脚本所用编程语言为JavaScript,可以使用ES6+语法,脚本与应用的交互是使用类似事件收发的方式进行,这是一个基本的脚本例子: 文件请使用UTF-8编码格式编写,脚本所用编程语言为JavaScript,可以使用ES6+语法,脚本与应用的交互是使用类似事件收发的方式进行,这是一个基本的脚本例子:
@@ -427,6 +457,7 @@ send(EVENT_NAMES.inited, {
| --- | --- | --- | ---
| `inited` | 脚本初始化完成后发送给应用的事件名,发送该事件时需要传入以下信息:`{status, sources, openDevTools}`<br>`status`:初始化结果(`true`成功,`false`失败)<br>`openDevTools`:是否打开DevTools,此选项可用于开发脚本时的调试<br>`sources`:支持的源信息对象,<br>`sources[kw/kg/tx/wy/mg].name`:源的名字(目前非必须)<br>`sources[kw/kg/tx/wy/mg].type`:源类型,目前固定值需为`music`<br>`sources[kw/kg/tx/wy/mg].actions`:支持的actions,由于目前只支持`musicUrl`,所以固定传`['musicUrl']`即可<br>`sources[kw/kg/tx/wy/mg].qualitys`:该源支持的音质列表,有效的值为`['128k', '320k', 'flac']`,该字段用于控制应用可用的音质类型 | `inited` | 脚本初始化完成后发送给应用的事件名,发送该事件时需要传入以下信息:`{status, sources, openDevTools}`<br>`status`:初始化结果(`true`成功,`false`失败)<br>`openDevTools`:是否打开DevTools,此选项可用于开发脚本时的调试<br>`sources`:支持的源信息对象,<br>`sources[kw/kg/tx/wy/mg].name`:源的名字(目前非必须)<br>`sources[kw/kg/tx/wy/mg].type`:源类型,目前固定值需为`music`<br>`sources[kw/kg/tx/wy/mg].actions`:支持的actions,由于目前只支持`musicUrl`,所以固定传`['musicUrl']`即可<br>`sources[kw/kg/tx/wy/mg].qualitys`:该源支持的音质列表,有效的值为`['128k', '320k', 'flac']`,该字段用于控制应用可用的音质类型
| `request` | 应用API请求事件名,回调入参:`handler({ source, action, info})`,回调必须返回`Promise`对象<br>`source`:音乐源,可能的值取决于初始化时传入的`sources`对象的源key值<br>`info`:请求附加信息,内容根据`action`变化<br>`action`:请求操作类型,目前只有`musicUrl`,即获取音乐URL链接,需要在 Promise 返回歌曲 url,`info`的结构:`{type, musicInfo}`,`info.type`:音乐质量,可能的值有`128k` / `320k` / `flac`(取决于初始化时对应源传入的`qualitys`值中的一个),`info.musicInfo`:音乐信息对象,里面有音乐ID、名字等信息 | `request` | 应用API请求事件名,回调入参:`handler({ source, action, info})`,回调必须返回`Promise`对象<br>`source`:音乐源,可能的值取决于初始化时传入的`sources`对象的源key值<br>`info`:请求附加信息,内容根据`action`变化<br>`action`:请求操作类型,目前只有`musicUrl`,即获取音乐URL链接,需要在 Promise 返回歌曲 url,`info`的结构:`{type, musicInfo}`,`info.type`:音乐质量,可能的值有`128k` / `320k` / `flac`(取决于初始化时对应源传入的`qualitys`值中的一个),`info.musicInfo`:音乐信息对象,里面有音乐ID、名字等信息
| `updateAlert` | 显示源更新弹窗,发送该事件时的参数:`{log, updateUrl}`<br>`log`:更新日志,必传,字符串类型,内容可以使用`\n`换行,最大长度1024,超过此长度后将被截取超出的部分<br>`updateUrl`:更新地址,用于引导用户去该地址更新源,选传,需为http协议的url地址,最大长度1024<br>此事件每次运行脚本只能调用一次(源版本v1.2.0新增)<br>例子:`lx.send(lx.EVENT_NAMES.updateAlert, { log: 'hello world', updateUrl: 'https://xxx.com' })`
#### `window.lx.on` #### `window.lx.on`
+2 -1
View File
@@ -48,7 +48,8 @@
软件变化请查看:[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)<br> 软件变化请查看:[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)<br>
软件下载请转到:[发布页面](https://github.com/lyswhut/lx-music-desktop/releases)<br> 软件下载请转到:[发布页面](https://github.com/lyswhut/lx-music-desktop/releases)<br>
或者到网盘下载(网盘内有MAC、windows版):`https://www.lanzoui.com/b0bf2cfa/` 密码:`glqw`(若链接无法打开请百度:蓝奏云链接打不开)<br> 或者到网盘下载(网盘内有MAC、windows版):`https://www.lanzoui.com/b0bf2cfa/` 密码:`glqw`(若链接无法打开请百度:蓝奏云链接打不开)<br>
使用常见问题请转至:[常见问题](https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md) 使用常见问题请转至:[常见问题](https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md)<br>
移动版项目地址:<https://github.com/lyswhut/lx-music-mobile>
#### Scheme URL支持 #### Scheme URL支持
+451 -284
View File
File diff suppressed because it is too large Load Diff
+21 -21
View File
@@ -1,6 +1,6 @@
{ {
"name": "lx-music-desktop", "name": "lx-music-desktop",
"version": "1.17.1", "version": "1.18.0",
"description": "一个免费的音乐查找助手", "description": "一个免费的音乐查找助手",
"main": "./dist/electron/main.js", "main": "./dist/electron/main.js",
"productName": "lx-music-desktop", "productName": "lx-music-desktop",
@@ -173,32 +173,32 @@
}, },
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme", "homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
"devDependencies": { "devDependencies": {
"@babel/core": "^7.16.12", "@babel/core": "^7.17.5",
"@babel/eslint-parser": "^7.16.5", "@babel/eslint-parser": "^7.17.0",
"@babel/plugin-proposal-class-properties": "^7.16.7", "@babel/plugin-proposal-class-properties": "^7.16.7",
"@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-modules-umd": "^7.16.7", "@babel/plugin-transform-modules-umd": "^7.16.7",
"@babel/plugin-transform-runtime": "^7.16.10", "@babel/plugin-transform-runtime": "^7.17.0",
"@babel/polyfill": "^7.12.1", "@babel/polyfill": "^7.12.1",
"@babel/preset-env": "^7.16.11", "@babel/preset-env": "^7.16.11",
"babel-loader": "^8.2.3", "babel-loader": "^8.2.3",
"babel-preset-minify": "^0.5.1", "babel-preset-minify": "^0.5.1",
"browserslist": "^4.19.1", "browserslist": "^4.19.3",
"cfonts": "^2.10.0", "cfonts": "^2.10.0",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"changelog-parser": "^2.8.0", "changelog-parser": "^2.8.0",
"copy-webpack-plugin": "^10.2.1", "copy-webpack-plugin": "^10.2.4",
"core-js": "^3.20.3", "core-js": "^3.21.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"css-loader": "^6.5.1", "css-loader": "^6.6.0",
"css-minimizer-webpack-plugin": "^3.4.1", "css-minimizer-webpack-plugin": "^3.4.1",
"del": "^6.0.0", "del": "^6.0.0",
"electron": "^13.6.8", "electron": "^13.6.9",
"electron-builder": "^22.11.7", "electron-builder": "^22.14.13",
"electron-debug": "^3.2.0", "electron-debug": "^3.2.0",
"electron-devtools-installer": "^3.2.0", "electron-devtools-installer": "^3.2.0",
"electron-to-chromium": "^1.4.56", "electron-to-chromium": "^1.4.73",
"eslint": "^8.7.0", "eslint": "^8.10.0",
"eslint-config-standard": "^16.0.3", "eslint-config-standard": "^16.0.3",
"eslint-formatter-friendly": "^7.0.0", "eslint-formatter-friendly": "^7.0.0",
"eslint-plugin-html": "^6.2.0", "eslint-plugin-html": "^6.2.0",
@@ -206,7 +206,7 @@
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.0.0", "eslint-plugin-promise": "^6.0.0",
"eslint-plugin-standard": "^4.1.0", "eslint-plugin-standard": "^4.1.0",
"eslint-plugin-vue": "^8.4.0", "eslint-plugin-vue": "^8.5.0",
"eslint-webpack-plugin": "^3.1.1", "eslint-webpack-plugin": "^3.1.1",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
@@ -215,7 +215,7 @@
"markdown-it": "^12.3.2", "markdown-it": "^12.3.2",
"mini-css-extract-plugin": "^2.5.3", "mini-css-extract-plugin": "^2.5.3",
"node-loader": "^2.0.0", "node-loader": "^2.0.0",
"postcss": "^8.4.5", "postcss": "^8.4.7",
"postcss-loader": "^6.2.1", "postcss-loader": "^6.2.1",
"postcss-pxtorem": "^6.0.0", "postcss-pxtorem": "^6.0.0",
"pug": "^3.0.2", "pug": "^3.0.2",
@@ -227,22 +227,22 @@
"svg-sprite-loader": "^6.0.11", "svg-sprite-loader": "^6.0.11",
"svg-transform-loader": "^2.0.13", "svg-transform-loader": "^2.0.13",
"svgo-loader": "^3.0.0", "svgo-loader": "^3.0.0",
"terser-webpack-plugin": "^5.3.0", "terser-webpack-plugin": "^5.3.1",
"url-loader": "^4.1.1", "url-loader": "^4.1.1",
"vue-loader": "^17.0.0", "vue-loader": "^17.0.0",
"vue-template-compiler": "^2.6.14", "vue-template-compiler": "^2.6.14",
"webpack": "^5.67.0", "webpack": "^5.69.1",
"webpack-cli": "^4.9.2", "webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.7.3", "webpack-dev-server": "^4.7.4",
"webpack-hot-middleware": "github:lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1", "webpack-hot-middleware": "github:lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1",
"webpack-merge": "^5.8.0" "webpack-merge": "^5.8.0"
}, },
"dependencies": { "dependencies": {
"bufferutil": "^4.0.6", "bufferutil": "^4.0.6",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"electron-log": "^4.4.5", "electron-log": "^4.4.6",
"electron-store": "^8.0.1", "electron-store": "^8.0.1",
"electron-updater": "^4.6.1", "electron-updater": "^4.6.5",
"font-list": "github:lyswhut/node-font-list#4edbb1933b49a9bac1eedd63a31da16b487fe57d", "font-list": "github:lyswhut/node-font-list#4edbb1933b49a9bac1eedd63a31da16b487fe57d",
"http-terminator": "^3.0.4", "http-terminator": "^3.0.4",
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
@@ -256,8 +256,8 @@
"socket.io": "^4.4.1", "socket.io": "^4.4.1",
"sortablejs": "^1.14.0", "sortablejs": "^1.14.0",
"utf-8-validate": "^5.0.8", "utf-8-validate": "^5.0.8",
"vue": "^3.2.29", "vue": "^3.2.31",
"vue-i18n": "^9.2.0-beta.29", "vue-i18n": "^9.2.0-beta.30",
"vue-router": "^4.0.12", "vue-router": "^4.0.12",
"vuex": "^4.0.2" "vuex": "^4.0.2"
} }
+21 -4
View File
@@ -1,12 +1,29 @@
### 新增
- 新增“双击列表里的歌曲时自动切换到当前列表播放”设置,此功能仅对歌单、排行榜有效,默认关闭
- 新增打开收藏的在线列表的对应平台详情页功能,可以在我的列表-列表右键菜单中使用
- 新增定时暂停播放功能,由于此功能大多数人可能不常用,所以将其放在设置-基本设置中
- 新增任务栏缩略图工具栏控制按钮(此功能仅在Windows平台可用),按钮分别为收藏/取消收藏(将歌曲添加到“我的收藏”列表)、上一曲、播放/暂停、下一曲
- 新增设置-基本设置-软件字体设置,此设置可用于设置主界面的字体(已知的问题:Windows 7 下可能会出现字体列表为空的情况,这是当前系统的 Powershell 版本小于5.1导致的,请自行尝试看常见解决)
- 新增Scheme URL对音乐搜索的调用支持,详情看常见问题-Scheme URL支持
- 新增Scheme URL以url传参的方式调用,详情看常见问题-Scheme URL支持
- 自定义源新增更新弹窗方法,同时自定义源管理新增是否允许源显示更新弹窗设置(出于防止滥用考虑),当源作者想要通知用户源已更新时,可以调用此方法弹窗告诉用户,调用说明看常见问题-自定义源部分
### 优化 ### 优化
- 优化kw源英文与翻译歌词的匹配 - 过滤tx源某些不支持播放的歌曲,解决播放此类内容会导致意外的问题
- 把歌曲的热门评论与最新评论拆分成两个列表显示
### 修复 ### 修复
- 修复快捷键与默认按键行为冲突的问题,现在若将某些有默认行为的按键(如在列表中上、下箭头、Home、End等键可以使列表滚动)设置为快捷键时,将禁用其默认行为 - 修复排行榜名字右击菜单的播放功能在播放非激活的列表时的列表获取问题
- 修复列表的聚焦问题,现在在列表中使用上、下箭头、空格等键滚动列表时不会导致滚动到一定距离后丢失焦点的问题 - 修复修改列表名时无法使用`Ctrl`键的问题
- 修复wy源某些歌曲获取歌词翻译的问题处理
- 修复下载功能的歌词换源时会进入死循环的问题
- 修复某些歌曲无法下载的问题
- 修复windows平台下软件目录存在`portable`文件夹时,仍会创建`C:\Users\<user>\AppData\Roaming\lx-music-desktop\Dictionaries\en-US-9-0.bdic`文件的问题,现在不会再创建文件,但仍会创建空目录(Electron的问题,目前暂无解决方法)
- 修复播放器的停止逻辑问题
### 其他 ### 其他
- 更新electron到v13.6.8 - 更新electron到v13.6.9
+1 -1
View File
File diff suppressed because one or more lines are too long
+5 -1
View File
@@ -2,7 +2,7 @@ const path = require('path')
const os = require('os') const os = require('os')
const defaultSetting = { const defaultSetting = {
version: '1.0.48', version: '1.0.51',
player: { player: {
togglePlayMethod: 'listLoop', togglePlayMethod: 'listLoop',
highQuality: false, highQuality: false,
@@ -16,6 +16,8 @@ const defaultSetting = {
isPlayLxlrc: true, isPlayLxlrc: true,
isSavePlayTime: false, isSavePlayTime: false,
audioVisualization: false, audioVisualization: false,
waitPlayEndStop: true,
waitPlayEndStopTime: '',
}, },
desktopLyric: { desktopLyric: {
enable: false, enable: false,
@@ -36,6 +38,7 @@ const defaultSetting = {
}, },
}, },
list: { list: {
isClickPlayList: false,
isShowAlbumName: true, isShowAlbumName: true,
isShowSource: true, isShowSource: true,
isSaveScrollLocation: true, isSaveScrollLocation: true,
@@ -99,6 +102,7 @@ const defaultSetting = {
sourceId: 'kw', sourceId: 'kw',
apiSource: 'temp', apiSource: 'temp',
sourceNameType: 'alias', sourceNameType: 'alias',
font: '',
isShowAnimation: true, isShowAnimation: true,
randomAnimate: true, randomAnimate: true,
ignoreVersion: null, ignoreVersion: null,
+8
View File
@@ -11,6 +11,9 @@ const names = {
clear_env_params_deeplink: 'clear_env_params_deeplink', clear_env_params_deeplink: 'clear_env_params_deeplink',
wait: 'wait', wait: 'wait',
wait_cancel: 'wait_cancel', wait_cancel: 'wait_cancel',
interval: 'interval',
interval_callback: 'interval_callback',
interval_cancel: 'interval_cancel',
open_dev_tools: 'open_dev_tools', open_dev_tools: 'open_dev_tools',
set_music_meta: 'set_music_meta', set_music_meta: 'set_music_meta',
@@ -66,6 +69,8 @@ const names = {
request_user_api_cancel: 'request_user_api_cancel', request_user_api_cancel: 'request_user_api_cancel',
get_user_api_status: 'get_user_api_status', get_user_api_status: 'get_user_api_status',
user_api_status: 'user_api_status', user_api_status: 'user_api_status',
user_api_show_update_alert: 'user_api_show_update_alert',
user_api_set_allow_update_alert: 'user_api_set_allow_update_alert',
get_lyric: 'get_lyric', get_lyric: 'get_lyric',
save_lyric: 'save_lyric', save_lyric: 'save_lyric',
@@ -81,6 +86,9 @@ const names = {
sync_action_list: 'sync_action_list', sync_action_list: 'sync_action_list',
sync_list: 'sync_list', sync_list: 'sync_list',
taskbar_set_thumbar_buttons: 'taskbar_set_thumbar_buttons',
taskbar_set_thumbnail_clip: 'taskbar_set_thumbnail_clip',
taskbar_on_thumbar_button_click: 'taskbar_on_thumbar_button_click',
}, },
winLyric: { winLyric: {
close: 'close', close: 'close',
+18 -1
View File
@@ -118,6 +118,7 @@
"lists__remove_tip_button": "Yes, that's right", "lists__remove_tip_button": "Yes, that's right",
"lists__rename": "Rename", "lists__rename": "Rename",
"lists__sort_list": "Sort songs", "lists__sort_list": "Sort songs",
"lists__source_detail": "Playlist Page",
"lists__sync": "Update", "lists__sync": "Update",
"load_list_file_error_detail": "We have helped you back up the old list file to {path}\nIt is stored in JSON format, you can try to repair and restore it manually\n\nError details: {detail}", "load_list_file_error_detail": "We have helped you back up the old list file to {path}\nIt is stored in JSON format, you can try to repair and restore it manually\n\nError details: {detail}",
"load_list_file_error_title": "Error loading playlist data", "load_list_file_error_title": "Error loading playlist data",
@@ -139,9 +140,18 @@
"my_list": "Your Library", "my_list": "Your Library",
"no_item": "Nothing's here...", "no_item": "Nothing's here...",
"not_agree": "Not accept", "not_agree": "Not accept",
"ok": "OK",
"pagination__next": "Next page", "pagination__next": "Next page",
"pagination__page": "Page {num}", "pagination__page": "Page {num}",
"pagination__prev": "Previous page", "pagination__prev": "Previous page",
"play_timeout": "Timed pause",
"play_timeout_close": "Close",
"play_timeout_confirm": "Confirm",
"play_timeout_end": "Wait for the song to finish before pausing",
"play_timeout_stop": "Cancel timer",
"play_timeout_tip": "Pause after {time}",
"play_timeout_unit": "minute",
"play_timeout_update": "Update timing",
"player__add_music_to": "Add the current song to...", "player__add_music_to": "Add the current song to...",
"player__buffering": "Buffering...", "player__buffering": "Buffering...",
"player__desktop_lyric_lock": "Right click to lock lyrics", "player__desktop_lyric_lock": "Right click to lock lyrics",
@@ -200,6 +210,7 @@
"setting__basic_control_btn_position": "Control Button Position", "setting__basic_control_btn_position": "Control Button Position",
"setting__basic_control_btn_position_left": "Left", "setting__basic_control_btn_position_left": "Left",
"setting__basic_control_btn_position_right": "Right", "setting__basic_control_btn_position_right": "Right",
"setting__basic_font": "Font",
"setting__basic_lang": "Language", "setting__basic_lang": "Language",
"setting__basic_lang_title": "The language displayed in the software", "setting__basic_lang_title": "The language displayed in the software",
"setting__basic_show_animation": "Show switching animation", "setting__basic_show_animation": "Show switching animation",
@@ -284,6 +295,7 @@
"setting__list_add_music_location_type": "Position when adding a song to the list", "setting__list_add_music_location_type": "Position when adding a song to the list",
"setting__list_add_music_location_type_bottom": "Bottom", "setting__list_add_music_location_type_bottom": "Bottom",
"setting__list_add_music_location_type_top": "Top", "setting__list_add_music_location_type_top": "Top",
"setting__list_click_action": "Automatically switch to the current list when double-clicking a song in the list (only valid for playlists and rankings)",
"setting__list_scroll": "Remember the position of the scroll bar of the playlist (only valid for my music classification)", "setting__list_scroll": "Remember the position of the scroll bar of the playlist (only valid for my music classification)",
"setting__list_source": "Show song source (only valid for my music category)", "setting__list_source": "Show song source (only valid for my music category)",
"setting__network": "Network", "setting__network": "Network",
@@ -320,6 +332,7 @@
"setting__play_quality": "Play 320K quality songs first (if supported)", "setting__play_quality": "Play 320K quality songs first (if supported)",
"setting__play_save_play_time": "Remember playback progress", "setting__play_save_play_time": "Remember playback progress",
"setting__play_task_bar": "Show playing progress on the taskbar", "setting__play_task_bar": "Show playing progress on the taskbar",
"setting__play_timeout": "Timed pause",
"setting__search": "Search", "setting__search": "Search",
"setting__search_focus_search_box": "Automatically focus the search box on startup", "setting__search_focus_search_box": "Automatically focus the search box on startup",
"setting__search_history": "Search history", "setting__search_history": "Search history",
@@ -398,12 +411,16 @@
"theme_purple": "Purple", "theme_purple": "Purple",
"theme_red": "Red", "theme_red": "Red",
"theme_yellow": "Yellow", "theme_yellow": "Yellow",
"user_api__allow_show_update_alert": "Allow update popup to show",
"user_api__btn_export": "Export", "user_api__btn_export": "Export",
"user_api__btn_import": "Import", "user_api__btn_import": "Import",
"user_api__btn_remove": "Remove", "user_api__btn_remove": "Remove",
"user_api__import_file": "Select music API script file", "user_api__import_file": "Select music API script file",
"user_api__max_tip": "There can only be a maximum of 20 sources at the same time🤪\nIf you want to continue importing, please remove some old sources to make room",
"user_api__noitem": "There is nothing here...😲", "user_api__noitem": "There is nothing here...😲",
"user_api__note": "Tip: Although we have isolated the script's running environment as much as possible, importing scripts containing malicious behaviors may still affect your system. Please import them carefully.", "user_api__note": "Tip: Although we have isolated the script's running environment as much as possible, importing scripts containing malicious behaviors may still affect your system. Please import them carefully.",
"user_api__readme": "Source writing instructions: ", "user_api__readme": "Source writing instructions: ",
"user_api__title": "Custom Source Management" "user_api__title": "Custom Source Management",
"user_api__update_alert": "Custom source [{name}] found new version:",
"user_api__update_alert_open_url": "Open update address"
} }
+18 -1
View File
@@ -118,6 +118,7 @@
"lists__remove_tip_button": "是的 没错", "lists__remove_tip_button": "是的 没错",
"lists__rename": "重命名", "lists__rename": "重命名",
"lists__sort_list": "排序歌曲", "lists__sort_list": "排序歌曲",
"lists__source_detail": "歌单详情页",
"lists__sync": "更新", "lists__sync": "更新",
"load_list_file_error_detail": "我们已经帮你把旧的列表文件备份到{path}\n它以 JSON 格式存储,你可以尝试手动修复并恢复它\n\n错误详情:{detail}", "load_list_file_error_detail": "我们已经帮你把旧的列表文件备份到{path}\n它以 JSON 格式存储,你可以尝试手动修复并恢复它\n\n错误详情:{detail}",
"load_list_file_error_title": "播放列表数据加载错误(建议到GitHub或加群反馈)", "load_list_file_error_title": "播放列表数据加载错误(建议到GitHub或加群反馈)",
@@ -139,9 +140,18 @@
"my_list": "我的列表", "my_list": "我的列表",
"no_item": "列表竟然是空的...", "no_item": "列表竟然是空的...",
"not_agree": "不接受", "not_agree": "不接受",
"ok": "我知道了",
"pagination__next": "下一页", "pagination__next": "下一页",
"pagination__page": "第 {num} 页", "pagination__page": "第 {num} 页",
"pagination__prev": "上一页", "pagination__prev": "上一页",
"play_timeout": "定时暂停",
"play_timeout_close": "关闭",
"play_timeout_confirm": "确认",
"play_timeout_end": "等待歌曲播放完毕再暂停",
"play_timeout_stop": "取消定时",
"play_timeout_tip": "{time} 后暂停播放",
"play_timeout_unit": "分钟",
"play_timeout_update": "更新定时",
"player__add_music_to": "添加当前歌曲到...", "player__add_music_to": "添加当前歌曲到...",
"player__buffering": "缓冲中...", "player__buffering": "缓冲中...",
"player__desktop_lyric_lock": "右击锁定歌词", "player__desktop_lyric_lock": "右击锁定歌词",
@@ -200,6 +210,7 @@
"setting__basic_control_btn_position": "控制按钮位置", "setting__basic_control_btn_position": "控制按钮位置",
"setting__basic_control_btn_position_left": "左边", "setting__basic_control_btn_position_left": "左边",
"setting__basic_control_btn_position_right": "右边", "setting__basic_control_btn_position_right": "右边",
"setting__basic_font": "字体",
"setting__basic_lang": "语言", "setting__basic_lang": "语言",
"setting__basic_lang_title": "软件显示的语言", "setting__basic_lang_title": "软件显示的语言",
"setting__basic_show_animation": "显示切换动画", "setting__basic_show_animation": "显示切换动画",
@@ -284,6 +295,7 @@
"setting__list_add_music_location_type": "添加歌曲到列表时的位置", "setting__list_add_music_location_type": "添加歌曲到列表时的位置",
"setting__list_add_music_location_type_bottom": "底部", "setting__list_add_music_location_type_bottom": "底部",
"setting__list_add_music_location_type_top": "顶部", "setting__list_add_music_location_type_top": "顶部",
"setting__list_click_action": "双击列表里的歌曲时自动切换到当前列表播放(仅对歌单、排行榜有效)",
"setting__list_scroll": "记住播放列表滚动条位置(仅对我的音乐分类有效)", "setting__list_scroll": "记住播放列表滚动条位置(仅对我的音乐分类有效)",
"setting__list_source": "显示歌曲源(仅对我的音乐分类有效)", "setting__list_source": "显示歌曲源(仅对我的音乐分类有效)",
"setting__network": "网络设置", "setting__network": "网络设置",
@@ -320,6 +332,7 @@
"setting__play_quality": "优先播放320K品质的歌曲(如果支持)", "setting__play_quality": "优先播放320K品质的歌曲(如果支持)",
"setting__play_save_play_time": "记住播放进度", "setting__play_save_play_time": "记住播放进度",
"setting__play_task_bar": "在任务栏上显示当前歌曲播放进度", "setting__play_task_bar": "在任务栏上显示当前歌曲播放进度",
"setting__play_timeout": "定时暂停",
"setting__search": "搜索设置", "setting__search": "搜索设置",
"setting__search_focus_search_box": "启动时自动聚焦搜索框", "setting__search_focus_search_box": "启动时自动聚焦搜索框",
"setting__search_history": "显示历史搜索记录", "setting__search_history": "显示历史搜索记录",
@@ -398,12 +411,16 @@
"theme_purple": "重斤球紫", "theme_purple": "重斤球紫",
"theme_red": "热情似火", "theme_red": "热情似火",
"theme_yellow": "信口雌黄", "theme_yellow": "信口雌黄",
"user_api__allow_show_update_alert": "允许显示更新弹窗",
"user_api__btn_export": "导出", "user_api__btn_export": "导出",
"user_api__btn_import": "导入", "user_api__btn_import": "导入",
"user_api__btn_remove": "移除", "user_api__btn_remove": "移除",
"user_api__import_file": "选择音乐API脚本文件", "user_api__import_file": "选择音乐API脚本文件",
"user_api__max_tip": "最多只能同时存在20个源哦🤪\n想要继续导入的话,请先移除一些旧的源腾出位置吧",
"user_api__noitem": "这里竟然是空的 😲", "user_api__noitem": "这里竟然是空的 😲",
"user_api__note": "提示:虽然我们已经尽可能地隔离了脚本的运行环境,但导入包含恶意行为的脚本仍可能会影响你的系统,请谨慎导入。", "user_api__note": "提示:虽然我们已经尽可能地隔离了脚本的运行环境,但导入包含恶意行为的脚本仍可能会影响你的系统,请谨慎导入。",
"user_api__readme": "源编写说明:", "user_api__readme": "源编写说明:",
"user_api__title": "自定义源管理" "user_api__title": "自定义源管理",
"user_api__update_alert": "自定义源 [{name}] 发现新版本:",
"user_api__update_alert_open_url": "打开更新地址"
} }
+18 -1
View File
@@ -118,6 +118,7 @@
"lists__remove_tip_button": "是的 沒錯", "lists__remove_tip_button": "是的 沒錯",
"lists__rename": "重命名", "lists__rename": "重命名",
"lists__sort_list": "排序歌曲", "lists__sort_list": "排序歌曲",
"lists__source_detail": "歌單詳情頁",
"lists__sync": "更新", "lists__sync": "更新",
"load_list_file_error_detail": "我們已經幫你把舊的列表文件備份到{path}\n它以 JSON 格式存儲,你可以嘗試手動修復並恢復它\n\n錯誤詳情:{detail}", "load_list_file_error_detail": "我們已經幫你把舊的列表文件備份到{path}\n它以 JSON 格式存儲,你可以嘗試手動修復並恢復它\n\n錯誤詳情:{detail}",
"load_list_file_error_title": "播放列表數據加載錯誤", "load_list_file_error_title": "播放列表數據加載錯誤",
@@ -139,9 +140,18 @@
"my_list": "我的列表", "my_list": "我的列表",
"no_item": "列表竟然是空的...", "no_item": "列表竟然是空的...",
"not_agree": "不接受", "not_agree": "不接受",
"ok": "我知道了",
"pagination__next": "下一頁", "pagination__next": "下一頁",
"pagination__page": "第 {num} 頁", "pagination__page": "第 {num} 頁",
"pagination__prev": "上一頁", "pagination__prev": "上一頁",
"play_timeout": "定時暫停",
"play_timeout_close": "關閉",
"play_timeout_confirm": "確認",
"play_timeout_end": "等待歌曲播放完畢再暫停",
"play_timeout_stop": "取消定時",
"play_timeout_tip": "{time} 後暫停播放",
"play_timeout_unit": "分鐘",
"play_timeout_update": "更新定時",
"player__add_music_to": "添加當前歌曲到...", "player__add_music_to": "添加當前歌曲到...",
"player__album": "專輯名:", "player__album": "專輯名:",
"player__buffering": "緩衝中...", "player__buffering": "緩衝中...",
@@ -200,6 +210,7 @@
"setting__basic_control_btn_position": "控制按鈕位置", "setting__basic_control_btn_position": "控制按鈕位置",
"setting__basic_control_btn_position_left": "左邊", "setting__basic_control_btn_position_left": "左邊",
"setting__basic_control_btn_position_right": "右邊", "setting__basic_control_btn_position_right": "右邊",
"setting__basic_font": "字體",
"setting__basic_lang": "語言", "setting__basic_lang": "語言",
"setting__basic_lang_title": "軟件顯示的語言", "setting__basic_lang_title": "軟件顯示的語言",
"setting__basic_show_animation": "顯示切換動畫", "setting__basic_show_animation": "顯示切換動畫",
@@ -284,6 +295,7 @@
"setting__list_add_music_location_type": "添加歌曲到列表時的位置", "setting__list_add_music_location_type": "添加歌曲到列表時的位置",
"setting__list_add_music_location_type_bottom": "底部", "setting__list_add_music_location_type_bottom": "底部",
"setting__list_add_music_location_type_top": "頂部", "setting__list_add_music_location_type_top": "頂部",
"setting__list_click_action": "雙擊列表裡的歌曲時自動切換到當前列表播放(僅對歌單、排行榜有效)",
"setting__list_scroll": "記住播放列表滾動條位置(僅對我的音樂分類有效)", "setting__list_scroll": "記住播放列表滾動條位置(僅對我的音樂分類有效)",
"setting__list_source": "顯示歌曲源(僅對我的音樂分類有效)", "setting__list_source": "顯示歌曲源(僅對我的音樂分類有效)",
"setting__network": "網絡設置", "setting__network": "網絡設置",
@@ -320,6 +332,7 @@
"setting__play_quality": "優先播放320K品質的歌曲(如果支持)", "setting__play_quality": "優先播放320K品質的歌曲(如果支持)",
"setting__play_save_play_time": "記住播放進度", "setting__play_save_play_time": "記住播放進度",
"setting__play_task_bar": "在任務欄上顯示當前歌曲播放進度", "setting__play_task_bar": "在任務欄上顯示當前歌曲播放進度",
"setting__play_timeout": "定時暫停",
"setting__search": "搜索設置", "setting__search": "搜索設置",
"setting__search_focus_search_box": "啟動時自動聚焦搜索框", "setting__search_focus_search_box": "啟動時自動聚焦搜索框",
"setting__search_history": "顯示歷史搜索記錄", "setting__search_history": "顯示歷史搜索記錄",
@@ -398,12 +411,16 @@
"theme_purple": "重斤球紫", "theme_purple": "重斤球紫",
"theme_red": "熱情似火", "theme_red": "熱情似火",
"theme_yellow": "信口雌黃", "theme_yellow": "信口雌黃",
"user_api__allow_show_update_alert": "允許顯示更新彈窗",
"user_api__btn_export": "導出", "user_api__btn_export": "導出",
"user_api__btn_import": "導入", "user_api__btn_import": "導入",
"user_api__btn_remove": "移除", "user_api__btn_remove": "移除",
"user_api__import_file": "選擇音樂API腳本文件", "user_api__import_file": "選擇音樂API腳本文件",
"user_api__max_tip": "最多只能同時存在20個源哦🤪\n想要繼續導入的話,請先移除一些舊的源騰出位置吧",
"user_api__noitem": "這裡竟然是空的 😲", "user_api__noitem": "這裡竟然是空的 😲",
"user_api__note": "提示:雖然我們已經盡可能地隔離了腳本的運行環境,但導入包含惡意行為的腳本仍可能會影響你的系統,請謹慎導入。", "user_api__note": "提示:雖然我們已經盡可能地隔離了腳本的運行環境,但導入包含惡意行為的腳本仍可能會影響你的系統,請謹慎導入。",
"user_api__readme": "源編寫說明:", "user_api__readme": "源編寫說明:",
"user_api__title": "自定義源管理" "user_api__title": "自定義源管理",
"user_api__update_alert": "自定義源 [{name}] 發現新版本:",
"user_api__update_alert_open_url": "打開更新地址"
} }
+2
View File
@@ -6,6 +6,7 @@ const Tray = require('./Tray')
const WinLyric = require('./WinLyric') const WinLyric = require('./WinLyric')
const HotKey = require('./HotKey') const HotKey = require('./HotKey')
const { Event: TaskBar } = require('../modules/taskbar')
const { Event: UserApi } = require('../modules/userApi') const { Event: UserApi } = require('../modules/userApi')
const { Event: Sync } = require('../modules/sync') const { Event: Sync } = require('../modules/sync')
@@ -15,5 +16,6 @@ if (!global.lx_event.tray) global.lx_event.tray = new Tray()
if (!global.lx_event.winLyric) global.lx_event.winLyric = new WinLyric() if (!global.lx_event.winLyric) global.lx_event.winLyric = new WinLyric()
if (!global.lx_event.hotKey) global.lx_event.hotKey = new HotKey() if (!global.lx_event.hotKey) global.lx_event.hotKey = new HotKey()
if (!global.lx_event.taskbar) global.lx_event.taskbar = new TaskBar()
if (!global.lx_event.userApi) global.lx_event.userApi = new UserApi() if (!global.lx_event.userApi) global.lx_event.userApi = new UserApi()
if (!global.lx_event.sync) global.lx_event.sync = new Sync() if (!global.lx_event.sync) global.lx_event.sync = new Sync()
+4
View File
@@ -129,6 +129,10 @@ app.on('web-contents-created', (event, contents) => {
event.preventDefault() event.preventDefault()
} }
}) })
// disable create dictionary
// https://github.com/lyswhut/lx-music-desktop/issues/773
contents.session.setSpellCheckerDictionaryDownloadURL('http://0.0.0.0')
}) })
-1
View File
@@ -2,4 +2,3 @@ require('./appMenu')
require('./winLyric') require('./winLyric')
require('./tray') require('./tray')
require('./hotKey') require('./hotKey')
require('./userApi')
+11
View File
@@ -0,0 +1,11 @@
const { EventEmitter } = require('events')
const TASKBAR_EVENT_NAME = require('./name')
class TaskBar extends EventEmitter {
thumbarButtonClick(type) {
this.emit(TASKBAR_EVENT_NAME.thumbarButtonClick, type)
}
}
module.exports = TaskBar
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
thumbarButtonClick: 'thumbarButtonClick',
}
+92
View File
@@ -0,0 +1,92 @@
const path = require('path')
const Event = require('./event/event')
const eventNames = require('./event/name')
exports.Event = Event
exports.eventNames = eventNames
exports.setThumbnailClip = (region = { x: 0, y: 0, width: 0, height: 0 }) => {
if (!global.modules.mainWindow) return
return global.modules.mainWindow.setThumbnailClip(region)
}
const getIconPath = name => {
return path.join(global.__static, 'images/taskbar', name + '.png')
}
const buttonsFlags = {
empty: true,
collect: false,
play: false,
next: true,
prev: true,
}
const createButtons = ({ empty = false, collect = false, play = false, next = true, prev = true }) => {
const buttons = [
collect
? {
icon: getIconPath('collected'),
click() {
global.lx_event.taskbar.thumbarButtonClick('unCollect')
},
tooltip: '取消收藏',
flags: ['nobackground'],
}
: {
icon: getIconPath('collect'),
click() {
global.lx_event.taskbar.thumbarButtonClick('collect')
},
tooltip: '收藏',
flags: ['nobackground'],
},
{
icon: getIconPath('prev'),
click() {
global.lx_event.taskbar.thumbarButtonClick('prev')
},
tooltip: '上一曲',
flags: prev ? ['nobackground'] : ['nobackground', 'disabled'],
},
play
? {
icon: getIconPath('pause'),
click() {
global.lx_event.taskbar.thumbarButtonClick('pause')
},
tooltip: '暂停',
flags: ['nobackground'],
}
: {
icon: getIconPath('play'),
click() {
global.lx_event.taskbar.thumbarButtonClick('play')
},
tooltip: '播放',
flags: ['nobackground'],
},
{
icon: getIconPath('next'),
click() {
global.lx_event.taskbar.thumbarButtonClick('next')
},
tooltip: '下一曲',
flags: next ? ['nobackground'] : ['nobackground', 'disabled'],
},
]
if (empty) {
for (const button of buttons) {
button.flags = ['nobackground', 'disabled']
}
}
return buttons
}
exports.setThumbarButtons = ({ empty, collect, play, next, prev } = buttonsFlags) => {
if (!global.modules.mainWindow) return
buttonsFlags.empty = empty
buttonsFlags.collect = collect
buttonsFlags.play = play
buttonsFlags.next = next
buttonsFlags.prev = prev
global.modules.mainWindow.setThumbarButtons(createButtons(buttonsFlags))
}
+4
View File
@@ -5,6 +5,10 @@ class UserApi extends EventEmitter {
status(info) { status(info) {
this.emit(USER_API_EVENT_NAME.status, info) this.emit(USER_API_EVENT_NAME.status, info)
} }
showUpdateAlert(info) {
this.emit(USER_API_EVENT_NAME.showUpdateAlert, info)
}
} }
module.exports = UserApi module.exports = UserApi
+1
View File
@@ -1,3 +1,4 @@
module.exports = { module.exports = {
status: 'status', status: 'status',
showUpdateAlert: 'showUpdateAlert',
} }
+6 -3
View File
@@ -1,8 +1,8 @@
const Event = require('./event/event') const Event = require('./event/event')
const eventNames = require('./event/name') const eventNames = require('./event/name')
const { closeWindow } = require('./main') const { closeWindow } = require('./main')
const { getUserApis, importApi, removeApi } = require('./utils') const { getUserApis, importApi, removeApi, setAllowShowUpdateAlert: saveAllowShowUpdateAlert } = require('./utils')
const { request, cancelRequest, getStatus, loadApi } = require('./rendererEvent/rendererEvent') const { request, cancelRequest, getStatus, loadApi, setAllowShowUpdateAlert } = require('./rendererEvent/rendererEvent')
// const { getApiList, importApi, removeApi, setApi, getStatus, request, eventNames } // const { getApiList, importApi, removeApi, setApi, getStatus, request, eventNames }
let userApiId let userApiId
@@ -41,4 +41,7 @@ exports.setApi = async id => {
await loadApi(id) await loadApi(id)
} }
exports.setAllowShowUpdateAlert = (id, enable) => {
saveAllowShowUpdateAlert(id, enable)
setAllowShowUpdateAlert(id, enable)
}
+5 -1
View File
@@ -15,7 +15,6 @@ fs.readFile(path.join(dir, 'renderer/user-api.html'), 'utf8', (err, data) => {
}) })
const denyEvents = [ const denyEvents = [
'new-window',
'will-navigate', 'will-navigate',
'will-redirect', 'will-redirect',
'will-attach-webview', 'will-attach-webview',
@@ -49,11 +48,13 @@ exports.createWindow = async userApi => {
contextIsolation: true, contextIsolation: true,
// worldSafeExecuteJavaScript: true, // worldSafeExecuteJavaScript: true,
nodeIntegration: false, nodeIntegration: false,
nodeIntegrationInWorker: false,
spellcheck: false, spellcheck: false,
autoplayPolicy: 'document-user-activation-required', autoplayPolicy: 'document-user-activation-required',
enableWebSQL: false, enableWebSQL: false,
disableDialogs: true, disableDialogs: true,
nativeWindowOpen: false,
webgl: false, webgl: false,
images: false, images: false,
@@ -70,6 +71,9 @@ exports.createWindow = async userApi => {
// eslint-disable-next-line node/no-callback-literal // eslint-disable-next-line node/no-callback-literal
callback(false) callback(false)
}) })
global.modules.userApiWindow.webContents.setWindowOpenHandler(() => {
return { action: 'deny' }
})
winEvent(global.modules.userApiWindow) winEvent(global.modules.userApiWindow)
+35 -13
View File
@@ -3,14 +3,16 @@ const needle = require('needle')
const { createCipheriv, publicEncrypt, constants, randomBytes, createHash } = require('crypto') const { createCipheriv, publicEncrypt, constants, randomBytes, createHash } = require('crypto')
const USER_API_RENDERER_EVENT_NAME = require('../rendererEvent/name') const USER_API_RENDERER_EVENT_NAME = require('../rendererEvent/name')
const sendMessage = (action, status, data, message) => { const sendMessage = (action, data, status, message) => {
ipcRenderer.send(action, { status, data, message }) ipcRenderer.send(action, { data, status, message })
} }
let isInitedApi = false let isInitedApi = false
let isShowedUpdateAlert = false
const EVENT_NAMES = { const EVENT_NAMES = {
request: 'request', request: 'request',
inited: 'inited', inited: 'inited',
updateAlert: 'updateAlert',
} }
const eventNames = Object.values(EVENT_NAMES) const eventNames = Object.values(EVENT_NAMES)
const events = { const events = {
@@ -35,7 +37,7 @@ const supportActions = {
const handleRequest = (context, { requestKey, data }) => { const handleRequest = (context, { requestKey, data }) => {
// console.log(data) // console.log(data)
if (!events.request) return sendMessage(USER_API_RENDERER_EVENT_NAME.response, false, { requestKey }, 'Request event is not defined') if (!events.request) return sendMessage(USER_API_RENDERER_EVENT_NAME.response, { requestKey }, false, 'Request event is not defined')
try { try {
events.request.call(context, { source: data.source, action: data.action, info: data.info }).then(response => { events.request.call(context, { source: data.source, action: data.action, info: data.info }).then(response => {
let sendData = { let sendData = {
@@ -53,12 +55,12 @@ const handleRequest = (context, { requestKey, data }) => {
} }
break break
} }
sendMessage(USER_API_RENDERER_EVENT_NAME.response, true, sendData) sendMessage(USER_API_RENDERER_EVENT_NAME.response, sendData, true)
}).catch(err => { }).catch(err => {
sendMessage(USER_API_RENDERER_EVENT_NAME.response, false, { requestKey }, err.message) sendMessage(USER_API_RENDERER_EVENT_NAME.response, { requestKey }, false, err.message)
}) })
} catch (err) { } catch (err) {
sendMessage(USER_API_RENDERER_EVENT_NAME.response, false, { requestKey }, err.message) sendMessage(USER_API_RENDERER_EVENT_NAME.response, { requestKey }, false, err.message)
} }
} }
@@ -80,7 +82,7 @@ const handleRequest = (context, { requestKey, data }) => {
*/ */
const handleInit = (context, info) => { const handleInit = (context, info) => {
if (!info) { if (!info) {
sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, 'Init failed') sendMessage(USER_API_RENDERER_EVENT_NAME.init, null, false, 'Init failed')
// sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, typeof info.message === 'string' ? info.message.substring(0, 100) : '') // sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, typeof info.message === 'string' ? info.message.substring(0, 100) : '')
return return
} }
@@ -88,7 +90,7 @@ const handleInit = (context, info) => {
sendMessage(USER_API_RENDERER_EVENT_NAME.openDevTools) sendMessage(USER_API_RENDERER_EVENT_NAME.openDevTools)
} }
if (!info.status) { if (!info.status) {
sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, 'Init failed') sendMessage(USER_API_RENDERER_EVENT_NAME.init, null, false, 'Init failed')
// sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, typeof info.message === 'string' ? info.message.substring(0, 100) : '') // sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, typeof info.message === 'string' ? info.message.substring(0, 100) : '')
return return
} }
@@ -109,16 +111,28 @@ const handleInit = (context, info) => {
} }
} catch (error) { } catch (error) {
console.log(error) console.log(error)
sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, error.message) sendMessage(USER_API_RENDERER_EVENT_NAME.init, null, false, error.message)
return return
} }
sendMessage(USER_API_RENDERER_EVENT_NAME.init, true, sourceInfo) sendMessage(USER_API_RENDERER_EVENT_NAME.init, sourceInfo, true)
ipcRenderer.on(USER_API_RENDERER_EVENT_NAME.request, (event, data) => { ipcRenderer.on(USER_API_RENDERER_EVENT_NAME.request, (event, data) => {
handleRequest(context, data) handleRequest(context, data)
}) })
} }
const handleShowUpdateAlert = (data, resolve, reject) => {
if (!data || typeof data != 'object') return reject(new Error('parameter format error.'))
if (!data.log || typeof data.log != 'string') return reject(new Error('log is required.'))
if (data.updateUrl && !/^https?:\/\/[^\s$.?#].[^\s]*$/.test(data.updateUrl) && data.updateUrl.length > 1024) delete data.updateUrl
if (data.log.length > 1024) data.log = data.log.substring(0, 1024) + '...'
sendMessage(USER_API_RENDERER_EVENT_NAME.showUpdateAlert, {
log: data.log,
updateUrl: data.updateUrl,
})
resolve()
}
contextBridge.exposeInMainWorld('lx', { contextBridge.exposeInMainWorld('lx', {
EVENT_NAMES, EVENT_NAMES,
request(url, { method = 'get', timeout, headers, body, form, formData }, callback) { request(url, { method = 'get', timeout, headers, body, form, formData }, callback) {
@@ -165,12 +179,18 @@ contextBridge.exposeInMainWorld('lx', {
if (!eventNames.includes(eventName)) return reject(new Error('The event is not supported: ' + eventName)) if (!eventNames.includes(eventName)) return reject(new Error('The event is not supported: ' + eventName))
switch (eventName) { switch (eventName) {
case EVENT_NAMES.inited: case EVENT_NAMES.inited:
if (isInitedApi) return if (isInitedApi) return reject(new Error('Script is inited'))
isInitedApi = true isInitedApi = true
handleInit(this, data) handleInit(this, data)
resolve()
break
case EVENT_NAMES.updateAlert:
if (isShowedUpdateAlert) return reject(new Error('The update alert can only be called once.'))
isShowedUpdateAlert = true
handleShowUpdateAlert(data, resolve, reject)
break break
default: default:
resolve(new Error('Unknown event name: ' + eventName)) reject(new Error('Unknown event name: ' + eventName))
} }
}) })
}, },
@@ -180,7 +200,9 @@ contextBridge.exposeInMainWorld('lx', {
case EVENT_NAMES.request: case EVENT_NAMES.request:
events.request = handler events.request = handler
break break
default: return Promise.reject(new Error('The event is not supported: ' + eventName))
} }
return Promise.resolve()
}, },
utils: { utils: {
crypto: { crypto: {
@@ -208,7 +230,7 @@ contextBridge.exposeInMainWorld('lx', {
}, },
}, },
}, },
version: '1.1.0', version: '1.2.0',
// removeEvent(eventName, handler) { // removeEvent(eventName, handler) {
// if (!eventNames.includes(eventName)) return Promise.reject(new Error('The event is not supported: ' + eventName)) // if (!eventNames.includes(eventName)) return Promise.reject(new Error('The event is not supported: ' + eventName))
// let handlers // let handlers
@@ -3,6 +3,7 @@ const names = {
request: '', request: '',
response: '', response: '',
openDevTools: '', openDevTools: '',
showUpdateAlert: '',
} }
@@ -3,6 +3,7 @@ const { mainOn, mainSend } = require('@common/ipc')
const USER_API_RENDERER_EVENT_NAME = require('../rendererEvent/name') const USER_API_RENDERER_EVENT_NAME = require('../rendererEvent/name')
const { createWindow } = require('../main') const { createWindow } = require('../main')
const { getUserApis } = require('../utils') const { getUserApis } = require('../utils')
const { openDevTools } = require('@main/utils')
let userApi let userApi
let status = { status: true } let status = { status: true }
@@ -32,14 +33,22 @@ const handleResponse = (event, { status, data: { requestKey, result }, message }
} }
const handleOpenDevTools = () => { const handleOpenDevTools = () => {
if (global.modules.userApiWindow) { if (global.modules.userApiWindow) {
global.modules.userApiWindow.webContents.openDevTools({ openDevTools(global.modules.userApiWindow.webContents)
mode: 'undocked',
})
} }
} }
const handleShowUpdateAlert = (event, { data }) => {
if (!userApi.allowShowUpdateAlert) return
global.lx_event.userApi.showUpdateAlert({
name: userApi.name,
description: userApi.description,
log: data.log,
updateUrl: data.updateUrl,
})
}
mainOn(USER_API_RENDERER_EVENT_NAME.init, handleInit) mainOn(USER_API_RENDERER_EVENT_NAME.init, handleInit)
mainOn(USER_API_RENDERER_EVENT_NAME.response, handleResponse) mainOn(USER_API_RENDERER_EVENT_NAME.response, handleResponse)
mainOn(USER_API_RENDERER_EVENT_NAME.openDevTools, handleOpenDevTools) mainOn(USER_API_RENDERER_EVENT_NAME.openDevTools, handleOpenDevTools)
mainOn(USER_API_RENDERER_EVENT_NAME.showUpdateAlert, handleShowUpdateAlert)
exports.loadApi = async apiId => { exports.loadApi = async apiId => {
if (!apiId) return global.lx_event.userApi.status(status = { status: false, message: 'api id is null' }) if (!apiId) return global.lx_event.userApi.status(status = { status: false, message: 'api id is null' })
@@ -82,3 +91,8 @@ exports.request = ({ requestKey, data }) => new Promise((resolve, reject) => {
}) })
exports.getStatus = () => status exports.getStatus = () => status
exports.setAllowShowUpdateAlert = (id, enable) => {
if (!userApi || userApi.id != id) return
userApi.allowShowUpdateAlert = enable
}
+11
View File
@@ -11,6 +11,9 @@ exports.getUserApis = () => {
userApis = defaultUserApis userApis = defaultUserApis
electronStore_userApi.set('userApis', userApis) electronStore_userApi.set('userApis', userApis)
} }
for (const api of userApis) {
if (api.allowShowUpdateAlert == null) api.allowShowUpdateAlert = false
}
return userApis return userApis
} }
@@ -27,6 +30,7 @@ exports.importApi = script => {
name, name,
description, description,
script, script,
allowShowUpdateAlert: true,
} }
userApis.push(apiInfo) userApis.push(apiInfo)
getStore('userApi').set('userApis', userApis) getStore('userApi').set('userApis', userApis)
@@ -42,3 +46,10 @@ exports.removeApi = ids => {
} }
getStore('userApi').set('userApis', userApis) getStore('userApi').set('userApis', userApis)
} }
exports.setAllowShowUpdateAlert = (id, enable) => {
const targetApi = userApis.find(api => api.id == id)
if (!targetApi) return
targetApi.allowShowUpdateAlert = enable
getStore('userApi').set('userApis', userApis)
}
+3 -1
View File
@@ -1,4 +1,4 @@
const { isWin } = require('@common/utils')
// require('./request') // require('./request')
// require('./appName') // require('./appName')
require('./progressBar') require('./progressBar')
@@ -23,6 +23,8 @@ require('./systemFonts')
require('./wait') require('./wait')
require('./openDevtools') require('./openDevtools')
if (isWin) require('./taskbar')
// require('./kw_decodeLyric') // require('./kw_decodeLyric')
require('./userApi') require('./userApi')
+2 -3
View File
@@ -1,13 +1,12 @@
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
const { openDevTools } = require('@main/utils')
mainOn(ipcMainWindowNames.open_dev_tools, event => { mainOn(ipcMainWindowNames.open_dev_tools, event => {
if (global.modules.mainWindow) { if (global.modules.mainWindow) {
if (global.modules.mainWindow.isDevToolsOpened()) { if (global.modules.mainWindow.isDevToolsOpened()) {
global.modules.mainWindow.webContents.closeDevTools() global.modules.mainWindow.webContents.closeDevTools()
} else { } else {
global.modules.mainWindow.webContents.openDevTools({ openDevTools(global.modules.mainWindow.webContents)
mode: 'undocked',
})
} }
} }
}) })
+18
View File
@@ -0,0 +1,18 @@
const { mainSend, mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('@common/ipc')
const { setThumbnailClip, setThumbarButtons, eventNames } = require('../modules/taskbar')
const { mainWindow: MAIN_WINDOW_EVENT_NAME } = require('@main/events/_name')
const handleThumbarButtonClick = action => {
mainSend(global.modules.mainWindow, ipcMainWindowNames.taskbar_on_thumbar_button_click, action)
}
global.lx_event.taskbar.on(eventNames.thumbarButtonClick, handleThumbarButtonClick)
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.show, setThumbarButtons)
mainHandle(ipcMainWindowNames.taskbar_set_thumbnail_clip, (event, clip) => {
return setThumbnailClip(clip)
})
mainOn(ipcMainWindowNames.taskbar_set_thumbar_buttons, (event, buttons) => {
setThumbarButtons(buttons)
})
+9 -1
View File
@@ -1,11 +1,15 @@
const { mainSend, mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('@common/ipc') const { mainSend, mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('@common/ipc')
const { getApiList, importApi, removeApi, setApi, getStatus, request, cancelRequest, eventNames } = require('../modules/userApi') const { getApiList, importApi, removeApi, setApi, getStatus, request, cancelRequest, eventNames, setAllowShowUpdateAlert } = require('../modules/userApi')
const handleStatusChange = status => { const handleStatusChange = status => {
mainSend(global.modules.mainWindow, ipcMainWindowNames.user_api_status, status) mainSend(global.modules.mainWindow, ipcMainWindowNames.user_api_status, status)
} }
const handleShowUpdateAlert = info => {
mainSend(global.modules.mainWindow, ipcMainWindowNames.user_api_show_update_alert, info)
}
global.lx_event.userApi.on(eventNames.status, handleStatusChange) global.lx_event.userApi.on(eventNames.status, handleStatusChange)
global.lx_event.userApi.on(eventNames.showUpdateAlert, handleShowUpdateAlert)
mainHandle(ipcMainWindowNames.import_user_api, async(event, script) => { mainHandle(ipcMainWindowNames.import_user_api, async(event, script) => {
return importApi(script) return importApi(script)
@@ -27,6 +31,10 @@ mainHandle(ipcMainWindowNames.get_user_api_status, event => {
return getStatus() return getStatus()
}) })
mainHandle(ipcMainWindowNames.user_api_set_allow_update_alert, (event, { id, enable }) => {
return setAllowShowUpdateAlert(id, enable)
})
mainHandle(ipcMainWindowNames.request_user_api, (event, musicInfo) => { mainHandle(ipcMainWindowNames.request_user_api, (event, musicInfo) => {
return request(musicInfo) return request(musicInfo)
}) })
+22 -1
View File
@@ -1,4 +1,4 @@
const { mainOn, mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('@common/ipc') const { mainOn, mainHandle, mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('@common/ipc')
const timeoutMap = new Map() const timeoutMap = new Map()
@@ -23,3 +23,24 @@ mainOn(ipcMainWindowNames.wait_cancel, (event, id) => {
clearTimeout(timeout.timeout) clearTimeout(timeout.timeout)
timeout.reject('cancelled') timeout.reject('cancelled')
}) })
mainOn(ipcMainWindowNames.interval, (event, { time, id }) => {
if (timeoutMap.has(id)) return
const timeout = setInterval(() => {
if (global.modules.mainWindow) mainSend(global.modules.mainWindow, ipcMainWindowNames.interval_callback, id)
}, time)
timeoutMap.set(id, {
timeout,
type: 'interval',
time,
})
})
mainOn(ipcMainWindowNames.interval_cancel, (event, id) => {
if (!timeoutMap.has(id)) return
const timeout = timeoutMap.get(id)
timeoutMap.delete(id)
if (timeout.type != 'interval') return
clearInterval(timeout.timeout)
})
+2
View File
@@ -1,4 +1,5 @@
const { isWin } = require('../../common/utils') const { isWin } = require('../../common/utils')
const { openDevTools } = require('@main/utils')
const { mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') const { mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
global.isQuitting = false global.isQuitting = false
global.isTrafficLightClose = false // 是否点击软件上的关闭按钮关闭 global.isTrafficLightClose = false // 是否点击软件上的关闭按钮关闭
@@ -30,6 +31,7 @@ module.exports = mainWindow => {
mainWindow.once('ready-to-show', () => { mainWindow.once('ready-to-show', () => {
mainWindow.show() mainWindow.show()
global.lx_event.mainWindow.readyToShow() global.lx_event.mainWindow.readyToShow()
if (global.envParams.cmdParams.debug) openDevTools(global.modules.mainWindow.webContents)
}) })
mainWindow.on('show', () => { mainWindow.on('show', () => {
+6
View File
@@ -41,3 +41,9 @@ exports.initSetting = (isShowErrorAlert = true) => {
global.appSetting = info.setting global.appSetting = info.setting
global.appSettingVersion = info.version global.appSettingVersion = info.version
} }
exports.openDevTools = webContents => {
webContents.openDevTools({
mode: 'undocked',
})
}
@@ -23,6 +23,9 @@ article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section { footer, header, hgroup, menu, nav, section {
display: block; display: block;
} }
html {
font-family: "Microsoft YaHei", "Microsoft Jhenghei", "PingFang SC", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", Helvetica, Arial, "Hiragino Sans GB", "Source Han Sans", "Noto Sans CJK Sc", sans-serif;
}
body { body {
line-height: 1; line-height: 1;
} }
@@ -41,3 +44,6 @@ table {
border-collapse: collapse; border-collapse: collapse;
border-spacing: 0; border-spacing: 0;
} }
button {
font-family: inherit;
}
+8 -1
View File
@@ -20,13 +20,20 @@ import useApp from '@renderer/core/useApp'
export default { export default {
setup() { setup() {
const theme = useRefGetter('theme') const theme = useRefGetter('theme')
const font = useRefGetter('font')
const dom_root = document.getElementById('root') const dom_root = document.getElementById('root')
watch(theme, (val) => { watch(theme, (val) => {
dom_root.className = val dom_root.className = val
}, {
immediate: true,
})
watch(font, (val) => {
document.documentElement.style.fontFamily = val
}, {
immediate: true,
}) })
dom_root.className = theme.value
useApp() useApp()
+6
View File
@@ -23,6 +23,9 @@ article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section { footer, header, hgroup, menu, nav, section {
display: block; display: block;
} }
html {
font-family: "Microsoft YaHei", "Microsoft Jhenghei", "PingFang SC", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", Helvetica, Arial, "Hiragino Sans GB", "Source Han Sans", "Noto Sans CJK Sc", sans-serif;
}
body { body {
line-height: 1; line-height: 1;
} }
@@ -41,3 +44,6 @@ table {
border-collapse: collapse; border-collapse: collapse;
border-spacing: 0; border-spacing: 0;
} }
button {
font-family: inherit;
}
+16 -16
View File
@@ -49,7 +49,7 @@
@color-player-pic-c2: darken(@color-theme_2, 30%); @color-player-pic-c2: darken(@color-theme_2, 30%);
@color-player-progress: darken(@color-theme_2, 6%); @color-player-progress: darken(@color-theme_2, 6%);
@color-player-progress-bar1: darken(@color-theme_2, 12%); @color-player-progress-bar1: darken(@color-theme_2, 12%);
@color-player-progress-bar2: lighten(@color-theme, 12%); @color-player-progress-bar2: fadeout(lighten(@color-theme, 12%), 20%);
@color-player-status-text: lighten(@color-theme_2-font, 32%); @color-player-status-text: lighten(@color-theme_2-font, 32%);
@color-player-detail-lyric: fadeout(@color-theme_2-font, 40%); @color-player-detail-lyric: fadeout(@color-theme_2-font, 40%);
@color-player-detail-lyric-active: darken(@color-theme, 2%); @color-player-detail-lyric-active: darken(@color-theme, 2%);
@@ -114,7 +114,7 @@
@color-green-player-pic-c2: darken(@color-green-theme_2, 30%); @color-green-player-pic-c2: darken(@color-green-theme_2, 30%);
@color-green-player-progress: darken(@color-green-theme_2, 6%); @color-green-player-progress: darken(@color-green-theme_2, 6%);
@color-green-player-progress-bar1: darken(@color-green-theme_2, 12%); @color-green-player-progress-bar1: darken(@color-green-theme_2, 12%);
@color-green-player-progress-bar2: lighten(@color-green-theme, 12%); @color-green-player-progress-bar2: fadeout(lighten(@color-green-theme, 12%), 20%);
@color-green-player-status-text: lighten(@color-green-theme_2-font, 32%); @color-green-player-status-text: lighten(@color-green-theme_2-font, 32%);
@color-green-player-detail-lyric: fadeout(@color-green-theme_2-font, 40%); @color-green-player-detail-lyric: fadeout(@color-green-theme_2-font, 40%);
@color-green-player-detail-lyric-active: darken(@color-green-theme, 2%); @color-green-player-detail-lyric-active: darken(@color-green-theme, 2%);
@@ -174,7 +174,7 @@
@color-yellow-player-pic-c2: darken(@color-yellow-theme_2, 30%); @color-yellow-player-pic-c2: darken(@color-yellow-theme_2, 30%);
@color-yellow-player-progress: darken(@color-yellow-theme_2, 6%); @color-yellow-player-progress: darken(@color-yellow-theme_2, 6%);
@color-yellow-player-progress-bar1: darken(@color-yellow-theme_2, 12%); @color-yellow-player-progress-bar1: darken(@color-yellow-theme_2, 12%);
@color-yellow-player-progress-bar2: lighten(@color-yellow-theme, 2%); @color-yellow-player-progress-bar2: fadeout(lighten(@color-yellow-theme, 2%), 20%);
@color-yellow-player-status-text: lighten(@color-yellow-theme_2-font, 32%); @color-yellow-player-status-text: lighten(@color-yellow-theme_2-font, 32%);
@color-yellow-player-detail-lyric: fadeout(@color-yellow-theme_2-font, 40%); @color-yellow-player-detail-lyric: fadeout(@color-yellow-theme_2-font, 40%);
@color-yellow-player-detail-lyric-active: darken(@color-yellow-theme, 7%); @color-yellow-player-detail-lyric-active: darken(@color-yellow-theme, 7%);
@@ -233,7 +233,7 @@
@color-orange-player-pic-c2: darken(@color-orange-theme_2, 30%); @color-orange-player-pic-c2: darken(@color-orange-theme_2, 30%);
@color-orange-player-progress: darken(@color-orange-theme_2, 6%); @color-orange-player-progress: darken(@color-orange-theme_2, 6%);
@color-orange-player-progress-bar1: darken(@color-orange-theme_2, 12%); @color-orange-player-progress-bar1: darken(@color-orange-theme_2, 12%);
@color-orange-player-progress-bar2: lighten(@color-orange-theme, 12%); @color-orange-player-progress-bar2: fadeout(lighten(@color-orange-theme, 12%), 20%);
@color-orange-player-status-text: lighten(@color-orange-theme_2-font, 32%); @color-orange-player-status-text: lighten(@color-orange-theme_2-font, 32%);
@color-orange-player-detail-lyric: fadeout(@color-orange-theme_2-font, 40%); @color-orange-player-detail-lyric: fadeout(@color-orange-theme_2-font, 40%);
@color-orange-player-detail-lyric-active: darken(@color-orange-theme, 7%); @color-orange-player-detail-lyric-active: darken(@color-orange-theme, 7%);
@@ -292,7 +292,7 @@
@color-blue-player-pic-c2: darken(@color-blue-theme_2, 30%); @color-blue-player-pic-c2: darken(@color-blue-theme_2, 30%);
@color-blue-player-progress: darken(@color-blue-theme_2, 6%); @color-blue-player-progress: darken(@color-blue-theme_2, 6%);
@color-blue-player-progress-bar1: darken(@color-blue-theme_2, 12%); @color-blue-player-progress-bar1: darken(@color-blue-theme_2, 12%);
@color-blue-player-progress-bar2: lighten(@color-blue-theme, 12%); @color-blue-player-progress-bar2: fadeout(lighten(@color-blue-theme, 12%), 20%);
@color-blue-player-status-text: lighten(@color-blue-theme_2-font, 32%); @color-blue-player-status-text: lighten(@color-blue-theme_2-font, 32%);
@color-blue-player-detail-lyric: fadeout(@color-blue-theme_2-font, 40%); @color-blue-player-detail-lyric: fadeout(@color-blue-theme_2-font, 40%);
@color-blue-player-detail-lyric-active: darken(@color-blue-theme, 2%); @color-blue-player-detail-lyric-active: darken(@color-blue-theme, 2%);
@@ -351,7 +351,7 @@
@color-red-player-pic-c2: darken(@color-red-theme_2, 30%); @color-red-player-pic-c2: darken(@color-red-theme_2, 30%);
@color-red-player-progress: darken(@color-red-theme_2, 6%); @color-red-player-progress: darken(@color-red-theme_2, 6%);
@color-red-player-progress-bar1: darken(@color-red-theme_2, 12%); @color-red-player-progress-bar1: darken(@color-red-theme_2, 12%);
@color-red-player-progress-bar2: lighten(@color-red-theme, 12%); @color-red-player-progress-bar2: fadeout(lighten(@color-red-theme, 12%), 20%);
@color-red-player-status-text: lighten(@color-red-theme_2-font, 32%); @color-red-player-status-text: lighten(@color-red-theme_2-font, 32%);
@color-red-player-detail-lyric: fadeout(@color-red-theme_2-font, 40%); @color-red-player-detail-lyric: fadeout(@color-red-theme_2-font, 40%);
@color-red-player-detail-lyric-active: lighten(@color-red-theme, 2%); @color-red-player-detail-lyric-active: lighten(@color-red-theme, 2%);
@@ -412,7 +412,7 @@
@color-pink-player-pic-c2: darken(@color-pink-theme_2, 30%); @color-pink-player-pic-c2: darken(@color-pink-theme_2, 30%);
@color-pink-player-progress: darken(@color-pink-theme_2, 6%); @color-pink-player-progress: darken(@color-pink-theme_2, 6%);
@color-pink-player-progress-bar1: darken(@color-pink-theme_2, 12%); @color-pink-player-progress-bar1: darken(@color-pink-theme_2, 12%);
@color-pink-player-progress-bar2: lighten(@color-pink-theme, 2%); @color-pink-player-progress-bar2: fadeout(lighten(@color-pink-theme, 2%), 20%);
@color-pink-player-status-text: lighten(@color-pink-theme_2-font, 32%); @color-pink-player-status-text: lighten(@color-pink-theme_2-font, 32%);
@color-pink-player-detail-lyric: fadeout(@color-pink-theme_2-font, 40%); @color-pink-player-detail-lyric: fadeout(@color-pink-theme_2-font, 40%);
@color-pink-player-detail-lyric-active: darken(@color-pink-theme, 2%); @color-pink-player-detail-lyric-active: darken(@color-pink-theme, 2%);
@@ -471,7 +471,7 @@
@color-purple-player-pic-c2: darken(@color-purple-theme_2, 30%); @color-purple-player-pic-c2: darken(@color-purple-theme_2, 30%);
@color-purple-player-progress: darken(@color-purple-theme_2, 6%); @color-purple-player-progress: darken(@color-purple-theme_2, 6%);
@color-purple-player-progress-bar1: darken(@color-purple-theme_2, 12%); @color-purple-player-progress-bar1: darken(@color-purple-theme_2, 12%);
@color-purple-player-progress-bar2: lighten(@color-purple-theme, 12%); @color-purple-player-progress-bar2: fadeout(lighten(@color-purple-theme, 12%), 20%);
@color-purple-player-status-text: lighten(@color-purple-theme_2-font, 32%); @color-purple-player-status-text: lighten(@color-purple-theme_2-font, 32%);
@color-purple-player-detail-lyric: fadeout(@color-purple-theme_2-font, 40%); @color-purple-player-detail-lyric: fadeout(@color-purple-theme_2-font, 40%);
@color-purple-player-detail-lyric-active: darken(@color-purple-theme, 2%); @color-purple-player-detail-lyric-active: darken(@color-purple-theme, 2%);
@@ -530,7 +530,7 @@
@color-grey-player-pic-c2: darken(@color-grey-theme_2, 30%); @color-grey-player-pic-c2: darken(@color-grey-theme_2, 30%);
@color-grey-player-progress: darken(@color-grey-theme_2, 6%); @color-grey-player-progress: darken(@color-grey-theme_2, 6%);
@color-grey-player-progress-bar1: darken(@color-grey-theme_2, 12%); @color-grey-player-progress-bar1: darken(@color-grey-theme_2, 12%);
@color-grey-player-progress-bar2: lighten(@color-grey-theme, 12%); @color-grey-player-progress-bar2: fadeout(lighten(@color-grey-theme, 12%), 20%);
@color-grey-player-status-text: lighten(@color-grey-theme_2-font, 32%); @color-grey-player-status-text: lighten(@color-grey-theme_2-font, 32%);
@color-grey-player-detail-lyric: fadeout(@color-grey-theme_2-font, 55%); @color-grey-player-detail-lyric: fadeout(@color-grey-theme_2-font, 55%);
@color-grey-player-detail-lyric-active: darken(@color-grey-theme, 3%); @color-grey-player-detail-lyric-active: darken(@color-grey-theme, 3%);
@@ -590,7 +590,7 @@
@color-ming-player-pic-c2: darken(@color-ming-theme_2, 30%); @color-ming-player-pic-c2: darken(@color-ming-theme_2, 30%);
@color-ming-player-progress: darken(@color-ming-theme_2, 6%); @color-ming-player-progress: darken(@color-ming-theme_2, 6%);
@color-ming-player-progress-bar1: darken(@color-ming-theme_2, 12%); @color-ming-player-progress-bar1: darken(@color-ming-theme_2, 12%);
@color-ming-player-progress-bar2: lighten(@color-ming-theme, 12%); @color-ming-player-progress-bar2: fadeout(lighten(@color-ming-theme, 12%), 20%);
@color-ming-player-status-text: lighten(@color-ming-theme_2-font, 32%); @color-ming-player-status-text: lighten(@color-ming-theme_2-font, 32%);
@color-ming-player-detail-lyric: fadeout(@color-ming-theme_2-font, 50%); @color-ming-player-detail-lyric: fadeout(@color-ming-theme_2-font, 50%);
@color-ming-player-detail-lyric-active: lighten(@color-ming-theme, 2%); @color-ming-player-detail-lyric-active: lighten(@color-ming-theme, 2%);
@@ -652,7 +652,7 @@
@color-blue2-player-pic-c2: darken(@color-blue2-theme_2, 30%); @color-blue2-player-pic-c2: darken(@color-blue2-theme_2, 30%);
@color-blue2-player-progress: darken(@color-blue2-theme_2, 6%); @color-blue2-player-progress: darken(@color-blue2-theme_2, 6%);
@color-blue2-player-progress-bar1: darken(@color-blue2-theme_2, 12%); @color-blue2-player-progress-bar1: darken(@color-blue2-theme_2, 12%);
@color-blue2-player-progress-bar2: lighten(@color-blue2-theme, 12%); @color-blue2-player-progress-bar2: fadeout(lighten(@color-blue2-theme, 12%), 20%);
@color-blue2-player-status-text: lighten(@color-blue2-theme_2-font, 32%); @color-blue2-player-status-text: lighten(@color-blue2-theme_2-font, 32%);
@color-blue2-player-detail-lyric: fadeout(@color-blue2-theme_2-font, 50%); @color-blue2-player-detail-lyric: fadeout(@color-blue2-theme_2-font, 50%);
@color-blue2-player-detail-lyric-active: darken(@color-blue2-theme, 2%); @color-blue2-player-detail-lyric-active: darken(@color-blue2-theme, 2%);
@@ -712,7 +712,7 @@
@color-black-player-pic-c2: lighten(@color-black-theme_2, 30%); @color-black-player-pic-c2: lighten(@color-black-theme_2, 30%);
@color-black-player-progress: lighten(@color-black-theme_2, 6%); @color-black-player-progress: lighten(@color-black-theme_2, 6%);
@color-black-player-progress-bar1: lighten(@color-black-theme_2, 12%); @color-black-player-progress-bar1: lighten(@color-black-theme_2, 12%);
@color-black-player-progress-bar2: lighten(@color-black-theme, 12%); @color-black-player-progress-bar2: fadeout(lighten(@color-black-theme, 12%), 20%);
@color-black-player-status-text: darken(@color-black-theme_2-font, 20%); @color-black-player-status-text: darken(@color-black-theme_2-font, 20%);
@color-black-player-detail-lyric: fadeout(darken(@color-black-theme_2-font, 30%), 10%); @color-black-player-detail-lyric: fadeout(darken(@color-black-theme_2-font, 30%), 10%);
@color-black-player-detail-lyric-active: lighten(@color-black-theme, 50%); @color-black-player-detail-lyric-active: lighten(@color-black-theme, 50%);
@@ -772,7 +772,7 @@
@color-mid_autumn-player-pic-c2: darken(@color-mid_autumn-theme_2, 30%); @color-mid_autumn-player-pic-c2: darken(@color-mid_autumn-theme_2, 30%);
@color-mid_autumn-player-progress: darken(@color-mid_autumn-theme_2, 10%); @color-mid_autumn-player-progress: darken(@color-mid_autumn-theme_2, 10%);
@color-mid_autumn-player-progress-bar1: darken(@color-mid_autumn-theme_2, 12%); @color-mid_autumn-player-progress-bar1: darken(@color-mid_autumn-theme_2, 12%);
@color-mid_autumn-player-progress-bar2: lighten(@color-mid_autumn-theme, 12%); @color-mid_autumn-player-progress-bar2: fadeout(lighten(@color-mid_autumn-theme, 12%), 20%);
@color-mid_autumn-player-status-text: lighten(@color-mid_autumn-theme_2-font, 32%); @color-mid_autumn-player-status-text: lighten(@color-mid_autumn-theme_2-font, 32%);
@color-mid_autumn-player-detail-lyric: fadeout(@color-mid_autumn-theme_2-font, 40%); @color-mid_autumn-player-detail-lyric: fadeout(@color-mid_autumn-theme_2-font, 40%);
@color-mid_autumn-player-detail-lyric-active: lighten(@color-mid_autumn-theme, 7%); @color-mid_autumn-player-detail-lyric-active: lighten(@color-mid_autumn-theme, 7%);
@@ -832,7 +832,7 @@
@color-mid_autumn-player-pic-c2: darken(@color-mid_autumn-theme_2, 30%); @color-mid_autumn-player-pic-c2: darken(@color-mid_autumn-theme_2, 30%);
@color-mid_autumn-player-progress: darken(@color-mid_autumn-theme_2, 10%); @color-mid_autumn-player-progress: darken(@color-mid_autumn-theme_2, 10%);
@color-mid_autumn-player-progress-bar1: darken(@color-mid_autumn-theme_2, 12%); @color-mid_autumn-player-progress-bar1: darken(@color-mid_autumn-theme_2, 12%);
@color-mid_autumn-player-progress-bar2: lighten(@color-mid_autumn-theme, 12%); @color-mid_autumn-player-progress-bar2: fadeout(lighten(@color-mid_autumn-theme, 12%), 20%);
@color-mid_autumn-player-status-text: lighten(@color-mid_autumn-theme_2-font, 32%); @color-mid_autumn-player-status-text: lighten(@color-mid_autumn-theme_2-font, 32%);
@color-mid_autumn-player-detail-lyric: lighten(@color-mid_autumn-theme, 7%); @color-mid_autumn-player-detail-lyric: lighten(@color-mid_autumn-theme, 7%);
@color-mid_autumn-player-detail-lyric-active: lighten(@color-mid_autumn-theme, 7%); @color-mid_autumn-player-detail-lyric-active: lighten(@color-mid_autumn-theme, 7%);
@@ -891,7 +891,7 @@
@color-naruto-player-pic-c2: darken(@color-naruto-theme_2, 30%); @color-naruto-player-pic-c2: darken(@color-naruto-theme_2, 30%);
@color-naruto-player-progress: darken(@color-naruto-theme_2, 10%); @color-naruto-player-progress: darken(@color-naruto-theme_2, 10%);
@color-naruto-player-progress-bar1: darken(@color-naruto-theme_2, 12%); @color-naruto-player-progress-bar1: darken(@color-naruto-theme_2, 12%);
@color-naruto-player-progress-bar2: lighten(@color-naruto-theme, 12%); @color-naruto-player-progress-bar2: fadeout(lighten(@color-naruto-theme, 12%), 20%);
@color-naruto-player-status-text: lighten(@color-naruto-theme_2-font, 32%); @color-naruto-player-status-text: lighten(@color-naruto-theme_2-font, 32%);
@color-naruto-player-detail-lyric: fadeout(@color-naruto-theme_2-font, 36%); @color-naruto-player-detail-lyric: fadeout(@color-naruto-theme_2-font, 36%);
@color-naruto-player-detail-lyric-active: darken(@color-naruto-theme, 7%); @color-naruto-player-detail-lyric-active: darken(@color-naruto-theme, 7%);
@@ -950,7 +950,7 @@
@color-happy_new_year-player-pic-c2: darken(@color-happy_new_year-theme_2, 30%); @color-happy_new_year-player-pic-c2: darken(@color-happy_new_year-theme_2, 30%);
@color-happy_new_year-player-progress: darken(@color-happy_new_year-theme_2, 10%); @color-happy_new_year-player-progress: darken(@color-happy_new_year-theme_2, 10%);
@color-happy_new_year-player-progress-bar1: darken(@color-happy_new_year-theme_2, 5%); @color-happy_new_year-player-progress-bar1: darken(@color-happy_new_year-theme_2, 5%);
@color-happy_new_year-player-progress-bar2: lighten(@color-happy_new_year-theme, 5%); @color-happy_new_year-player-progress-bar2: fadeout(lighten(@color-happy_new_year-theme, 5%), 20%);
@color-happy_new_year-player-status-text: lighten(@color-happy_new_year-theme_2-font, 32%); @color-happy_new_year-player-status-text: lighten(@color-happy_new_year-theme_2-font, 32%);
@color-happy_new_year-player-detail-lyric: fadeout(@color-happy_new_year-theme_2-font, 36%); @color-happy_new_year-player-detail-lyric: fadeout(@color-happy_new_year-theme_2-font, 36%);
@color-happy_new_year-player-detail-lyric-active: darken(@color-happy_new_year-theme, 7%); @color-happy_new_year-player-detail-lyric-active: darken(@color-happy_new_year-theme, 7%);
@@ -41,6 +41,7 @@ export default {
default: false, default: false,
}, },
}, },
emits: ['update:modelValue', 'change'],
data() { data() {
return { return {
bool: false, bool: false,
+1 -1
View File
@@ -24,7 +24,7 @@ export default {
default: false, default: false,
}, },
modelValue: { modelValue: {
type: String, type: [String, Number],
default: '', default: '',
}, },
type: { type: {
@@ -125,14 +125,13 @@ export default {
.progress-bar2 { .progress-bar2 {
background-color: @color-player-progress-bar2; background-color: @color-player-progress-bar2;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3); will-change: transform;
opacity: 0.8;
} }
.progress-bar3 { .progress-bar3 {
background-color: @color-player-progress-bar2; background-color: @color-player-progress-bar2;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3); box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
opacity: 0.3; opacity: 0.5;
} }
.bar-transition { .bar-transition {
+18 -6
View File
@@ -10,12 +10,13 @@ div(:class="$style.player")
div(:class="$style.title" @click="handleCopy(title)" :tips="title + $t('copy_tip')") {{title}} div(:class="$style.title" @click="handleCopy(title)" :tips="title + $t('copy_tip')") {{title}}
control-btns control-btns
div(:class="$style.column2") div(:class="$style.column2")
common-progress-bar(:progress="progress" :handleTransitionEnd="handleTransitionEnd" :isActiveTransition="isActiveTransition") common-progress-bar(:progress="progress" :handleTransitionEnd="handleTransitionEnd" :isActiveTransition="isActiveTransition" v-if="!isShowPlayerDetail")
div(:class="$style.column3") div(:class="$style.column3")
span(:class="$style.statusText") {{statusText}} template(v-if="!isShowPlayerDetail")
span {{nowPlayTimeStr}} span(:class="$style.statusText") {{statusText}}
span(style="margin: 0 5px;") / span {{nowPlayTimeStr}}
span {{maxPlayTimeStr}} span(style="margin: 0 5px;") /
span {{maxPlayTimeStr}}
div(:class="$style.right") div(:class="$style.right")
div(:class="$style.playBtn" @click='playPrev' :tips="$t('player__prev')" style="transform: rotate(180deg);") div(:class="$style.playBtn" @click='playPrev' :tips="$t('player__prev')" style="transform: rotate(180deg);")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve') svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
@@ -48,7 +49,17 @@ import { player as eventPlayerNames } from '@renderer/event/names'
import ControlBtns from './ControlBtns' import ControlBtns from './ControlBtns'
import usePlayProgress from '@renderer/utils/compositions/usePlayProgress' import usePlayProgress from '@renderer/utils/compositions/usePlayProgress'
// import { lyric } from '@renderer/core/share/lyric' // import { lyric } from '@renderer/core/share/lyric'
import { statusText, musicInfo, setMusicInfo, setShowPlayerDetail, isPlay, musicInfoItem, playInfo, playMusicInfo } from '@renderer/core/share/player' import {
statusText,
musicInfo,
setMusicInfo,
isShowPlayerDetail,
setShowPlayerDetail,
isPlay,
musicInfoItem,
playInfo,
playMusicInfo,
} from '@renderer/core/share/player'
export default { export default {
name: 'CorePlayBar', name: 'CorePlayBar',
@@ -138,6 +149,7 @@ export default {
playNext, playNext,
playPrev, playPrev,
handleToMusicLocation, handleToMusicLocation,
isShowPlayerDetail,
} }
}, },
} }
@@ -2,11 +2,11 @@
<div :class="['right', $style.right]"> <div :class="['right', $style.right]">
<div :class="['lyric', $style.lyric, { [$style.draging]: isMsDown }]" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric"> <div :class="['lyric', $style.lyric, { [$style.draging]: isMsDown }]" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric">
<div :class="$style.lyricSpace"></div> <div :class="$style.lyricSpace"></div>
<div :class="[$style.lyricText]" ref="dom_lyric_text"></div> <div ref="dom_lyric_text"></div>
<div :class="$style.lyricSpace"></div> <div :class="$style.lyricSpace"></div>
</div> </div>
<transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut"> <transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut">
<div :class="[$style.lyricSelectContent, 'select', 'scroll']" v-if="isShowLrcSelectContent" @contextmenu="handleCopySelectText"> <div :class="[$style.lyricSelectContent, 'select', 'scroll', 'lyricSelectContent']" v-if="isShowLrcSelectContent" @contextmenu="handleCopySelectText">
<div v-for="(info, index) in lyric.lines" :key="index" :class="[$style.lyricSelectline, { [$style.lrcActive]: lyric.line == index }]"> <div v-for="(info, index) in lyric.lines" :key="index" :class="[$style.lyricSelectline, { [$style.lrcActive]: lyric.line == index }]">
<span>{{info.text}}</span> <span>{{info.text}}</span>
<br v-if="info.translation"/> <br v-if="info.translation"/>
@@ -6,11 +6,10 @@ div(:class="$style.footer")
div(:class="$style.progressContent") div(:class="$style.progressContent")
common-progress-bar(:class-name="$style.progress" :progress="progress" :handleTransitionEnd="handleTransitionEnd" :isActiveTransition="isActiveTransition") common-progress-bar(:class-name="$style.progress" :progress="progress" :handleTransitionEnd="handleTransitionEnd" :isActiveTransition="isActiveTransition")
div(:class="$style.timeLabel") div(:class="$style.timeLabel")
span(style="margin-left: 15px") {{status}} span(:class="$style.status" style="margin-left: 15px") {{status}}
div span {{nowPlayTimeStr}}
span {{nowPlayTimeStr}} span(style="margin: 0 5px;") /
span(style="margin: 0 5px;") / span {{maxPlayTimeStr}}
span {{maxPlayTimeStr}}
div(:class="$style.playControl") div(:class="$style.playControl")
div(:class="$style.playBtn" @click="playPrev" style="transform: rotate(180deg);" :tips="$t('player__prev')") div(:class="$style.playBtn" @click="playPrev" style="transform: rotate(180deg);" :tips="$t('player__prev')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve') svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
@@ -91,7 +90,7 @@ export default {
.progress-content { .progress-content {
position: relative; position: relative;
height: 15px; height: 16px;
padding: 5px 0; padding: 5px 0;
width: 100%; width: 100%;
} }
@@ -108,11 +107,13 @@ export default {
width: 100%; width: 100%;
height: 18px; height: 18px;
display: flex; display: flex;
justify-content: space-between;
span { span {
font-size: 13px; font-size: 13px;
} }
} }
.status {
flex: auto;
}
.play-control { .play-control {
flex: none; flex: none;
@@ -1,5 +1,5 @@
<template lang="pug"> <template lang="pug">
div.comment(:class="$style.comment") div.comment(:class="$style.comment" ref="dom_container")
div(:class="$style.commentHeader") div(:class="$style.commentHeader")
h3 {{$t('comment__title', { name: title })}} h3 {{$t('comment__title', { name: title })}}
div(:class="$style.commentHeaderBtns") div(:class="$style.commentHeaderBtns")
@@ -10,21 +10,27 @@ div.comment(:class="$style.comment")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 24 24' space='preserve') svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-close') use(xlink:href='#icon-close')
div.scroll(:class="$style.commentMain" ref="dom_comment") div(:class="$style.commentMain")
div(v-if="page == 1") header(:class="$style.tab_header")
h2(:class="$style.commentType") {{$t('comment__hot_title')}} button(type="button" @click="handleToggleTab('hot')" :class="[$style.commentType, { [$style.active]: tabActiveId == 'hot' }]") {{$t('comment__hot_title')}} ({{hotComment.total}})
p(:class="$style.commentLabel" style="cursor: pointer;" v-if="isHotLoadError" @click="handleGetHotComment(currentMusicInfo)") {{$t('comment__hot_load_error')}} button(type="button" @click="handleToggleTab('new')" :class="[$style.commentType, { [$style.active]: tabActiveId == 'new' }]") {{$t('comment__new_title')}} ({{newComment.total}})
p(:class="$style.commentLabel" v-else-if="isHotLoading && !hotComments.length") {{$t('comment__hot_loading')}} main(:class="$style.tab_main" ref="dom_tabMain")
comment-floor(v-if="!isHotLoadError && hotComments.length" :class="[$style.commentFloor, isHotLoading ? $style.loading : null]" :comments="hotComments") div(:class="$style.tab_content")
p(:class="$style.commentLabel" v-else-if="!isHotLoadError && !isHotLoading") {{$t('comment__no_content')}} div.scroll(:class="$style.tab_content_scroll" ref="dom_commentHot")
div p(:class="$style.commentLabel" style="cursor: pointer;" v-if="hotComment.isLoadError" @click="handleGetHotComment(currentMusicInfo, hotComment.nextPage, hotComment.limit)") {{$t('comment__hot_load_error')}}
h2(:class="$style.commentType") {{$t('comment__new_title')}} ({{total}}) p(:class="$style.commentLabel" v-else-if="hotComment.isLoading && !hotComment.list.length") {{$t('comment__hot_loading')}}
p(:class="$style.commentLabel" style="cursor: pointer;" v-if="isNewLoadError" @click="handleGetNewComment(currentMusicInfo, nextPage, limit)") {{$t('comment__new_load_error')}} comment-floor(v-if="!hotComment.isLoadError && hotComment.list.length" :class="[$style.commentFloor, hotComment.isLoading ? $style.loading : null]" :comments="hotComment.list")
p(:class="$style.commentLabel" v-else-if="isNewLoading && !newComments.length") {{$t('comment__new_loading')}} p(:class="$style.commentLabel" v-else-if="!hotComment.isLoadError && !hotComment.isLoading") {{$t('comment__no_content')}}
comment-floor(v-if="!isNewLoadError && newComments.length" :class="[$style.commentFloor, isNewLoading ? $style.loading : null]" :comments="newComments") div(:class="$style.pagination")
p(:class="$style.commentLabel" v-else-if="!isNewLoadError && !isNewLoading") {{$t('comment__no_content')}} material-pagination(:count="hotComment.total" :btnLength="5" :limit="hotComment.limit" :page="hotComment.page" @btn-click="handleToggleHotCommentPage")
div(:class="$style.pagination") div(:class="$style.tab_content")
material-pagination(:count="total" :btnLength="5" :limit="limit" :page="page" @btn-click="handleToggleCommentPage") div.scroll(:class="$style.tab_content_scroll" ref="dom_commentNew")
p(:class="$style.commentLabel" style="cursor: pointer;" v-if="newComment.isLoadError" @click="handleGetNewComment(currentMusicInfo, newComment.nextPage, newComment.limit)") {{$t('comment__new_load_error')}}
p(:class="$style.commentLabel" v-else-if="newComment.isLoading && !newComment.list.length") {{$t('comment__new_loading')}}
comment-floor(v-if="!newComment.isLoadError && newComment.list.length" :class="[$style.commentFloor, newComment.isLoading ? $style.loading : null]" :comments="newComment.list")
p(:class="$style.commentLabel" v-else-if="!newComment.isLoadError && !newComment.isLoading") {{$t('comment__no_content')}}
div(:class="$style.pagination")
material-pagination(:count="newComment.total" :btnLength="5" :limit="newComment.limit" :page="newComment.page" @btn-click="handleToggleCommentPage")
</template> </template>
<script> <script>
@@ -52,16 +58,16 @@ export default {
name: '', name: '',
singer: '', singer: '',
}, },
page: 1, tabActiveId: 'hot',
total: 0, newComment: {
maxPage: 1, isLoading: false,
limit: 20, isLoadError: false,
isHotLoading: true, page: 1,
isNewLoading: false, total: 0,
isHotLoadError: true, maxPage: 1,
isNewLoadError: false, nextPage: 1,
nextPage: 1, limit: 20,
newComments: [ list: [
// { // {
// text: ['123123hhh'], // text: ['123123hhh'],
// userName: 'dsads', // userName: 'dsads',
@@ -71,8 +77,17 @@ export default {
// likedCount: 100, // likedCount: 100,
// reply: [], // reply: [],
// }, // },
], ],
hotComments: [ },
hotComment: {
isLoading: true,
isLoadError: true,
page: 1,
total: 0,
maxPage: 1,
nextPage: 1,
limit: 20,
list: [
// { // {
// text: ['123123hhh'], // text: ['123123hhh'],
// userName: 'dsads', // userName: 'dsads',
@@ -91,7 +106,8 @@ export default {
// }, // },
// ], // ],
// }, // },
], ],
},
} }
}, },
computed: { computed: {
@@ -102,12 +118,22 @@ export default {
: '^-^' : '^-^'
}, },
}, },
mounted() {
this.setWidth()
window.addEventListener('resize', this.setWidth)
},
beforeUnmount() {
window.removeEventListener('resize', this.setWidth)
},
watch: { watch: {
show(n) { show(n) {
if (n) this.handleShowComment() if (n) this.handleShowComment()
}, },
}, },
methods: { methods: {
setWidth() {
this.$refs.dom_container.style.width = this.$refs.dom_container.clientWidth + 'px'
},
async getComment(musicInfo, page, limit, retryNum = 0) { async getComment(musicInfo, page, limit, retryNum = 0) {
let resp let resp
try { try {
@@ -118,65 +144,92 @@ export default {
} }
return resp return resp
}, },
async getHotComment(musicInfo, retryNum = 0) { async getHotComment(musicInfo, page, limit, retryNum = 0) {
let resp let resp
try { try {
resp = await music[musicInfo.source].comment.getHotComment(musicInfo) resp = await music[musicInfo.source].comment.getHotComment(musicInfo, page, limit)
} catch (error) { } catch (error) {
if (error.message == '取消请求' || ++retryNum > 2) throw error if (error.message == '取消请求' || ++retryNum > 2) throw error
resp = await this.getHotComment(musicInfo, retryNum) resp = await this.getHotComment(musicInfo, page, limit, retryNum)
} }
return resp return resp
}, },
handleGetNewComment(musicInfo, page, limit) { handleGetNewComment(musicInfo, page, limit) {
this.isNewLoadError = false this.newComment.isLoadError = false
this.isNewLoading = true this.newComment.isLoading = true
this.getComment(musicInfo, page, limit).then(comment => { this.getComment(musicInfo, page, limit).then(comment => {
this.isNewLoading = false this.newComment.isLoading = false
this.total = comment.total this.newComment.total = comment.total
this.maxPage = comment.maxPage this.newComment.maxPage = comment.maxPage
this.page = page this.newComment.page = page
this.newComments = comment.comments this.newComment.list = comment.comments
this.$nextTick(() => { this.$nextTick(() => {
scrollTo(this.$refs.dom_comment, 0, 300) scrollTo(this.$refs.dom_commentNew, 0, 300)
}) })
}).catch(err => { }).catch(err => {
console.log(err) console.log(err)
if (err.message == '取消请求') return if (err.message == '取消请求') return
this.isNewLoadError = true this.newComment.isLoadError = true
this.isNewLoading = false this.newComment.isLoading = false
}) })
}, },
handleGetHotComment(musicInfo) { handleGetHotComment(musicInfo, page, limit) {
this.isHotLoadError = false this.hotComment.isLoadError = false
this.isHotLoading = true this.hotComment.isLoading = true
this.getHotComment(musicInfo).then(hotComment => { this.getHotComment(musicInfo, page, limit).then(hotComment => {
this.isHotLoading = false this.hotComment.isLoading = false
this.hotComments = hotComment.comments this.hotComment.total = hotComment.total
this.hotComment.maxPage = hotComment.maxPage
this.hotComment.page = page
this.hotComment.list = hotComment.comments
this.$nextTick(() => {
scrollTo(this.$refs.dom_commentHot, 0, 300)
})
}).catch(err => { }).catch(err => {
console.log(err) console.log(err)
if (err.message == '取消请求') return if (err.message == '取消请求') return
this.isHotLoadError = true this.hotComment.isLoadError = true
this.isHotLoading = false this.hotComment.isLoading = false
}) })
}, },
handleShowComment() { handleShowComment() {
if (!this.musicInfo.songmid || !music[this.musicInfo.source].comment) return if (!this.musicInfo.songmid || !music[this.musicInfo.source].comment) return
// if (this.musicInfo.songmid != this.currentMusicInfo.songmid) { // if (this.musicInfo.songmid != this.currentMusicInfo.songmid) {
this.page = 1 this.hotComment.page = 1
this.total = 0 this.hotComment.total = 0
this.maxPage = 1 this.hotComment.maxPage = 1
this.nextPage = 1 this.hotComment.nextPage = 1
this.newComment.page = 1
this.newComment.total = 0
this.newComment.maxPage = 1
this.newComment.nextPage = 1
// } // }
this.isShowComment = true this.isShowComment = true
this.currentMusicInfo = this.musicInfo this.currentMusicInfo = this.musicInfo
if (this.page == 1) this.handleGetHotComment(this.currentMusicInfo) this.handleGetHotComment(this.currentMusicInfo, this.hotComment.page, this.hotComment.limit)
this.handleGetNewComment(this.currentMusicInfo, this.page, this.limit) this.handleGetNewComment(this.currentMusicInfo, this.newComment.page, this.newComment.limit)
},
handleToggleHotCommentPage(page) {
this.hotComment.nextPage = page
this.handleGetHotComment(this.currentMusicInfo, page, this.hotComment.limit)
}, },
handleToggleCommentPage(page) { handleToggleCommentPage(page) {
this.nextPage = page this.newComment.nextPage = page
this.handleGetNewComment(this.currentMusicInfo, page, this.limit) this.handleGetNewComment(this.currentMusicInfo, page, this.newComment.limit)
},
handleToggleTab(id) {
if (this.tabActiveId == id) return
switch (id) {
case 'hot':
this.$refs.dom_tabMain.scrollLeft = 0
break
case 'new':
this.$refs.dom_tabMain.scrollLeft = this.$refs.dom_tabMain.clientWidth
break
}
this.tabActiveId = id
}, },
}, },
} }
@@ -229,10 +282,39 @@ export default {
} }
.commentMain { .commentMain {
flex: auto; flex: auto;
padding-left: 15px;
padding-right: 10px;
background-color: @color-reply-floor; background-color: @color-reply-floor;
border-radius: 4px; border-radius: 4px;
display: flex;
flex-direction: column;
}
.tab_header {
display: flex;
flex-flow: row nowrap;
gap: 15px;
padding-left: 15px;
padding-right: 10px;
}
.tab_main {
flex: auto;
display: flex;
flex-flow: row nowrap;
overflow: hidden;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
}
.tab_content {
flex-shrink: 0;
width: 100%;
position: relative;
}
.tab_content_scroll {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
padding-left: 15px;
padding-right: 10px;
} }
.commentLabel { .commentLabel {
padding: 15px; padding: 15px;
@@ -240,9 +322,20 @@ export default {
font-size: 14px; font-size: 14px;
} }
.commentType { .commentType {
padding: 10px 0; padding: 5px;
margin: 5px 0;
font-size: 13px; font-size: 13px;
color: @color-theme; background: none;
border: none;
cursor: pointer;
transition: @transition-theme;
transition-property: opacity, color;
&:hover {
opacity: .7;
}
&.active {
color: @color-theme;
}
} }
.commentFloor { .commentFloor {
opacity: 1; opacity: 1;
@@ -268,7 +361,9 @@ each(@themes, {
color: ~'@{color-@{value}-theme_2-font-label}'; color: ~'@{color-@{value}-theme_2-font-label}';
} }
.commentType { .commentType {
color: ~'@{color-@{value}-theme}'; &.active {
color: ~'@{color-@{value}-theme}';
}
} }
} }
}) })
+22 -202
View File
@@ -162,25 +162,25 @@ export default {
} }
} }
.bg { // .bg {
position: absolute; // position: absolute;
width: 100%; // width: 100%;
height: 100%; // height: 100%;
top: 0; // top: 0;
left: 0; // left: 0;
background-size: 110% 110%; // background-size: 110% 110%;
filter: blur(60px); // filter: blur(60px);
z-index: -1; // z-index: -1;
} // }
.bg2 { // .bg2 {
position: absolute; // position: absolute;
width: 100%; // width: 100%;
height: 100%; // height: 100%;
top: 0; // top: 0;
left: 0; // left: 0;
z-index: -1; // z-index: -1;
background-color: rgba(255, 255, 255, .8); // background-color: rgba(255, 255, 255, .8);
} // }
.header { .header {
position: relative; position: relative;
@@ -299,6 +299,9 @@ export default {
.lyric { .lyric {
font-size: 13px; font-size: 13px;
} }
.lyricSelectContent {
font-size: 14px;
}
} }
.comment { .comment {
opacity: 1; opacity: 1;
@@ -346,131 +349,6 @@ export default {
} }
} }
.right {
flex: 0 0 60%;
// padding: 0 30px;
position: relative;
transition: flex-basis @transition-theme;
&:before {
position: absolute;
z-index: 1;
top: 0;
left: 0;
content: ' ';
height: 100px;
width: 100%;
background-image: linear-gradient(0deg,rgba(255,255,255,0) 0%,@color-theme_2-background_1 95%);
pointer-events: none;
}
&:after {
position: absolute;
bottom: 0;
left: 0;
content: ' ';
height: 100px;
width: 100%;
background-image: linear-gradient(-180deg,rgba(255,255,255,0) 0%,@color-theme_2-background_1 95%);
pointer-events: none;
}
}
.lyric {
text-align: center;
height: 100%;
overflow: hidden;
font-size: 16px;
cursor: grab;
&.draging {
cursor: grabbing;
}
:global {
.lrc-content {
line-height: 1.2;
margin: 16px 0;
overflow-wrap: break-word;
color: @color-player-detail-lyric;
.translation {
transition: @transition-theme !important;
transition-property: font-size, color;
font-size: .9em;
margin-top: 5px;
}
.line {
transition-property: font-size, color !important;
background: none !important;
-webkit-text-fill-color: unset;
// -webkit-text-fill-color: none !important;
}
&.active {
.line {
color: @color-theme;
}
.translation {
font-size: 1em;
color: @color-theme;
}
span {
// color: @color-theme;
font-size: 1.2em;
}
}
span {
transition: @transition-theme !important;
transition-property: font-size !important;
font-size: 1em;
background-repeat: no-repeat;
background-color: rgba(77, 77, 77, 0.9);
background-image: -webkit-linear-gradient(top, @color-theme, @color-theme);
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
background-size: 0 100%;
}
}
}
// p {
// padding: 8px 0;
// line-height: 1.2;
// overflow-wrap: break-word;
// transition: @transition-theme !important;
// transition-property: color, font-size;
// }
// .lrc-active {
// color: @color-theme;
// font-size: 1.2em;
// }
}
.lyricSelectContent {
position: absolute;
left: 0;
top: 0;
// text-align: center;
height: 100%;
width: 100%;
font-size: 16px;
background-color: @color-theme_2-background_1;
z-index: 10;
color: @color-player-detail-lyric;
.lyricSelectline {
padding: 8px 0;
overflow-wrap: break-word;
transition: @transition-theme !important;
transition-property: color, font-size;
line-height: 1.3;
}
.lyricSelectlineTransition {
font-size: 14px;
}
.lrc-active {
color: @color-theme;
}
}
.lyric-space {
height: 70%;
}
.comment { .comment {
position: absolute; position: absolute;
@@ -491,14 +369,6 @@ each(@themes, {
background-color: ~'@{color-@{value}-theme_2-background_1}'; background-color: ~'@{color-@{value}-theme_2-background_1}';
// color: ~'@{color-@{value}-theme_2-font}'; // color: ~'@{color-@{value}-theme_2-font}';
} }
.right {
&:before {
background-image: linear-gradient(0deg,rgba(255,255,255,0) 0%,~'@{color-@{value}-theme_2-background_1}' 95%);
}
&:after {
background-image: linear-gradient(-180deg,rgba(255,255,255,0) 0%,~'@{color-@{value}-theme_2-background_1}' 95%);
}
}
.header { .header {
&.controlBtnLeft { &.controlBtnLeft {
.controBtn { .controBtn {
@@ -542,56 +412,6 @@ each(@themes, {
box-shadow: 0 0 4px ~'@{color-@{value}-theme-hover}'; box-shadow: 0 0 4px ~'@{color-@{value}-theme-hover}';
// border-color: ~'@{color-@{value}-theme-hover}'; // border-color: ~'@{color-@{value}-theme-hover}';
} }
.lyric {
:global {
.lrc-content {
color: ~'@{color-@{value}-player-detail-lyric}';
&.active {
.translation {
color: ~'@{color-@{value}-player-detail-lyric-active}';
}
.line {
color: ~'@{color-@{value}-player-detail-lyric-active}';
}
}
span {
background-color: ~'@{color-@{value}-player-detail-lyric}';
background-image: -webkit-linear-gradient(top, ~'@{color-@{value}-player-detail-lyric-active}', ~'@{color-@{value}-player-detail-lyric-active}');
}
}
}
}
// .lrc-active {
// color: ~'@{color-@{value}-theme}';
// }
.lyricSelectContent {
background-color: ~'@{color-@{value}-theme_2-background_1}';
color: ~'@{color-@{value}-player-detail-lyric}';
.lrc-active {
color: ~'@{color-@{value}-theme}';
}
}
.footerLeftControlBtns {
color: ~'@{color-@{value}-theme_2-font}';
}
.footerLeftControlBtn {
&.active {
color: ~'@{color-@{value}-theme}';
}
}
.progress {
background-color: ~'@{color-@{value}-player-progress}';
}
.progress-bar1 {
background-color: ~'@{color-@{value}-player-progress-bar1}';
}
.progress-bar2 {
background-color: ~'@{color-@{value}-player-progress-bar2}';
}
.play-btn {
color: ~'@{color-@{value}-player-detail-play-btn}';
}
} }
}) })
@@ -50,7 +50,7 @@ div(:class="$style.songList")
<script> <script>
import { clipboardWriteText, assertApiSupport } from '@renderer/utils' import { clipboardWriteText, assertApiSupport } from '@renderer/utils'
import { ref, useCssModule } from '@renderer/utils/vueTools' import { ref, useCssModule, useRefGetter } from '@renderer/utils/vueTools'
import useList from './useList' import useList from './useList'
import useMenu from './useMenu' import useMenu from './useMenu'
import usePlay from './usePlay' import usePlay from './usePlay'
@@ -95,7 +95,7 @@ export default {
type: String, type: String,
}, },
}, },
emits: ['show-menu', 'togglePage'], emits: ['show-menu', 'play-list', 'togglePage'],
setup(props, { emit }) { setup(props, { emit }) {
const rightClickSelectedIndex = ref(-1) const rightClickSelectedIndex = ref(-1)
const dom_listContent = ref(null) const dom_listContent = ref(null)
@@ -103,18 +103,20 @@ export default {
const styles = useCssModule() const styles = useCssModule()
const setting = useRefGetter('setting')
const { const {
selectedList, selectedList,
listItemHeight, listItemHeight,
handleSelectData, handleSelectData,
removeAllSelect, removeAllSelect,
} = useList({ props, emit }) } = useList({ props })
const { const {
handlePlayMusic, handlePlayMusic,
handlePlayMusicLater, handlePlayMusicLater,
doubleClickPlay, doubleClickPlay,
} = usePlay({ selectedList, props, removeAllSelect }) } = usePlay({ selectedList, props, removeAllSelect, setting, emit })
const { const {
isShowListAdd, isShowListAdd,
@@ -2,7 +2,7 @@ import { useCommit } from '@renderer/utils/vueTools'
import { defaultList } from '@renderer/core/share/list' import { defaultList } from '@renderer/core/share/list'
import { getList } from '@renderer/core/share/utils' import { getList } from '@renderer/core/share/utils'
export default ({ selectedList, props, removeAllSelect }) => { export default ({ selectedList, props, removeAllSelect, setting, emit }) => {
let clickTime = 0 let clickTime = 0
let clickIndex = -1 let clickIndex = -1
@@ -47,7 +47,11 @@ export default ({ selectedList, props, removeAllSelect }) => {
clickIndex = index clickIndex = index
return return
} }
handlePlayMusic(index, true) if (setting.value.list.isClickPlayList) {
emit('play-list', index)
} else {
handlePlayMusic(index, true)
}
clickTime = 0 clickTime = 0
clickIndex = -1 clickIndex = -1
} }
+1 -1
View File
@@ -104,7 +104,7 @@ export const getPlayIndex = (listId, musicInfo, isTempPlay) => {
} }
const list = getList(listId) const list = getList(listId)
if (list?.length) { if (list?.length && musicInfo) {
if (musicInfo.key) { // 已下载的歌曲 if (musicInfo.key) { // 已下载的歌曲
const currentKey = musicInfo.key const currentKey = musicInfo.key
playIndex = list.findIndex(m => m.key == currentKey) playIndex = list.findIndex(m => m.key == currentKey)
+2 -1
View File
@@ -40,7 +40,7 @@ export default () => {
isProd, isProd,
isLinux, isLinux,
}) })
usePlayer({ setting }) const initPlayer = usePlayer({ setting })
const handleEnvParams = useHandleEnvParams() const handleEnvParams = useHandleEnvParams()
const initData = useDataInit({ const initData = useDataInit({
setting, setting,
@@ -60,6 +60,7 @@ export default () => {
// 初始化我的列表、下载列表等数据 // 初始化我的列表、下载列表等数据
initData().then(() => { initData().then(() => {
initPlayer()
handleEnvParams(envParams) // 处理传入的启动参数 handleEnvParams(envParams) // 处理传入的启动参数
initDeepLink(envParams) initDeepLink(envParams)
}) })
-268
View File
@@ -1,268 +0,0 @@
import { useCommit, useAction, onBeforeUnmount, useRouter, useI18n, markRaw } from '@renderer/utils/vueTools'
import { base as eventBaseName } from '@renderer/event/names'
import { getEnvParams, clearEnvParamsDeeplink } from '@renderer/utils/tools'
import { decodeName } from '@renderer/utils'
// import { allList, defaultList, loveList, userLists } from '@renderer/core/share/list'
import { isShowPlayerDetail, setShowPlayerDetail, playMusicInfo } from '@renderer/core/share/player'
import usePlaySonglist from './compositions/usePlaySonglist'
import { dialog } from '@renderer/plugins/Dialog'
const sources = ['kw', 'kg', 'tx', 'wy', 'mg']
const sourceVerify = source => {
if (!sources.includes(source)) throw new Error('Source no match')
}
const qualitys = ['128k', '320k', 'flac', 'flac32bit']
const qualityFilter = (source, types) => {
types = types.filter(({ type }) => qualitys.includes(type)).map(({ type, size, hash }) => {
if (size != null && typeof size != 'string') throw new Error(type + ' size type no match')
if (source == 'kg' && typeof hash != 'string') throw new Error(type + ' hash type no match')
return hash == null ? { type, size } : { type, size, hash }
})
if (!types.length) throw new Error('quality no match')
return types
}
const dataVerify = (rules, data) => {
const newData = {}
for (const rule of rules) {
const val = data[rule.key]
if (rule.required && val == null) throw new Error(rule.key + ' missing')
if (val != null) {
if (rule.types && !rule.types.includes(typeof val)) throw new Error(rule.key + ' type no match')
if (rule.max && String(val).length > rule.max) throw new Error(rule.key + ' max length no match')
if (rule.min && String(val).length > rule.min) throw new Error(rule.key + ' min length no match')
}
newData[rule.key] = val
}
return newData
}
export default () => {
// const setList = useCommit('list', 'setList')
// const listAdd = useCommit('list', 'listAdd')
// const listMove = useCommit('list', 'listMove')
// const listAddMultiple = useCommit('list', 'listAddMultiple')
// const listMoveMultiple = useCommit('list', 'listMoveMultiple')
// const listRemove = useCommit('list', 'listRemove')
// const listRemoveMultiple = useCommit('list', 'listRemoveMultiple')
// const listClear = useCommit('list', 'listClear')
// const updateMusicInfo = useCommit('list', 'updateMusicInfo')
// const createUserList = useCommit('list', 'createUserList')
// const removeUserList = useCommit('list', 'removeUserList')
// const setUserListName = useCommit('list', 'setUserListName')
// const setMusicPosition = useCommit('list', 'setMusicPosition')
// // const setSyncListData = useCommit('list', 'setSyncListData')
// const setUserListPosition = useCommit('list', 'setUserListPosition')
const router = useRouter()
const setTempPlayList = useCommit('player', 'setTempPlayList')
const playNext = useAction('player', 'playNext')
const playSongListDetail = usePlaySonglist()
const { t } = useI18n()
let isInited = false
const handleOpenSonglist = params => {
if (params.id) {
router.replace({
path: '/songList',
query: {
source: params.source,
id: params.id,
},
})
} else if (params.url) {
router.replace({
path: '/songList',
query: {
source: params.source,
url: params.url,
},
})
}
}
const handlePlayMusic = _musicInfo => {
const musicInfo = {
..._musicInfo,
singer: decodeName(_musicInfo.singer),
name: decodeName(_musicInfo.name),
albumName: decodeName(_musicInfo.albumName),
otherSource: null,
_types: {},
typeUrl: {},
}
for (const type of musicInfo.types) {
musicInfo._types[type.type] = { size: type.size }
}
markRaw(musicInfo)
const isPlaying = !!playMusicInfo.musicInfo
setTempPlayList([{ listId: '__temp__', musicInfo, isTop: true }])
if (isPlaying) playNext()
}
const handleSonglist = (action, songlistInfo) => {
sourceVerify(songlistInfo.source)
switch (action) {
case 'open':
songlistInfo = dataVerify([
{ key: 'source', types: ['string'] },
{ key: 'id', types: ['string', 'number'], max: 64 },
{ key: 'url', types: ['string'], max: 500 },
], songlistInfo)
if (isShowPlayerDetail.value) setShowPlayerDetail(false)
handleOpenSonglist(songlistInfo)
break
case 'play':
songlistInfo = dataVerify([
{ key: 'source', types: ['string'] },
{ key: 'id', types: ['string', 'number'], max: 64 },
{ key: 'url', types: ['string'], max: 500 },
{ key: 'index', types: ['number'], max: 1000000 },
], songlistInfo)
playSongListDetail(songlistInfo.source, songlistInfo.id ?? songlistInfo.url, songlistInfo.index ?? 0)
break
default: throw new Error('Unknown action: ' + action)
}
}
const handleMusic = (action, musicInfo) => {
switch (musicInfo.source) {
case 'kw':
musicInfo = dataVerify([
{ key: 'name', types: ['string'], required: true, max: 200 },
{ key: 'singer', types: ['string'], required: true, max: 200 },
{ key: 'source', types: ['string'], required: true },
{ key: 'songmid', types: ['string', 'number'], max: 64, required: true },
{ key: 'img', types: ['string'], max: 1024 },
{ key: 'albumId', types: ['string', 'number'], max: 64 },
{ key: 'interval', types: ['string'], max: 64 },
{ key: 'albumName', types: ['string'], max: 64 },
{ key: 'types', types: ['object'], required: true },
], musicInfo)
break
case 'kg':
musicInfo = dataVerify([
{ key: 'name', types: ['string'], required: true, max: 200 },
{ key: 'singer', types: ['string'], required: true, max: 200 },
{ key: 'source', types: ['string'], required: true },
{ key: 'songmid', types: ['string', 'number'], max: 64, required: true },
{ key: 'img', types: ['string'], max: 1024 },
{ key: 'albumId', types: ['string', 'number'], max: 64 },
{ key: 'interval', types: ['string'], max: 64 },
{ key: '_interval', types: ['number'], max: 64 },
{ key: 'albumName', types: ['string'], max: 64 },
{ key: 'types', types: ['object'], required: true },
{ key: 'hash', types: ['string'], required: true, max: 64 },
], musicInfo)
break
case 'tx':
musicInfo = dataVerify([
{ key: 'name', types: ['string'], required: true, max: 200 },
{ key: 'singer', types: ['string'], required: true, max: 200 },
{ key: 'source', types: ['string'], required: true },
{ key: 'songmid', types: ['string', 'number'], max: 64, required: true },
{ key: 'img', types: ['string'], max: 1024 },
{ key: 'albumId', types: ['string', 'number'], max: 64 },
{ key: 'interval', types: ['string'], max: 64 },
{ key: 'albumName', types: ['string'], max: 64 },
{ key: 'types', types: ['object'], required: true },
{ key: 'strMediaMid', types: ['string'], required: true, max: 64 },
{ key: 'albumMid', types: ['string'], max: 64 },
], musicInfo)
break
case 'wy':
musicInfo = dataVerify([
{ key: 'name', types: ['string'], required: true, max: 200 },
{ key: 'singer', types: ['string'], required: true, max: 200 },
{ key: 'source', types: ['string'], required: true },
{ key: 'songmid', types: ['string', 'number'], max: 64, required: true },
{ key: 'img', types: ['string'], max: 1024 },
{ key: 'albumId', types: ['string', 'number'], max: 64 },
{ key: 'interval', types: ['string'], max: 64 },
{ key: 'albumName', types: ['string'], max: 64 },
{ key: 'types', types: ['object'], required: true },
], musicInfo)
break
case 'mg':
musicInfo = dataVerify([
{ key: 'name', types: ['string'], required: true, max: 200 },
{ key: 'singer', types: ['string'], required: true, max: 200 },
{ key: 'source', types: ['string'], required: true },
{ key: 'songmid', types: ['string', 'number'], max: 64, required: true },
{ key: 'img', types: ['string'], max: 1024 },
{ key: 'albumId', types: ['string', 'number'], max: 64 },
{ key: 'interval', types: ['string'], max: 64 },
{ key: 'albumName', types: ['string'], max: 64 },
{ key: 'types', types: ['object'], required: true },
{ key: 'copyrightId', types: ['string', 'number'], required: true, max: 64 },
{ key: 'lrcUrl', types: ['string'], max: 1024 },
], musicInfo)
break
default: throw new Error('Unknown action: ' + action)
}
musicInfo.types = qualityFilter(musicInfo.source, musicInfo.types)
switch (action) {
case 'play':
handlePlayMusic(musicInfo)
break
default: throw new Error('Unknown action: ' + action)
}
}
const handleLinkAction = link => {
// console.log(link)
const [url, search] = link.split('?')
const [type, action] = url.replace('lxmusic://', '').split('/')
const params = {}
for (const param of search.split('&')) {
const [key, value] = param.split('=')
params[key] = value
}
if (params.data) params.data = JSON.parse(decodeURIComponent(params.data))
console.log(params.data)
switch (type) {
case 'music':
handleMusic(action, params.data)
break
case 'songlist':
handleSonglist(action, params.data)
break
default: throw new Error('Unknown type: ' + type)
}
}
const handleFocus = () => {
if (!isInited) return
getEnvParams().then(envParams => {
if (!envParams.deeplink) return
clearEnvParamsDeeplink()
try {
handleLinkAction(envParams.deeplink)
} catch (err) {
dialog(`${t('deep_link__handle_error_tip', { message: err.message })}`)
}
})
}
window.eventHub.on(eventBaseName.focus, handleFocus)
onBeforeUnmount(() => {
window.eventHub.off(eventBaseName.focus, handleFocus)
})
return envParams => {
if (envParams.deeplink) {
clearEnvParamsDeeplink()
try {
handleLinkAction(envParams.deeplink)
} catch (err) {
dialog(`${t('deep_link__handle_error_tip', { message: err.message })}`)
}
}
isInited = true
}
}
@@ -0,0 +1,74 @@
import { onBeforeUnmount } from '@renderer/utils/vueTools'
import { base as eventBaseName } from '@renderer/event/names'
import { getEnvParams, clearEnvParamsDeeplink } from '@renderer/utils/tools'
import { useDialog } from './utils'
import useMusicAction from './useMusicAction'
import useSonglistAction from './useSonglistAction'
export default () => {
let isInited = false
const showErrorDialog = useDialog()
const handleMusicAction = useMusicAction()
const handleSonglistAction = useSonglistAction()
const handleLinkAction = link => {
// console.log(link)
const [url, search] = link.split('?')
const [type, action, ...paths] = url.replace('lxmusic://', '').split('/')
const params = {}
if (search) {
for (const param of search.split('&')) {
const [key, value] = param.split('=')
params[key] = value
}
if (params.data) params.data = JSON.parse(decodeURIComponent(params.data))
}
params.paths = paths.map(p => decodeURIComponent(p))
console.log(params)
switch (type) {
case 'music':
handleMusicAction(action, params)
break
case 'songlist':
handleSonglistAction(action, params)
break
default: throw new Error('Unknown type: ' + type)
}
}
const handleFocus = () => {
if (!isInited) return
getEnvParams().then(envParams => {
if (!envParams.deeplink) return
clearEnvParamsDeeplink()
try {
handleLinkAction(envParams.deeplink)
} catch (err) {
showErrorDialog(err.message)
}
})
}
window.eventHub.on(eventBaseName.focus, handleFocus)
onBeforeUnmount(() => {
window.eventHub.off(eventBaseName.focus, handleFocus)
})
return envParams => {
if (envParams.deeplink) {
clearEnvParamsDeeplink()
try {
handleLinkAction(envParams.deeplink)
} catch (err) {
showErrorDialog(err.message)
}
}
isInited = true
}
}
@@ -0,0 +1,167 @@
import { useCommit, useAction, useRouter, markRaw } from '@renderer/utils/vueTools'
import { decodeName } from '@renderer/utils'
// import { allList, defaultList, loveList, userLists } from '@renderer/core/share/list'
import { playMusicInfo } from '@renderer/core/share/player'
import { dataVerify, qualityFilter, sources } from './utils'
const useSearchMusic = () => {
const router = useRouter()
return ({ paths, data: params }) => {
let text
let source
if (params) {
text = dataVerify([
{ key: 'keywords', types: ['string', 'number'], max: 128, required: true },
], params).keywords
source = params.source
} else {
if (!paths.length) throw new Error('Keyword missing')
if (paths.length > 1) {
text = paths[1]
source = paths[0]
} else {
text = paths[0]
}
if (text.length > 128) text = text.substring(0, 128)
}
const sourceList = [...sources, 'all']
source = sourceList.includes(source) ? source : null
router.replace({
path: '/search',
query: {
text,
source,
},
})
}
}
const usePlayMusic = () => {
const setTempPlayList = useCommit('player', 'setTempPlayList')
const playNext = useAction('player', 'playNext')
const filterInfoByPlayMusic = musicInfo => {
switch (musicInfo.source) {
case 'kw':
musicInfo = dataVerify([
{ key: 'name', types: ['string'], required: true, max: 200 },
{ key: 'singer', types: ['string'], required: true, max: 200 },
{ key: 'source', types: ['string'], required: true },
{ key: 'songmid', types: ['string', 'number'], max: 64, required: true },
{ key: 'img', types: ['string'], max: 1024 },
{ key: 'albumId', types: ['string', 'number'], max: 64 },
{ key: 'interval', types: ['string'], max: 64 },
{ key: 'albumName', types: ['string'], max: 64 },
{ key: 'types', types: ['object'], required: true },
], musicInfo)
break
case 'kg':
musicInfo = dataVerify([
{ key: 'name', types: ['string'], required: true, max: 200 },
{ key: 'singer', types: ['string'], required: true, max: 200 },
{ key: 'source', types: ['string'], required: true },
{ key: 'songmid', types: ['string', 'number'], max: 64, required: true },
{ key: 'img', types: ['string'], max: 1024 },
{ key: 'albumId', types: ['string', 'number'], max: 64 },
{ key: 'interval', types: ['string'], max: 64 },
{ key: '_interval', types: ['number'], max: 64 },
{ key: 'albumName', types: ['string'], max: 64 },
{ key: 'types', types: ['object'], required: true },
{ key: 'hash', types: ['string'], required: true, max: 64 },
], musicInfo)
break
case 'tx':
musicInfo = dataVerify([
{ key: 'name', types: ['string'], required: true, max: 200 },
{ key: 'singer', types: ['string'], required: true, max: 200 },
{ key: 'source', types: ['string'], required: true },
{ key: 'songmid', types: ['string', 'number'], max: 64, required: true },
{ key: 'img', types: ['string'], max: 1024 },
{ key: 'albumId', types: ['string', 'number'], max: 64 },
{ key: 'interval', types: ['string'], max: 64 },
{ key: 'albumName', types: ['string'], max: 64 },
{ key: 'types', types: ['object'], required: true },
{ key: 'strMediaMid', types: ['string'], required: true, max: 64 },
{ key: 'albumMid', types: ['string'], max: 64 },
], musicInfo)
break
case 'wy':
musicInfo = dataVerify([
{ key: 'name', types: ['string'], required: true, max: 200 },
{ key: 'singer', types: ['string'], required: true, max: 200 },
{ key: 'source', types: ['string'], required: true },
{ key: 'songmid', types: ['string', 'number'], max: 64, required: true },
{ key: 'img', types: ['string'], max: 1024 },
{ key: 'albumId', types: ['string', 'number'], max: 64 },
{ key: 'interval', types: ['string'], max: 64 },
{ key: 'albumName', types: ['string'], max: 64 },
{ key: 'types', types: ['object'], required: true },
], musicInfo)
break
case 'mg':
musicInfo = dataVerify([
{ key: 'name', types: ['string'], required: true, max: 200 },
{ key: 'singer', types: ['string'], required: true, max: 200 },
{ key: 'source', types: ['string'], required: true },
{ key: 'songmid', types: ['string', 'number'], max: 64, required: true },
{ key: 'img', types: ['string'], max: 1024 },
{ key: 'albumId', types: ['string', 'number'], max: 64 },
{ key: 'interval', types: ['string'], max: 64 },
{ key: 'albumName', types: ['string'], max: 64 },
{ key: 'types', types: ['object'], required: true },
{ key: 'copyrightId', types: ['string', 'number'], required: true, max: 64 },
{ key: 'lrcUrl', types: ['string'], max: 1024 },
], musicInfo)
break
default: throw new Error('Unknown source: ' + musicInfo.source)
}
musicInfo.types = qualityFilter(musicInfo.source, musicInfo.types)
return musicInfo
}
return ({ data: _musicInfo }) => {
_musicInfo = filterInfoByPlayMusic(_musicInfo)
const musicInfo = {
..._musicInfo,
singer: decodeName(_musicInfo.singer),
name: decodeName(_musicInfo.name),
albumName: decodeName(_musicInfo.albumName),
otherSource: null,
_types: {},
typeUrl: {},
}
for (const type of musicInfo.types) {
musicInfo._types[type.type] = { size: type.size }
}
markRaw(musicInfo)
const isPlaying = !!playMusicInfo.musicInfo
setTempPlayList([{ listId: '__temp__', musicInfo, isTop: true }])
if (isPlaying) playNext()
}
}
export default () => {
const handleSearchMusic = useSearchMusic()
const handlePlayMusic = usePlayMusic()
return (action, info) => {
switch (action) {
case 'search':
handleSearchMusic(info)
break
case 'play':
handlePlayMusic(info)
break
default: throw new Error('Unknown action: ' + action)
}
}
}
@@ -0,0 +1,106 @@
import { useRouter } from '@renderer/utils/vueTools'
import { isShowPlayerDetail, setShowPlayerDetail } from '@renderer/core/share/player'
import usePlaySonglist from '../compositions/usePlaySonglist'
import { dataVerify, sourceVerify } from './utils'
const useOpenSonglist = () => {
const router = useRouter()
const handleOpenSonglist = params => {
if (params.id) {
router.replace({
path: '/songList',
query: {
source: params.source,
id: params.id,
},
})
} else if (params.url) {
router.replace({
path: '/songList',
query: {
source: params.source,
url: params.url,
},
})
}
}
return ({ paths, data }) => {
let songlistInfo = {
source: null,
id: null,
url: null,
}
if (data) {
songlistInfo = data
} else {
songlistInfo.source = paths[0]
songlistInfo.url = paths[1]
}
sourceVerify(songlistInfo.source)
songlistInfo = dataVerify([
{ key: 'source', types: ['string'] },
{ key: 'id', types: ['string', 'number'], max: 64 },
{ key: 'url', types: ['string'], max: 500 },
], songlistInfo)
if (!songlistInfo.id && !songlistInfo.url) throw new Error('id or url missing')
if (isShowPlayerDetail.value) setShowPlayerDetail(false)
handleOpenSonglist(songlistInfo)
}
}
const usePlaySonglistDetail = () => {
const playSongListDetail = usePlaySonglist()
return ({ paths, data }) => {
let songlistInfo = {
source: null,
id: null,
url: null,
index: null,
}
if (data) {
songlistInfo = data
} else {
songlistInfo.source = paths[0]
songlistInfo.url = paths[1]
songlistInfo.index = paths[2]
if (songlistInfo.index != null) songlistInfo.index = parseInt(songlistInfo.index)
}
sourceVerify(songlistInfo.source)
songlistInfo = dataVerify([
{ key: 'source', types: ['string'] },
{ key: 'id', types: ['string', 'number'], max: 64 },
{ key: 'url', types: ['string'], max: 500 },
{ key: 'index', types: ['number'], max: 1000000 },
], songlistInfo)
if (!songlistInfo.id && !songlistInfo.url) throw new Error('id or url missing')
playSongListDetail(songlistInfo.source, songlistInfo.id ?? songlistInfo.url, songlistInfo.index ?? 0)
}
}
export default () => {
const handleOpenSonglist = useOpenSonglist()
const handlePlaySonglist = usePlaySonglistDetail()
return (action, info) => {
switch (action) {
case 'open':
handleOpenSonglist(info)
break
case 'play':
handlePlaySonglist(info)
break
default: throw new Error('Unknown action: ' + action)
}
}
}
@@ -0,0 +1,45 @@
import { useI18n } from '@renderer/utils/vueTools'
import { dialog } from '@renderer/plugins/Dialog'
export const useDialog = () => {
const { t } = useI18n()
const errorDialog = message => {
dialog({
message: `${t('deep_link__handle_error_tip', { message })}`,
confirmButtonText: t('ok'),
})
}
return errorDialog
}
export const sources = ['kw', 'kg', 'tx', 'wy', 'mg']
export const sourceVerify = source => {
if (!sources.includes(source)) throw new Error('Source no match')
}
export const qualitys = ['128k', '320k', 'flac', 'flac32bit']
export const qualityFilter = (source, types) => {
types = types.filter(({ type }) => qualitys.includes(type)).map(({ type, size, hash }) => {
if (size != null && typeof size != 'string') throw new Error(type + ' size type no match')
if (source == 'kg' && typeof hash != 'string') throw new Error(type + ' hash type no match')
return hash == null ? { type, size } : { type, size, hash }
})
if (!types.length) throw new Error('quality no match')
return types
}
export const dataVerify = (rules, data) => {
const newData = {}
for (const rule of rules) {
const val = data[rule.key]
if (rule.required && val == null) throw new Error(rule.key + ' missing')
if (val != null) {
if (rule.types && !rule.types.includes(typeof val)) throw new Error(rule.key + ' type no match')
if (rule.max && String(val).length > rule.max) throw new Error(rule.key + ' max length no match')
if (rule.min && String(val).length > rule.min) throw new Error(rule.key + ' min length no match')
}
newData[rule.key] = val
}
return newData
}
+31 -3
View File
@@ -1,18 +1,22 @@
import { onBeforeUnmount } from '@renderer/utils/vueTools' import { onBeforeUnmount, useI18n } from '@renderer/utils/vueTools'
import { onUserApiStatus, setUserApi, getUserApiList, userApiRequest, userApiRequestCancel } from '@renderer/utils/tools' import { onUserApiStatus, setUserApi, getUserApiList, userApiRequest, userApiRequestCancel, onShowUserApiUpdateAlert } from '@renderer/utils/tools'
import { openUrl } from '@renderer/utils'
import apiSourceInfo from '@renderer/utils/music/api-source-info' import apiSourceInfo from '@renderer/utils/music/api-source-info'
import music from '@renderer/utils/music' import music from '@renderer/utils/music'
import { apiSource, qualityList, userApi } from '@renderer/core/share' import { apiSource, qualityList, userApi } from '@renderer/core/share'
import { dialog } from '@renderer/plugins/Dialog'
export default ({ setting }) => { export default ({ setting }) => {
const { t } = useI18n()
if (/^user_api/.test(setting.value.apiSource)) { if (/^user_api/.test(setting.value.apiSource)) {
setUserApi(setting.value.apiSource) setUserApi(setting.value.apiSource)
} else { } else {
qualityList.value = music.supportQuality[setting.value.apiSource] qualityList.value = music.supportQuality[setting.value.apiSource]
} }
const rUserApiStatus = onUserApiStatus(({ status, message, apiInfo }) => { const rUserApiStatus = onUserApiStatus((event, { status, message, apiInfo }) => {
userApi.status = status userApi.status = status
userApi.message = message userApi.message = message
@@ -66,8 +70,32 @@ export default ({ setting }) => {
} }
}) })
const rUserApiShowUpdateAlert = onShowUserApiUpdateAlert((event, { name, log, updateUrl }) => {
if (updateUrl) {
dialog({
message: `${t('user_api__update_alert', { name })}\n${log}`,
selection: true,
showCancel: true,
confirmButtonText: t('user_api__update_alert_open_url'),
cancelButtonText: t('close'),
}).then(confirm => {
if (!confirm) return
setTimeout(() => {
openUrl(updateUrl)
}, 300)
})
} else {
dialog({
message: `${t('user_api__update_alert', { name })}\n${log}`,
selection: true,
confirmButtonText: t('ok'),
})
}
})
onBeforeUnmount(() => { onBeforeUnmount(() => {
rUserApiStatus() rUserApiStatus()
rUserApiShowUpdateAlert()
}) })
return () => { return () => {
@@ -4,6 +4,8 @@ import {
import useMediaDevice from './useMediaDevice' import useMediaDevice from './useMediaDevice'
import usePlayerEvent from './usePlayerEvent' import usePlayerEvent from './usePlayerEvent'
import usePlayer from './usePlayer' import usePlayer from './usePlayer'
import useTaskbar from './useTaskbar'
import { init as initPlayTimeoutStop } from '@renderer/utils/timeoutStop'
export default ({ setting }) => { export default ({ setting }) => {
createAudio() createAudio()
@@ -11,5 +13,12 @@ export default ({ setting }) => {
usePlayerEvent() usePlayerEvent()
useMediaDevice({ setting }) // 初始化音频驱动输出设置 useMediaDevice({ setting }) // 初始化音频驱动输出设置
usePlayer({ setting }) usePlayer({ setting })
const initTaskbar = useTaskbar()
initPlayTimeoutStop()
return () => {
initTaskbar()
}
} }
@@ -49,6 +49,7 @@ export default ({
} }
const handleLoadstart = () => { const handleLoadstart = () => {
if (global.isPlayedStop) return
startLoadingTimeout() startLoadingTimeout()
setAllStatus(t('player__loading')) setAllStatus(t('player__loading'))
} }
@@ -77,6 +78,7 @@ export default ({
const handleError = errCode => { const handleError = errCode => {
if (!musicInfo.songmid) return if (!musicInfo.songmid) return
clearLoadingTimeout() clearLoadingTimeout()
if (global.isPlayedStop) return
if (playMusicInfo.listId != 'download' && errCode !== 1 && retryNum < 2) { // 若音频URL无效则尝试刷新2次URL if (playMusicInfo.listId != 'download' && errCode !== 1 && retryNum < 2) { // 若音频URL无效则尝试刷新2次URL
// console.log(this.retryNum) // console.log(this.retryNum)
retryNum++ retryNum++
@@ -96,6 +98,11 @@ export default ({
clearLoadingTimeout() clearLoadingTimeout()
} }
const handlePlayedStop = () => {
clearDelayNextTimeout()
clearLoadingTimeout()
}
window.eventHub.on(eventPlayerNames.player_loadstart, handleLoadstart) window.eventHub.on(eventPlayerNames.player_loadstart, handleLoadstart)
window.eventHub.on(eventPlayerNames.player_loadeddata, handleLoadeddata) window.eventHub.on(eventPlayerNames.player_loadeddata, handleLoadeddata)
@@ -105,6 +112,7 @@ export default ({
window.eventHub.on(eventPlayerNames.player_emptied, handleEmpied) window.eventHub.on(eventPlayerNames.player_emptied, handleEmpied)
window.eventHub.on(eventPlayerNames.error, handleError) window.eventHub.on(eventPlayerNames.error, handleError)
window.eventHub.on(eventPlayerNames.setPlayInfo, handleSetPlayInfo) window.eventHub.on(eventPlayerNames.setPlayInfo, handleSetPlayInfo)
window.eventHub.on(eventPlayerNames.playedStop, handlePlayedStop)
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.eventHub.off(eventPlayerNames.player_loadstart, handleLoadstart) window.eventHub.off(eventPlayerNames.player_loadstart, handleLoadstart)
@@ -115,5 +123,6 @@ export default ({
window.eventHub.off(eventPlayerNames.player_emptied, handleEmpied) window.eventHub.off(eventPlayerNames.player_emptied, handleEmpied)
window.eventHub.off(eventPlayerNames.error, handleError) window.eventHub.off(eventPlayerNames.error, handleError)
window.eventHub.off(eventPlayerNames.setPlayInfo, handleSetPlayInfo) window.eventHub.off(eventPlayerNames.setPlayInfo, handleSetPlayInfo)
window.eventHub.off(eventPlayerNames.playedStop, handlePlayedStop)
}) })
} }
+34 -10
View File
@@ -27,7 +27,6 @@ import {
setMusicInfo, setMusicInfo,
musicInfoItem, musicInfoItem,
playMusicInfo, playMusicInfo,
playInfo,
setPlayList, setPlayList,
setPlayMusicInfo, setPlayMusicInfo,
playedList, playedList,
@@ -103,11 +102,13 @@ export default ({ setting }) => {
setAllStatus('Try toggle source...') setAllStatus('Try toggle source...')
}, },
}).then(url => { }).then(url => {
if (global.isPlayedStop) return
if (targetSong !== musicInfoItem.value || isPlay.value || type != getPlayType(setting.value.player.highQuality, musicInfoItem.value)) return if (targetSong !== musicInfoItem.value || isPlay.value || type != getPlayType(setting.value.player.highQuality, musicInfoItem.value)) return
setMusicInfo({ url }) setMusicInfo({ url })
setResource(url) setResource(url)
}).catch(err => { }).catch(err => {
// console.log('err', err.message) // console.log('err', err.message)
if (global.isPlayedStop) return
if (targetSong !== musicInfoItem.value || isPlay.value) return if (targetSong !== musicInfoItem.value || isPlay.value) return
if (err.message == requestMsg.cancelRequest) return if (err.message == requestMsg.cancelRequest) return
if (!isRetryed) return setUrl(targetSong, isRefresh, true) if (!isRetryed) return setUrl(targetSong, isRefresh, true)
@@ -193,11 +194,10 @@ export default ({ setting }) => {
const setPlayStatus = () => { const setPlayStatus = () => {
setPlay(true) setPlay(true)
setTitle(`${musicInfo.name} - ${musicInfo.singer}`)
} }
const setPauseStatus = () => { const setPauseStatus = () => {
setPlay(false) setPlay(false)
setTitle() if (global.isPlayedStop) handlePause()
} }
const setStopStatus = () => { const setStopStatus = () => {
setPlay(false) setPlay(false)
@@ -226,7 +226,7 @@ export default ({ setting }) => {
return return
} }
if (playInfo.musicInfo) { if (playMusicInfo.musicInfo) {
setPlayerStop() setPlayerStop()
window.eventHub.emit(eventPlayerNames.pause) window.eventHub.emit(eventPlayerNames.pause)
setStopStatus() setStopStatus()
@@ -279,20 +279,27 @@ export default ({ setting }) => {
name: musicInfo.name, name: musicInfo.name,
album: musicInfo.albumName, album: musicInfo.albumName,
}) })
setTitle(`${musicInfo.name} - ${musicInfo.singer}`)
} }
const handelStop = () => { const handelStop = () => {
setPlayerStop() setPlayerStop()
setPlayMusicInfo(playMusicInfo.listId, null)
window.eventHub.emit(eventPlayerNames.stop) window.eventHub.emit(eventPlayerNames.stop)
} }
const handleEnded = () => { const handleEnded = () => {
setAllStatus(t('player__end')) setAllStatus(t('player__end'))
if (global.isPlayedStop) return
playNext() playNext()
} }
// 播放、暂停播放切换 const handlePause = () => {
const handleTogglePlay = async() => { setPlayerPause()
}
const handlePlay = async() => {
if (playMusicInfo.musicInfo == null) return if (playMusicInfo.musicInfo == null) return
if (isPlayerEmpty()) { if (isPlayerEmpty()) {
if (playMusicInfo.listId == 'download') { if (playMusicInfo.listId == 'download') {
@@ -313,13 +320,24 @@ export default ({ setting }) => {
} }
return return
} }
setPlayerPlay()
}
// 播放、暂停播放切换
const handleTogglePlay = () => {
if (global.isPlayedStop) global.isPlayedStop = false
if (isPlay.value) { if (isPlay.value) {
setPlayerPause() handlePause()
} else { } else {
setPlayerPlay() handlePlay()
} }
} }
const handlePlayedStop = () => {
clearDelayNextTimeout()
clearLoadTimeout()
}
watch(() => setting.value.player.togglePlayMethod, newValue => { watch(() => setting.value.player.togglePlayMethod, newValue => {
setLoopPlay(newValue === 'singleLoop') setLoopPlay(newValue === 'singleLoop')
if (playedList.length) clearPlayedList() if (playedList.length) clearPlayedList()
@@ -339,13 +357,16 @@ export default ({ setting }) => {
window.eventHub.on(eventPlayerNames.stop, setStopStatus) window.eventHub.on(eventPlayerNames.stop, setStopStatus)
window.eventHub.on(eventPlayerNames.playMusic, playMusic) window.eventHub.on(eventPlayerNames.playMusic, playMusic)
window.eventHub.on(eventPlayerNames.setPlay, handlePlay)
window.eventHub.on(eventPlayerNames.setPause, handlePause)
window.eventHub.on(eventPlayerNames.setStop, handelStop)
window.eventHub.on(eventPlayerNames.setTogglePlay, handleTogglePlay) window.eventHub.on(eventPlayerNames.setTogglePlay, handleTogglePlay)
window.eventHub.on(eventPlayerNames.setPlayPrev, playPrev) window.eventHub.on(eventPlayerNames.setPlayPrev, playPrev)
window.eventHub.on(eventPlayerNames.setPlayNext, playNext) window.eventHub.on(eventPlayerNames.setPlayNext, playNext)
window.eventHub.on(eventPlayerNames.setPlayInfo, handleSetPlayInfo) window.eventHub.on(eventPlayerNames.setPlayInfo, handleSetPlayInfo)
window.eventHub.on(eventPlayerNames.setStop, handelStop)
window.eventHub.on(eventPlayerNames.player_ended, handleEnded) window.eventHub.on(eventPlayerNames.player_ended, handleEnded)
window.eventHub.on(eventPlayerNames.playedStop, handlePlayedStop)
onBeforeUnmount(() => { onBeforeUnmount(() => {
@@ -360,11 +381,14 @@ export default ({ setting }) => {
window.eventHub.off(eventPlayerNames.playMusic, playMusic) window.eventHub.off(eventPlayerNames.playMusic, playMusic)
window.eventHub.off(eventPlayerNames.setTogglePlay, handleTogglePlay) window.eventHub.off(eventPlayerNames.setTogglePlay, handleTogglePlay)
window.eventHub.off(eventPlayerNames.setPlay, handlePlay)
window.eventHub.off(eventPlayerNames.setPause, handlePause)
window.eventHub.off(eventPlayerNames.setStop, handelStop)
window.eventHub.off(eventPlayerNames.setPlayPrev, playPrev) window.eventHub.off(eventPlayerNames.setPlayPrev, playPrev)
window.eventHub.off(eventPlayerNames.setPlayNext, playNext) window.eventHub.off(eventPlayerNames.setPlayNext, playNext)
window.eventHub.off(eventPlayerNames.setPlayInfo, handleSetPlayInfo) window.eventHub.off(eventPlayerNames.setPlayInfo, handleSetPlayInfo)
window.eventHub.off(eventPlayerNames.setStop, handelStop)
window.eventHub.off(eventPlayerNames.player_ended, handleEnded) window.eventHub.off(eventPlayerNames.player_ended, handleEnded)
window.eventHub.off(eventPlayerNames.playedStop, handlePlayedStop)
}) })
} }
@@ -0,0 +1,128 @@
import { onBeforeUnmount, useCommit } from '@renderer/utils/vueTools'
import { player as eventPlayerNames, taskbar as eventTaskbarNames } from '@renderer/event/names'
import { onTaskbarThumbarClick, setTaskbarThumbnailClip, setTaskbarThumbarButtons } from '@renderer/utils/tools'
// import store from '@renderer/store'
import { loveList, getList } from '@renderer/core/share/list'
import { playMusicInfo } from '@renderer/core/share/player'
export default () => {
const listAdd = useCommit('list', 'listAdd')
const listRemove = useCommit('list', 'listRemove')
// const setVisibleDesktopLyric = useCommit('setVisibleDesktopLyric')
// const setLockDesktopLyric = useCommit('setLockDesktopLyric')
const buttons = {
empty: true,
collect: false,
play: false,
prev: true,
next: true,
lrc: false,
lockLrc: false,
}
const setButtons = () => {
setTaskbarThumbarButtons(buttons)
}
const updateCollectStatus = () => {
let status = !!playMusicInfo.musicInfo && getList(loveList.id).some(musicInfo => playMusicInfo.musicInfo.songmid == musicInfo.songmid)
if (buttons.collect == status) return false
buttons.collect = status
return true
}
const handlePlay = () => {
if (buttons.empty) buttons.empty = false
buttons.play = true
setButtons()
}
const handlePause = () => {
if (buttons.empty) buttons.empty = false
buttons.play = false
setButtons()
}
const handleStop = async() => {
if (playMusicInfo.musicInfo != null) return
if (buttons.collect) buttons.collect = false
buttons.empty = true
setButtons()
}
const handleSetPlayInfo = () => {
if (!updateCollectStatus()) return
setButtons()
}
const handleSetTaskbarThumbnailClip = (clip) => {
setTaskbarThumbnailClip(clip)
}
// const updateSetting = () => {
// const setting = store.getters.setting
// buttons.lrc = setting.desktopLyric.enable
// buttons.lockLrc = setting.desktopLyric.isLock
// setButtons()
// }
const rTaskbarThumbarClick = onTaskbarThumbarClick((event, action) => {
switch (action) {
case 'play':
window.eventHub.emit(eventPlayerNames.setPlay)
break
case 'pause':
window.eventHub.emit(eventPlayerNames.setPause)
break
case 'prev':
window.eventHub.emit(eventPlayerNames.setPlayPrev)
break
case 'next':
window.eventHub.emit(eventPlayerNames.setPlayNext)
break
case 'collect':
if (!playMusicInfo.musicInfo) return
listAdd({ id: loveList.id, musicInfo: playMusicInfo.musicInfo })
if (updateCollectStatus()) setButtons()
break
case 'unCollect':
if (!playMusicInfo.musicInfo) return
listRemove({ listId: loveList.id, id: playMusicInfo.musicInfo.songmid })
if (updateCollectStatus()) setButtons()
break
// case 'lrc':
// setVisibleDesktopLyric(true)
// updateSetting()
// break
// case 'unLrc':
// setVisibleDesktopLyric(false)
// updateSetting()
// break
// case 'lockLrc':
// setLockDesktopLyric(true)
// updateSetting()
// break
// case 'unlockLrc':
// setLockDesktopLyric(false)
// updateSetting()
// break
}
})
window.eventHub.on(eventPlayerNames.play, handlePlay)
window.eventHub.on(eventPlayerNames.pause, handlePause)
window.eventHub.on(eventPlayerNames.stop, handleStop)
window.eventHub.on(eventPlayerNames.setPlayInfo, handleSetPlayInfo)
window.eventHub.on(eventTaskbarNames.setTaskbarThumbnailClip, handleSetTaskbarThumbnailClip)
onBeforeUnmount(() => {
rTaskbarThumbarClick()
window.eventHub.off(eventPlayerNames.play, handlePlay)
window.eventHub.off(eventPlayerNames.pause, handlePause)
window.eventHub.off(eventPlayerNames.stop, handleStop)
window.eventHub.off(eventPlayerNames.setPlayInfo, handleSetPlayInfo)
window.eventHub.off(eventTaskbarNames.setTaskbarThumbnailClip, handleSetTaskbarThumbnailClip)
})
return () => {
// const setting = store.getters.setting
// buttons.lrc = setting.desktopLyric.enable
// buttons.lockLrc = setting.desktopLyric.isLock
updateCollectStatus()
if (playMusicInfo.musicInfo != null) buttons.empty = false
setButtons()
}
}
@@ -15,7 +15,7 @@ export default ({ playNext }) => {
const { playIndex } = updatePlayIndex() const { playIndex } = updatePlayIndex()
if (playIndex < 0 && !playMusicInfo.isTempPlay) { // 歌曲被移除 if (playIndex < 0 && !playMusicInfo.isTempPlay) { // 歌曲被移除
if (getList(playMusicInfo.listId).length) { if (getList(playMusicInfo.listId).length && !global.isPlayedStop) {
playNext() playNext()
} else { } else {
window.eventHub.emit(eventPlayerNames.setStop) window.eventHub.emit(eventPlayerNames.setStop)
+9 -3
View File
@@ -15,6 +15,7 @@ const names = {
}, },
player: { player: {
setTogglePlay: 'setTogglePlay', // 播放/暂停切换 setTogglePlay: 'setTogglePlay', // 播放/暂停切换
setPlay: 'setPlay', // 播放
setPause: 'setPause', // 暂停 setPause: 'setPause', // 暂停
setStop: 'setStop', // 停止 setStop: 'setStop', // 停止
setPlayPrev: 'setPlayPrev', // 上一曲 setPlayPrev: 'setPlayPrev', // 上一曲
@@ -26,11 +27,12 @@ const names = {
playMusic: 'playMusic', playMusic: 'playMusic',
setPlayInfo: 'setPlayInfo', setPlayInfo: 'setPlayInfo', // 设置播放信息
updatePic: 'updatePic', updatePic: 'updatePic', // 更新图片事件
updateLyric: 'updateLyric', updateLyric: 'updateLyric', // 更新歌词事件
activeTransition: 'activeTransition', // 激活进度条动画事件 activeTransition: 'activeTransition', // 激活进度条动画事件
playedStop: 'playedStop', // 定时停止事件
// 播放器事件 // 播放器事件
play: 'play', play: 'play',
@@ -64,6 +66,9 @@ const names = {
send_sync_list: 'send_sync_list', send_sync_list: 'send_sync_list',
handle_sync_list: 'handle_sync_list', handle_sync_list: 'handle_sync_list',
}, },
taskbar: {
setTaskbarThumbnailClip: 'setTaskbarThumbnailClip',
},
} }
for (const item of Object.keys(names)) { for (const item of Object.keys(names)) {
@@ -78,3 +83,4 @@ export const player = names.player
export const list = names.list export const list = names.list
export const download = names.download export const download = names.download
export const sync = names.sync export const sync = names.sync
export const taskbar = names.taskbar
+5 -4
View File
@@ -1,6 +1,6 @@
<template> <template>
<Modal :show="visible" @close="handleCancel" @after-leave="afterLeave" :closeBtn="false" :teleport="teleport"> <Modal :show="visible" @close="handleCancel" @after-leave="afterLeave" :closeBtn="false" :teleport="teleport">
<main :class="$style.main">{{message}}</main> <main class="scroll" :class="[$style.main, { 'select': selection }]">{{message}}</main>
<footer :class="$style.footer"> <footer :class="$style.footer">
<Btn :class="$style.btn" v-if="showCancel" @click="handleCancel">{{cancelBtnText}}</Btn> <Btn :class="$style.btn" v-if="showCancel" @click="handleCancel">{{cancelBtnText}}</Btn>
<Btn :class="$style.btn" @click="handleComfirm">{{confirmBtnText}}</Btn> <Btn :class="$style.btn" @click="handleComfirm">{{confirmBtnText}}</Btn>
@@ -30,6 +30,7 @@ export default {
cancelButtonText: '', cancelButtonText: '',
confirmButtonText: '', confirmButtonText: '',
teleport: '#root', teleport: '#root',
selection: false,
} }
}, },
computed: { computed: {
@@ -58,9 +59,9 @@ export default {
.main { .main {
flex: auto; flex: auto;
min-height: 40px; min-height: 40px;
padding: 15px; padding: 15px 15px 0;
font-size: 14px; font-size: 14px;
max-width: 320px; // max-width: 320px;
min-width: 220px; min-width: 220px;
line-height: 1.5; line-height: 1.5;
white-space: pre-line; white-space: pre-line;
@@ -68,7 +69,7 @@ export default {
.footer { .footer {
flex: none; flex: none;
padding: 0 15px 15px; padding: 15px;
display: flex; display: flex;
flex-flow: row nowrap; flex-flow: row nowrap;
justify-content: flex-end; justify-content: flex-end;
+3 -1
View File
@@ -10,10 +10,11 @@ const defaultOptions = {
showCancel: false, showCancel: false,
cancelButtonText: '', cancelButtonText: '',
confirmButtonText: '', confirmButtonText: '',
selection: false,
} }
export const dialog = function(options) { export const dialog = function(options) {
const { message, showCancel, cancelButtonText, confirmButtonText, teleport } = const { message, showCancel, cancelButtonText, confirmButtonText, teleport, selection } =
Object.assign({}, defaultOptions, typeof options == 'string' ? { message: options } : options || {}) Object.assign({}, defaultOptions, typeof options == 'string' ? { message: options } : options || {})
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let app = createApp(Dialog, { let app = createApp(Dialog, {
@@ -32,6 +33,7 @@ export const dialog = function(options) {
instance.cancelButtonText = cancelButtonText instance.cancelButtonText = cancelButtonText
instance.confirmButtonText = confirmButtonText instance.confirmButtonText = confirmButtonText
instance.teleport = teleport instance.teleport = teleport
instance.selection = selection
// 挂载 // 挂载
document.getElementById('container').appendChild(instance.$el) document.getElementById('container').appendChild(instance.$el)
+3
View File
@@ -6,6 +6,9 @@ export default {
let theme = themes.find(theme => theme.id == state.setting.themeId) let theme = themes.find(theme => theme.id == state.setting.themeId)
return (theme && theme.className) || '' return (theme && theme.className) || ''
}, },
font(state) {
return state.setting.font
},
themes(state) { themes(state) {
return { return {
active: state.setting.themeId, active: state.setting.themeId,
+11 -1
View File
@@ -162,6 +162,7 @@ const handleGetLyric = function(musicInfo, retryedSource = [], originMusic) {
reqPromise = Promise.reject(err) reqPromise = Promise.reject(err)
} }
return reqPromise.catch(err => { return reqPromise.catch(err => {
// console.log(err)
if (!retryedSource.includes(musicInfo.source)) retryedSource.push(musicInfo.source) if (!retryedSource.includes(musicInfo.source)) retryedSource.push(musicInfo.source)
return this.dispatch('list/getOtherSource', originMusic).then(otherSource => { return this.dispatch('list/getOtherSource', originMusic).then(otherSource => {
console.log('find otherSource', otherSource) console.log('find otherSource', otherSource)
@@ -169,7 +170,7 @@ const handleGetLyric = function(musicInfo, retryedSource = [], originMusic) {
for (const item of otherSource) { for (const item of otherSource) {
if (retryedSource.includes(item.source)) continue if (retryedSource.includes(item.source)) continue
console.log('try toggle to: ', item.source, item.name, item.singer, item.interval) console.log('try toggle to: ', item.source, item.name, item.singer, item.interval)
return getLyric.call(this, item, retryedSource, originMusic) return handleGetLyric.call(this, item, retryedSource, originMusic)
} }
} }
return Promise.reject(err) return Promise.reject(err)
@@ -438,6 +439,14 @@ const actions = {
dispatch('startTask') dispatch('startTask')
return return
} }
if (err.message?.startsWith('Resume failed')) {
fs.unlink(downloadInfo.metadata.filePath, err => {
if (err) return commit('onError', { downloadInfo, errorMsg: '删除不匹配的文件失败:' + err.message })
dls[downloadInfo.key].start()
commit('setStatusText', { downloadInfo, text: '正在重试' })
})
return
}
if (err.code == 'ENOTFOUND') { if (err.code == 'ENOTFOUND') {
commit('onError', { downloadInfo, errorMsg: '链接失效' }) commit('onError', { downloadInfo, errorMsg: '链接失效' })
refreshUrl.call(_this, commit, downloadInfo, rootState.setting.download.isUseOtherSource) refreshUrl.call(_this, commit, downloadInfo, rootState.setting.download.isUseOtherSource)
@@ -561,6 +570,7 @@ const actions = {
filePath: path.join(rootState.setting.download.savePath, downloadInfo.metadata.fileName), filePath: path.join(rootState.setting.download.savePath, downloadInfo.metadata.fileName),
}) })
dl.updateSaveInfo(rootState.setting.download.savePath, downloadInfo.metadata.fileName) dl.updateSaveInfo(rootState.setting.download.savePath, downloadInfo.metadata.fileName)
if (tryNum[downloadInfo.key]) tryNum[downloadInfo.key] = 0
try { try {
await dl.start() await dl.start()
} catch (error) { } catch (error) {
+1
View File
@@ -32,6 +32,7 @@ const state = {
} }
const playMusic = () => { const playMusic = () => {
if (global.isPlayedStop) global.isPlayedStop = false
window.eventHub.emit(eventPlayerNames.playMusic) window.eventHub.emit(eventPlayerNames.playMusic)
} }
+4 -4
View File
@@ -68,7 +68,7 @@ const getters = {
const actions = { const actions = {
getTags({ state, rootState, commit }) { getTags({ state, rootState, commit }) {
let source = rootState.setting.songList.source let source = rootState.setting.songList.source
return music[source].songList.getTags().then(result => commit('setTags', { tags: result, source })) return music[source]?.songList.getTags().then(result => commit('setTags', { tags: result, source }))
}, },
getList({ state, rootState, commit }, page) { getList({ state, rootState, commit }, page) {
let source = rootState.setting.songList.source let source = rootState.setting.songList.source
@@ -79,7 +79,7 @@ const actions = {
if (state.list.list.length && state.list.key == key) return if (state.list.list.length && state.list.key == key) return
if (cache.has(key)) return Promise.resolve(cache.get(key)).then(result => commit('setList', { result, key, page })) if (cache.has(key)) return Promise.resolve(cache.get(key)).then(result => commit('setList', { result, key, page }))
commit('clearList') commit('clearList')
return music[source].songList.getList(sortId, tabId, page).then(result => commit('setList', { result, key, page })) return music[source]?.songList.getList(sortId, tabId, page).then(result => commit('setList', { result, key, page }))
}, },
getListDetail({ state, commit }, { id, source, page, isRefresh = false }) { getListDetail({ state, commit }, { id, source, page, isRefresh = false }) {
let key = `sdetail__${source}__${id}__${page}` let key = `sdetail__${source}__${id}__${page}`
@@ -89,7 +89,7 @@ const actions = {
return ( return (
cache.has(key) cache.has(key)
? Promise.resolve(cache.get(key)) ? Promise.resolve(cache.get(key))
: music[source].songList.getListDetail(id, page).then(result => ({ ...result, list: filterList(result.list) })) : music[source]?.songList.getListDetail(id, page).then(result => ({ ...result, list: filterList(result.list) }))
).then(result => { ).then(result => {
commit('setListDetail', { result, key, source, id, page }) commit('setListDetail', { result, key, source, id, page })
return result.list return result.list
@@ -102,7 +102,7 @@ const actions = {
if (isRefresh && cache.has(key)) cache.delete(key) if (isRefresh && cache.has(key)) cache.delete(key)
return cache.has(key) return cache.has(key)
? Promise.resolve(cache.get(key)) ? Promise.resolve(cache.get(key))
: music[source].songList.getListDetail(id, page).then(result => { : music[source]?.songList.getListDetail(id, page).then(result => {
cache.set(key, result) cache.set(key, result)
return result return result
}) })
+1
View File
@@ -3,6 +3,7 @@ export default {
state.setting.themeId = val state.setting.themeId = val
}, },
setSearchSource(state, { searchSource, tempSearchSource }) { setSearchSource(state, { searchSource, tempSearchSource }) {
console.log(searchSource, tempSearchSource)
if (searchSource != null) state.setting.search.searchSource = searchSource if (searchSource != null) state.setting.search.searchSource = searchSource
if (tempSearchSource != null) state.setting.search.tempSearchSource = tempSearchSource if (tempSearchSource != null) state.setting.search.tempSearchSource = tempSearchSource
}, },
+4 -3
View File
@@ -153,7 +153,7 @@ class Task extends EventEmitter {
__initDownload(response) { __initDownload(response) {
this.progress.total = parseInt(response.headers['content-length'] || 0) this.progress.total = parseInt(response.headers['content-length'] || 0)
let options = {} let options = {}
let isResumable = this.options.forceResume || response.headers['accept-ranges'] !== 'none' let isResumable = this.options.forceResume || response.headers['accept-ranges'] !== 'none' || (typeof response.headers['accept-ranges'] == 'string' && parseInt(response.headers['accept-ranges'].replace(/^bytes=(\d+)/, '$1')) > 0)
if (isResumable) { if (isResumable) {
options.flags = 'a' options.flags = 'a'
if (this.progress.downloaded) this.progress.total -= 10 if (this.progress.downloaded) this.progress.total -= 10
@@ -227,8 +227,9 @@ class Task extends EventEmitter {
if (this.resumeLastChunk) { if (this.resumeLastChunk) {
chunk = this.__handleDiffChunk(chunk) chunk = this.__handleDiffChunk(chunk)
if (!chunk) { if (!chunk) {
this.__handleError(new Error('Resume failed, response chunk does not match.')) this.__handleStop().finally(() => {
this.stop() this.__handleError(new Error('Resume failed, response chunk does not match.'))
})
return return
} }
} }
+65 -3
View File
@@ -93,7 +93,7 @@ const easeInOutQuad = (t, b, c, d) => {
t-- t--
return (-c / 2) * (t * (t - 2) - 1) + b return (-c / 2) * (t * (t - 2) - 1) + b
} }
const handleScroll = (element, to, duration = 300, fn = () => {}) => { const handleScrollY = (element, to, duration = 300, fn = () => {}) => {
if (!element) return fn() if (!element) return fn()
const start = element.scrollTop || element.scrollY || 0 const start = element.scrollTop || element.scrollY || 0
let cancel = false let cancel = false
@@ -148,10 +148,72 @@ export const scrollTo = (element, to, duration = 300, fn = () => {}, delay = 0)
} }
timeout = setTimeout(() => { timeout = setTimeout(() => {
timeout = null timeout = null
scrollCancelFn = handleScroll(element, to, duration, fn, delay) scrollCancelFn = handleScrollY(element, to, duration, fn, delay)
}, delay) }, delay)
} else { } else {
cancelFn = handleScroll(element, to, duration, fn, delay) cancelFn = handleScrollY(element, to, duration, fn, delay)
}
return cancelFn
}
const handleScrollX = (element, to, duration = 300, fn = () => {}) => {
if (!element) return fn()
const start = element.scrollLeft || element.scrollX || 0
let cancel = false
if (to > start) {
let maxScrollLeft = element.scrollWidth - element.clientWidth
if (to > maxScrollLeft) to = maxScrollLeft
} else if (to < start) {
if (to < 0) to = 0
} else return fn()
const change = to - start
const increment = 10
if (!change) return fn()
let currentTime = 0
let val
const animateScroll = () => {
currentTime += increment
val = parseInt(easeInOutQuad(currentTime, start, change, duration))
if (element.scrollTo) {
element.scrollTo(val, 0)
} else {
element.scrollLeft = val
}
if (currentTime < duration) {
if (cancel) return fn()
setTimeout(animateScroll, increment)
} else {
fn()
}
}
animateScroll()
return () => {
cancel = true
}
}
/**
* 设置滚动条位置
* @param {*} element 要设置滚动的容器 dom
* @param {*} to 滚动的目标位置
* @param {*} duration 滚动完成时间 ms
* @param {*} fn 滚动完成后的回调
* @param {*} delay 延迟执行时间
*/
export const scrollXTo = (element, to, duration = 300, fn = () => {}, delay = 0) => {
let cancelFn
let timeout
if (delay) {
let scrollCancelFn
cancelFn = () => {
timeout == null ? scrollCancelFn && scrollCancelFn() : clearTimeout(timeout)
}
timeout = setTimeout(() => {
timeout = null
scrollCancelFn = handleScrollX(element, to, duration, fn, delay)
}, delay)
} else {
cancelFn = handleScrollX(element, to, duration, fn, delay)
} }
return cancelFn return cancelFn
} }
@@ -193,4 +193,8 @@ export default {
} }
}) })
}, },
getDetailPageUrl(id) {
if (typeof id == 'string') id = id.replace('kg__', '')
return `https://www.kugou.com/yy/rank/home/1-${id}.html`
},
} }
+5
View File
@@ -718,6 +718,11 @@ export default {
} }
}) })
}, },
getDetailPageUrl(id) {
if (typeof id == 'string') id = id.replace('id_', '')
return `https://www.kugou.com/yy/special/single/${id}.html`
},
} }
// getList // getList
@@ -191,4 +191,8 @@ export default {
} }
}) })
}, },
// getDetailPageUrl(id) {
// return `http://www.kuwo.cn/rankList/${id}`
// },
} }
+7
View File
@@ -307,6 +307,13 @@ export default {
getTags() { getTags() {
return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({ tags, hotTag, source: 'kw' })) return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({ tags, hotTag, source: 'kw' }))
}, },
getDetailPageUrl(id) {
if (/^digest-/.test(id)) {
let result = id.split('__')
id = result[1]
}
return `http://www.kuwo.cn/playlist_detail/${id}`
},
} }
// getList // getList
+11 -2
View File
@@ -4,8 +4,7 @@ import { sizeFormate } from '../../index'
// const boardList = [{ id: 'mg__27553319', name: '咪咕尖叫新歌榜', bangid: '27553319' }, { id: 'mg__27186466', name: '咪咕尖叫热歌榜', bangid: '27186466' }, { id: 'mg__27553408', name: '咪咕尖叫原创榜', bangid: '27553408' }, { id: 'mg__23189800', name: '咪咕港台榜', bangid: '23189800' }, { id: 'mg__23189399', name: '咪咕内地榜', bangid: '23189399' }, { id: 'mg__19190036', name: '咪咕欧美榜', bangid: '19190036' }, { id: 'mg__23189813', name: '咪咕日韩榜', bangid: '23189813' }, { id: 'mg__23190126', name: '咪咕彩铃榜', bangid: '23190126' }, { id: 'mg__15140045', name: '咪咕KTV榜', bangid: '15140045' }, { id: 'mg__15140034', name: '咪咕网络榜', bangid: '15140034' }, { id: 'mg__23217754', name: 'MV榜', bangid: '23217754' }, { id: 'mg__23218151', name: '新专辑榜', bangid: '23218151' }, { id: 'mg__21958042', name: 'iTunes榜', bangid: '21958042' }, { id: 'mg__21975570', name: 'billboard榜', bangid: '21975570' }, { id: 'mg__22272815', name: '台湾Hito中文榜', bangid: '22272815' }, { id: 'mg__22272904', name: '中国TOP排行榜', bangid: '22272904' }, { id: 'mg__22272943', name: '韩国Melon榜', bangid: '22272943' }, { id: 'mg__22273437', name: '英国UK榜', bangid: '22273437' }] // const boardList = [{ id: 'mg__27553319', name: '咪咕尖叫新歌榜', bangid: '27553319' }, { id: 'mg__27186466', name: '咪咕尖叫热歌榜', bangid: '27186466' }, { id: 'mg__27553408', name: '咪咕尖叫原创榜', bangid: '27553408' }, { id: 'mg__23189800', name: '咪咕港台榜', bangid: '23189800' }, { id: 'mg__23189399', name: '咪咕内地榜', bangid: '23189399' }, { id: 'mg__19190036', name: '咪咕欧美榜', bangid: '19190036' }, { id: 'mg__23189813', name: '咪咕日韩榜', bangid: '23189813' }, { id: 'mg__23190126', name: '咪咕彩铃榜', bangid: '23190126' }, { id: 'mg__15140045', name: '咪咕KTV榜', bangid: '15140045' }, { id: 'mg__15140034', name: '咪咕网络榜', bangid: '15140034' }, { id: 'mg__23217754', name: 'MV榜', bangid: '23217754' }, { id: 'mg__23218151', name: '新专辑榜', bangid: '23218151' }, { id: 'mg__21958042', name: 'iTunes榜', bangid: '21958042' }, { id: 'mg__21975570', name: 'billboard榜', bangid: '21975570' }, { id: 'mg__22272815', name: '台湾Hito中文榜', bangid: '22272815' }, { id: 'mg__22272904', name: '中国TOP排行榜', bangid: '22272904' }, { id: 'mg__22272943', name: '韩国Melon榜', bangid: '22272943' }, { id: 'mg__22273437', name: '英国UK榜', bangid: '22273437' }]
const boardList = [{ id: 'mg__27553319', name: '尖叫新歌榜', bangid: '27553319' }, { id: 'mg__27186466', name: '尖叫热歌榜', bangid: '27186466' }, { id: 'mg__27553408', name: '尖叫原创榜', bangid: '27553408' }, { id: 'mg__23189800', name: '港台榜', bangid: '23189800' }, { id: 'mg__23189399', name: '内地榜', bangid: '23189399' }, { id: 'mg__19190036', name: '欧美榜', bangid: '19190036' }, { id: 'mg__23189813', name: '日韩榜', bangid: '23189813' }, { id: 'mg__23190126', name: '彩铃榜', bangid: '23190126' }, { id: 'mg__15140045', name: 'KTV榜', bangid: '15140045' }, { id: 'mg__15140034', name: '网络榜', bangid: '15140034' }, { id: 'mg__21958042', name: '美国iTunes榜', bangid: '21958042' }, { id: 'mg__21975570', name: '美国billboard榜', bangid: '21975570' }, { id: 'mg__22272815', name: '台湾Hito中文榜', bangid: '22272815' }, { id: 'mg__22272943', name: '韩国Melon榜', bangid: '22272943' }, { id: 'mg__22273437', name: '英国UK榜', bangid: '22273437' }] const boardList = [{ id: 'mg__27553319', name: '尖叫新歌榜', bangid: '27553319', webId: 'jianjiao_newsong' }, { id: 'mg__27186466', name: '尖叫热歌榜', bangid: '27186466', webId: 'jianjiao_hotsong' }, { id: 'mg__27553408', name: '尖叫原创榜', bangid: '27553408', webId: 'jianjiao_original' }, { id: 'mg__23189800', name: '港台榜', bangid: '23189800', webId: 'hktw' }, { id: 'mg__23189399', name: '内地榜', bangid: '23189399', webId: 'mainland' }, { id: 'mg__19190036', name: '欧美榜', bangid: '19190036', webId: 'eur_usa' }, { id: 'mg__23189813', name: '日韩榜', bangid: '23189813', webId: 'jpn_kor' }, { id: 'mg__23190126', name: '彩铃榜', bangid: '23190126', webId: 'coloring' }, { id: 'mg__15140045', name: 'KTV榜', bangid: '15140045', webId: 'ktv' }, { id: 'mg__15140034', name: '网络榜', bangid: '15140034', webId: 'network' }, { id: 'mg__21958042', name: '美国iTunes榜', bangid: '21958042', webId: 'itunes' }, { id: 'mg__21975570', name: '美国billboard榜', bangid: '21975570', webId: 'billboard' }, { id: 'mg__22272815', name: '台湾Hito中文榜', bangid: '22272815', webId: 'hito' }, { id: 'mg__22272943', name: '韩国Melon榜', bangid: '22272943', webId: 'mnet' }, { id: 'mg__22273437', name: '英国UK榜', bangid: '22273437', webId: 'uk' }]
export default { export default {
limit: 200, limit: 200,
list: [ list: [
@@ -216,4 +215,14 @@ export default {
} }
}) })
}, },
getDetailPageUrl(id) {
if (typeof id == 'string') id = id.replace('mg__', '')
for (const item of boardList) {
if (item.bangid == id) {
return `https://music.migu.cn/v3/music/top/${item.webId}`
}
}
return null
},
} }
+4
View File
@@ -355,6 +355,10 @@ export default {
getTags() { getTags() {
return this.getTag() return this.getTag()
}, },
getDetailPageUrl(id) {
return `https://music.migu.cn/v3/music/playlist/${id}`
},
} }
// getList // getList
@@ -237,4 +237,9 @@ export default {
}) })
}) })
}, },
getDetailPageUrl(id) {
if (typeof id == 'string') id = id.replace('tx__', '')
return `https://y.qq.com/n/ryqq/toplist/${id}`
},
} }
+7 -3
View File
@@ -32,7 +32,10 @@ export default {
}, },
handleResult(rawList) { handleResult(rawList) {
// console.log(rawList) // console.log(rawList)
return rawList.map(item => { const list = []
rawList.forEach(item => {
if (!item.strMediaMid) return
let types = [] let types = []
let _types = {} let _types = {}
if (item.size128 !== 0) { if (item.size128 !== 0) {
@@ -64,7 +67,7 @@ export default {
} }
} }
// types.reverse() // types.reverse()
return { list.push({
singer: this.getSinger(item.singer), singer: this.getSinger(item.singer),
name: item.songname, name: item.songname,
albumName: item.albumname, albumName: item.albumname,
@@ -83,8 +86,9 @@ export default {
types, types,
_types, _types,
typeUrl: {}, typeUrl: {},
} })
}) })
return list
}, },
search(str, page = 1, { limit } = {}) { search(str, page = 1, { limit } = {}) {
if (limit == null) limit = this.limit if (limit == null) limit = this.limit
+4
View File
@@ -292,6 +292,10 @@ export default {
getTags() { getTags() {
return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({ tags, hotTag, source: 'tx' })) return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({ tags, hotTag, source: 'tx' }))
}, },
getDetailPageUrl(id) {
return `https://y.qq.com/n/ryqq/playlist/${id}`
},
} }
// getList // getList
@@ -199,4 +199,9 @@ export default {
source: 'wy', source: 'wy',
} }
}, },
getDetailPageUrl(id) {
if (typeof id == 'string') id = id.replace('wy__', '')
return `https://music.163.com/#/discover/toplist?id=${id}`
},
} }
+1 -1
View File
@@ -53,7 +53,7 @@ export default songmid => {
if (body.code !== 200 || !body?.lrc?.lyric) return Promise.reject(new Error('Get lyric failed')) if (body.code !== 200 || !body?.lrc?.lyric) return Promise.reject(new Error('Get lyric failed'))
return { return {
lyric: body.lrc.lyric, lyric: body.lrc.lyric,
tlyric: body.tlyric.lyric, tlyric: body.tlyric?.lyric ?? '',
// lxlyric: parseLyric(body.klyric.lyric), // lxlyric: parseLyric(body.klyric.lyric),
} }
}) })
+4
View File
@@ -289,6 +289,10 @@ export default {
getTags() { getTags() {
return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({ tags, hotTag, source: 'wy' })) return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({ tags, hotTag, source: 'wy' }))
}, },
getDetailPageUrl(id) {
return `https://music.163.com/#/playlist?id=${id}`
},
} }
// getList // getList
+93
View File
@@ -0,0 +1,93 @@
import { ref, computed } from '@renderer/utils/vueTools'
import { rendererSend, rendererOn, NAMES } from '@common/ipc'
import { isPlay } from '@renderer/core/share/player'
import store from '@renderer/store'
import { player as eventPlayerNames } from '@renderer/event/names'
global.isPlayedStop = false
const time = ref(-1)
const timeoutTools = {
inited: false,
isRunning: false,
timeout: null,
time: -1,
id: 'play__stop__timeout',
exit() {
const setting = store.getters.setting
global.isPlayedStop = true
if (!setting.player.waitPlayEndStop && isPlay.value) {
window.eventHub.emit(eventPlayerNames.setPause)
}
},
clearTimeout() {
rendererSend(NAMES.mainWindow.interval_cancel, this.id)
if (!this.isRunning) return
this.time = -1
time.value = -1
this.isRunning = false
},
start(_time) {
this.clearTimeout()
this.time = _time
time.value = _time
this.isRunning = true
rendererSend(NAMES.mainWindow.interval, {
time: 1000,
id: this.id,
})
},
init() {
if (this.inited) return
this.clearTimeout()
rendererOn(NAMES.mainWindow.interval_callback, (event, id) => {
if (id !== this.id) return
if (this.time > 0) {
this.time--
time.value--
} else {
this.clearTimeout()
this.exit()
}
})
this.inited = true
},
}
export const init = () => {
timeoutTools.init()
}
export const startTimeoutStop = time => {
if (global.isPlayedStop) global.isPlayedStop = false
timeoutTools.start(time)
}
export const stopTimeoutStop = () => {
if (global.isPlayedStop) global.isPlayedStop = false
timeoutTools.clearTimeout()
}
const formatTime = time => {
// let d = parseInt(time / 86400)
// d = d ? d.toString() + ':' : ''
// time = time % 86400
let h = parseInt(time / 3600)
h = h ? h.toString() + ':' : ''
time = time % 3600
const m = parseInt(time / 60).toString().padStart(2, '0')
const s = parseInt(time % 60).toString().padStart(2, '0')
return `${h}${m}:${s}`
}
export const useTimeout = () => {
const timeLabel = computed(() => {
return time.value > 0 ? formatTime(time.value) : ''
})
return {
time,
timeLabel,
}
}
+26 -2
View File
@@ -13,6 +13,17 @@ export const setUserApi = source => {
}) })
} }
export const onShowUserApiUpdateAlert = callback => {
rendererOn(NAMES.mainWindow.user_api_show_update_alert, callback)
return () => {
rendererOff(callback)
}
}
export const setAllowShowUserApiUpdateAlert = (id, enable) => {
return rendererInvoke(NAMES.mainWindow.user_api_set_allow_update_alert, { id, enable })
}
export const saveMyList = data => { export const saveMyList = data => {
rendererSend(NAMES.mainWindow.save_playlist, { rendererSend(NAMES.mainWindow.save_playlist, {
type: 'myList', type: 'myList',
@@ -105,7 +116,7 @@ export const getSearchHistoryList = () => {
} }
export const onUserApiStatus = callback => { export const onUserApiStatus = callback => {
rendererOn(NAMES.mainWindow.user_api_status, (event, { status, message, apiInfo }) => callback({ status, message, apiInfo })) rendererOn(NAMES.mainWindow.user_api_status, callback)
return () => { return () => {
rendererOff(callback) rendererOff(callback)
} }
@@ -133,7 +144,7 @@ export const setDesktopLyricInfo = (type, data, info) => {
}) })
} }
export const onGetDesktopLyricInfo = callback => { export const onGetDesktopLyricInfo = callback => {
rendererOn(NAMES.mainWindow.get_lyric_info, (event, info) => callback(event, info)) rendererOn(NAMES.mainWindow.get_lyric_info, callback)
return () => { return () => {
rendererOff(callback) rendererOff(callback)
} }
@@ -299,3 +310,16 @@ export const hotKeySetConfig = (config) => {
export const hotKeyGetStatus = () => { export const hotKeyGetStatus = () => {
return rendererInvoke(NAMES.hotKey.status) return rendererInvoke(NAMES.hotKey.status)
} }
export const onTaskbarThumbarClick = callback => {
rendererOn(NAMES.mainWindow.taskbar_on_thumbar_button_click, callback)
return () => {
rendererOff(callback)
}
}
export const setTaskbarThumbnailClip = (clip) => {
return rendererInvoke(NAMES.mainWindow.taskbar_set_thumbnail_clip, clip)
}
export const setTaskbarThumbarButtons = (buttons) => {
rendererSend(NAMES.mainWindow.taskbar_set_thumbar_buttons, buttons)
}
+12 -4
View File
@@ -16,6 +16,7 @@
<material-online-list <material-online-list
ref="songList" ref="songList"
@show-menu="hideListsMenu" @show-menu="hideListsMenu"
@play-list="handlePlayList"
@toggle-page="handleGetList" @toggle-page="handleGetList"
:rowWidth="{r1: '5%', r2: 'auto', r3: '22%', r4: '22%', r5: '9%', r6: '15%'}" :rowWidth="{r1: '5%', r2: 'auto', r3: '22%', r4: '22%', r5: '9%', r6: '15%'}"
:page="page" :page="page"
@@ -193,7 +194,6 @@ export default {
case 'play': case 'play':
this.playSongListDetail({ this.playSongListDetail({
boardId: board.id, boardId: board.id,
list: [...this.list],
id, id,
}) })
break break
@@ -220,12 +220,13 @@ export default {
sourceListId: `board__${boardId}`, sourceListId: `board__${boardId}`,
}) })
}, },
async playSongListDetail({ boardId, id, list }) { async playSongListDetail({ boardId, id, index = 0 }) {
let isPlayingList = false let isPlayingList = false
const list = this.tabId == boardId ? [...this.list] : null
if (list?.length) { if (list?.length) {
this.setTempList({ this.setTempList({
list, list,
index: 0, index,
id, id,
}) })
isPlayingList = true isPlayingList = true
@@ -242,11 +243,18 @@ export default {
} else { } else {
this.setTempList({ this.setTempList({
list: fullList, list: fullList,
index: 0, index,
id, id,
}) })
} }
}, },
handlePlayList(index) {
this.playSongListDetail({
boardId: this.tabId,
id: `board__${this.source}__${this.tabId}`,
index,
})
},
}, },
} }
</script> </script>
+40 -25
View File
@@ -2,7 +2,7 @@
div(:class="$style.search") div(:class="$style.search")
//- transition //- transition
div(:class="$style.header") div(:class="$style.header")
base-tab(:class="$style.tab" :list="sources" align="left" item-key="id" item-name="name" v-model="searchSourceId") base-tab(:class="$style.tab" :list="sources" align="left" item-key="id" item-name="name" @change="handleSourceChange" v-model="searchSourceId")
div(:class="$style.main") div(:class="$style.main")
div(:class="$style.list" v-show="isLoading || listInfo.list.length") div(:class="$style.list" v-show="isLoading || listInfo.list.length")
div.thead(:class="$style.thead") div.thead(:class="$style.thead")
@@ -109,12 +109,28 @@ export default {
isLoading: false, isLoading: false,
} }
}, },
beforeRouteUpdate(to, from, next) { beforeRouteUpdate(to, from) {
if (to.query.text === undefined) return if (to.query.source && (this.sourceList[to.query.source] || to.query.source == 'all')) {
this.text = to.query.text if (this.setting.search.searchSource != to.query.source) {
this.page = 1 this.setSearchSource({
this.handleSearch(this.text, this.page) searchSource: to.query.source,
next() })
}
if (this.searchSourceId != to.query.source) {
this.searchSourceId = to.query.source
}
this.$nextTick(() => {
this.handleGetHotSearch()
})
}
if (to.query.text != null && this.text != to.query.text) {
this.text = to.query.text
}
this.$nextTick(() => {
this.page = 1
this.handleSearch(this.text, this.page)
})
}, },
created() { created() {
this.listenEvent() this.listenEvent()
@@ -124,18 +140,20 @@ export default {
}, },
mounted() { mounted() {
// console.log('mounted') // console.log('mounted')
if (this.$route.query.source && (this.sourceList[this.$route.query.source] || this.$route.query.source == 'all')) {
// 处理搜索源不存在时页面报错的问题 this.setSearchSource({
if (!this.sourceList[this.setting.search.searchSource] && this.setting.search.searchSource != 'all') { searchSource: this.$route.query.source,
})
} else if (!this.sourceList[this.setting.search.searchSource] && this.setting.search.searchSource != 'all') { // 处理搜索源不存在时页面报错的问题
this.setSearchSource({ this.setSearchSource({
searchSource: 'kw', searchSource: 'kw',
}) })
} }
this.searchSourceId = this.setting.search.searchSource this.searchSourceId = this.setting.search.searchSource
if (this.$route.query.text === undefined) { if (this.$route.query.text == null) {
this.text = this.$store.getters['search/searchText'] this.text = this.$store.getters['search/searchText']
this.page = this.listInfo.page this.page = this.listInfo.page
} else if (this.$route.query.text === '') { } else if (this.$route.query.text == '') {
this.clearList() this.clearList()
} else { } else {
this.text = this.$route.query.text this.text = this.$route.query.text
@@ -156,18 +174,6 @@ export default {
'listInfo.list'() { 'listInfo.list'() {
this.removeAllSelect() this.removeAllSelect()
}, },
searchSourceId(n) {
if (n === this.setting.search.searchSource) return
if (this.text !== '') this.isLoading = true
this.$nextTick(() => {
this.page = 1
this.handleSearch(this.text, this.page)
this.handleGetHotSearch()
})
this.setSearchSource({
searchSource: n,
})
},
}, },
computed: { computed: {
...mapGetters(['userInfo', 'setting']), ...mapGetters(['userInfo', 'setting']),
@@ -417,7 +423,7 @@ export default {
this.getHotSearch(this.setting.search.searchSource) this.getHotSearch(this.setting.search.searchSource)
}, },
handleNoitemSearch(text) { handleNoitemSearch(text) {
this.$router.push({ this.$router.replace({
path: 'search', path: 'search',
query: { query: {
text, text,
@@ -512,6 +518,15 @@ export default {
break break
} }
}, },
handleSourceChange(source) {
this.$router.replace({
path: 'search',
query: {
text: this.text,
source,
},
})
},
}, },
} }
</script> </script>
+49 -3
View File
@@ -19,7 +19,7 @@
<span :class="$style.listsLabel">{{loveList.name}}</span> <span :class="$style.listsLabel">{{loveList.name}}</span>
</li> </li>
<li class="user-list" <li class="user-list"
:class="[$style.listsItem, {[$style.active]:item.id == listId}, {[$style.clicked]: listsData.rightClickItemIndex == index}, {[$style.fetching]: fetchingListStatus[item.id]}]" :data-index="index" :class="[$style.listsItem, {[$style.active]: item.id == listId}, {[$style.clicked]: listsData.rightClickItemIndex == index}, {[$style.fetching]: fetchingListStatus[item.id]}]" :data-index="index"
@contextmenu="handleListsItemRigthClick($event, index)" :tips="item.name" v-for="(item, index) in userLists" :key="item.id" @contextmenu="handleListsItemRigthClick($event, index)" :tips="item.name" v-for="(item, index) in userLists" :key="item.id"
> >
<span :class="$style.listsLabel" @click="handleListToggle(item.id, index + 2)">{{item.name}}</span> <span :class="$style.listsLabel" @click="handleListToggle(item.id, index + 2)">{{item.name}}</span>
@@ -41,7 +41,7 @@
<script> <script>
import { mapMutations, mapActions } from 'vuex' import { mapMutations, mapActions } from 'vuex'
import { openSaveDir, saveLxConfigFile, selectDir, readLxConfigFile, filterFileName } from '@renderer/utils' import { openSaveDir, saveLxConfigFile, selectDir, readLxConfigFile, filterFileName, openUrl } from '@renderer/utils'
import musicSdk from '@renderer/utils/music' import musicSdk from '@renderer/utils/music'
import DuplicateMusicModal from './DuplicateMusicModal' import DuplicateMusicModal from './DuplicateMusicModal'
import ListSortModal from './ListSortModal' import ListSortModal from './ListSortModal'
@@ -97,6 +97,7 @@ export default {
rename: true, rename: true,
duplicate: true, duplicate: true,
sort: true, sort: true,
sourceDetail: true,
import: true, import: true,
export: true, export: true,
sync: false, sync: false,
@@ -141,6 +142,11 @@ export default {
action: 'duplicate', action: 'duplicate',
disabled: !this.listsData.itemMenuControl.duplicate, disabled: !this.listsData.itemMenuControl.duplicate,
}, },
{
name: this.$t('lists__source_detail'),
action: 'sourceDetail',
disabled: !this.listsData.itemMenuControl.sourceDetail,
},
{ {
name: this.$t('lists__import'), name: this.$t('lists__import'),
action: 'import', action: 'import',
@@ -197,8 +203,17 @@ export default {
...mapActions('leaderboard', { ...mapActions('leaderboard', {
getBoardListAll: 'getListAll', getBoardListAll: 'getListAll',
}), }),
handle_key_mod_down() { handle_key_mod_down(event) {
if (!this.keyEvent.isModDown) { if (!this.keyEvent.isModDown) {
// console.log(event)
switch (event.event.target.tagName) {
case 'INPUT':
case 'SELECT':
case 'TEXTAREA':
return
default: if (event.event.target.isContentEditable) return
}
this.keyEvent.isModDown = true this.keyEvent.isModDown = true
this.setDisabledSort(false) this.setDisabledSort(false)
const dom_target = this.dom_lists_list.querySelector('.' + this.$style.editing) const dom_target = this.dom_lists_list.querySelector('.' + this.$style.editing)
@@ -279,6 +294,7 @@ export default {
break break
} }
this.listsData.itemMenuControl.sort = !!getList(this.getTargetListInfo(index)?.id).length this.listsData.itemMenuControl.sort = !!getList(this.getTargetListInfo(index)?.id).length
this.listsData.itemMenuControl.sourceDetail = this.assertSupportDetail(source, index)
this.listsData.rightClickItemIndex = index this.listsData.rightClickItemIndex = index
this.listsData.menuLocation.x = event.currentTarget.offsetLeft + event.offsetX this.listsData.menuLocation.x = event.currentTarget.offsetLeft + event.offsetX
this.listsData.menuLocation.y = event.currentTarget.offsetTop + event.offsetY - this.dom_lists_list.scrollTop this.listsData.menuLocation.y = event.currentTarget.offsetTop + event.offsetY - this.dom_lists_list.scrollTop
@@ -313,6 +329,9 @@ export default {
this.selectedSortListInfo = this.getTargetListInfo(index) this.selectedSortListInfo = this.getTargetListInfo(index)
this.isShowListSortModal = true this.isShowListSortModal = true
break break
case 'sourceDetail':
this.openSourceDetailPage(index)
break
case 'import': case 'import':
this.handleImportList(index) this.handleImportList(index)
break break
@@ -373,6 +392,33 @@ export default {
} }
return list return list
}, },
assertSupportDetail(source, index) {
if (source) {
const { sourceListId } = this.userLists[index]
if (sourceListId) {
if (/board__/.test(sourceListId)) {
// const id = sourceListId.replace(/board__/, '')
return !!musicSdk[source]?.leaderboard?.getDetailPageUrl
} else {
return !!musicSdk[source]?.songList?.getDetailPageUrl
}
}
}
return false
},
openSourceDetailPage(index) {
const { source, sourceListId } = this.userLists[index]
if (!sourceListId) return
let url
if (/board__/.test(sourceListId)) {
const id = sourceListId.replace(/board__/, '')
url = musicSdk[source].leaderboard.getDetailPageUrl(id)
} else {
url = musicSdk[source].songList.getDetailPageUrl(sourceListId)
}
if (!url) return
openUrl(url)
},
handleExportList(index) { handleExportList(index) {
const list = this.getTargetListInfo(index) const list = this.getTargetListInfo(index)
if (!list) return if (!list) return
@@ -0,0 +1,175 @@
<template lang="pug">
material-modal(:show="modelValue" bg-close @close="handleCloseModal" @after-enter="$refs.dom_input.focus()" teleport="#view")
main(:class="$style.main")
h2 {{$t('play_timeout')}}
div(:class="$style.content")
div(:class="[$style.row, $style.inputGroup]")
base-input(:class="$style.input" ref="dom_input" v-model="time" type="number")
p(:class="$style.inputLabel") {{$t('play_timeout_unit')}}
div(:class="$style.row")
base-checkbox(id="play_timeout_end" v-model="currentStting.player.waitPlayEndStop" :label="$t('play_timeout_end')")
div(:class="[$style.row, $style.tip, { [$style.show]: !!timeLabel }]")
p {{$t('play_timeout_tip', { time: timeLabel })}}
div(:class="$style.footer")
base-btn(:class="$style.footerBtn" @click="handleCancel") {{$t(timeLabel ? 'play_timeout_stop' : 'play_timeout_close')}}
base-btn(:class="$style.footerBtn" @click="handleConfirm") {{$t(timeLabel ? 'play_timeout_update' : 'play_timeout_confirm')}}
</template>
<script>
import { currentStting } from '../setting'
import { useTimeout, startTimeoutStop, stopTimeoutStop } from '@renderer/utils/timeoutStop'
import { ref } from '@renderer/utils/vueTools'
const MAX_MIN = 1440
const rxp = /([1-9]\d*)/
export default {
props: {
modelValue: {
type: Boolean,
default: false,
},
},
setup(props, { emit }) {
const { timeLabel } = useTimeout()
const time = ref(currentStting.value.player.waitPlayEndStopTime)
const handleCloseModal = () => {
emit('update:modelValue', false)
}
const handleCancel = () => {
if (timeLabel.value) {
stopTimeoutStop()
}
handleCloseModal()
}
const verify = () => {
const orgText = time.value
let text = time.value
if (rxp.test(text)) {
text = RegExp.$1
if (parseInt(text) > MAX_MIN) {
text = text.substring(0, text.length - 1)
}
} else {
text = ''
}
time.value = text
return text && orgText == text ? parseInt(text) : ''
}
const handleConfirm = () => {
let time = verify()
if (time == '') return
currentStting.value.player.waitPlayEndStopTime = time
startTimeoutStop(time * 60)
handleCloseModal()
}
return {
currentStting,
timeLabel,
time,
handleCloseModal,
handleCancel,
handleConfirm,
}
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.main {
padding: 15px;
max-width: 530px;
min-width: 280px;
display: flex;
flex-flow: column nowrap;
justify-content: center;
min-height: 0;
// max-height: 100%;
// overflow: hidden;
h2 {
font-size: 16px;
color: @color-theme_2-font;
line-height: 1.3;
text-align: center;
}
}
.content {
padding-top: 15px;
font-size: 14px;
}
.row {
padding-top: 5px;
}
.inputGroup {
display: flex;
align-items: center;
}
.input {
flex: auto;
}
.inputLabel {
flex: none;
margin-left: 10px;
}
.tip {
visibility: hidden;
&.show {
visibility: visible;
}
}
.footer {
margin-top: 20px;
display: flex;
flex-flow: row nowrap;
}
.footerBtn {
flex: auto;
height: 36px;
line-height: 36px;
padding: 0 10px !important;
width: 150px;
.mixin-ellipsis-1;
+ .footerBtn {
margin-left: 15px;
}
}
.ruleLink {
.mixin-ellipsis-1;
}
each(@themes, {
:global(#root.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
}
}
.listItem {
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&.active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
h3 {
color: ~'@{color-@{value}-theme_2-font}';
}
p {
color: ~'@{color-@{value}-theme_2-font-label}';
}
}
.noitem {
color: ~'@{color-@{value}-theme_2-font-label}';
}
}
})
</style>
@@ -9,12 +9,15 @@ dd
label {{$t('theme_' + theme.className)}} label {{$t('theme_' + theme.className)}}
dd dd
.gap-top.top div
base-checkbox(id="setting_show_animate" v-model="currentStting.isShowAnimation" :label="$t('setting__basic_show_animation')") .gap-top.top
.gap-top base-checkbox(id="setting_show_animate" v-model="currentStting.isShowAnimation" :label="$t('setting__basic_show_animation')")
base-checkbox(id="setting_animate" v-model="currentStting.randomAnimate" :label="$t('setting__basic_animation')") .gap-top
.gap-top base-checkbox(id="setting_animate" v-model="currentStting.randomAnimate" :label="$t('setting__basic_animation')")
base-checkbox(id="setting_to_tray" v-model="currentStting.tray.isShow" :label="$t('setting__basic_to_tray')") .gap-top
base-checkbox(id="setting_to_tray" v-model="currentStting.tray.isShow" :label="$t('setting__basic_to_tray')")
p.gap-top
base-btn.btn(min @click="isShowPlayTimeoutModal = true") {{$t('setting__play_timeout')}} {{ timeLabel ? ` (${timeLabel})` : '' }}
dd(:tips="$t('setting__basic_source_title')") dd(:tips="$t('setting__basic_source_title')")
h3#basic_source {{$t('setting__basic_source')}} h3#basic_source {{$t('setting__basic_source')}}
@@ -42,12 +45,17 @@ dd(:tips="$t('setting__basic_sourcename_title')")
div div
base-checkbox.gap-left(v-for="item in sourceNameTypes" :key="item.id" :id="`setting_abasic_sourcename_${item.id}`" base-checkbox.gap-left(v-for="item in sourceNameTypes" :key="item.id" :id="`setting_abasic_sourcename_${item.id}`"
name="setting_basic_sourcename" need v-model="currentStting.sourceNameType" :value="item.id" :label="item.label") name="setting_basic_sourcename" need v-model="currentStting.sourceNameType" :value="item.id" :label="item.label")
dd dd
h3#basic_control_btn_position {{$t('setting__basic_control_btn_position')}} h3#basic_control_btn_position {{$t('setting__basic_control_btn_position')}}
div div
base-checkbox.gap-left(v-for="item in controlBtnPositionList" :key="item.id" :id="`setting_basic_control_btn_position_${item.id}`" base-checkbox.gap-left(v-for="item in controlBtnPositionList" :key="item.id" :id="`setting_basic_control_btn_position_${item.id}`"
name="setting_basic_control_btn_position" need v-model="currentStting.controlBtnPosition" :value="item.id" :label="item.name") name="setting_basic_control_btn_position" need v-model="currentStting.controlBtnPosition" :value="item.id" :label="item.name")
dd
h3#basic_font {{$t('setting__basic_font')}}
div
base-selection.gap-teft(:list="fontList" v-model="currentStting.font" item-key="id" item-name="label")
play-timeout-modal(v-model="isShowPlayTimeoutModal")
user-api-modal(v-model="isShowUserApiModal") user-api-modal(v-model="isShowUserApiModal")
</template> </template>
@@ -58,12 +66,17 @@ import { langList } from '@/lang'
import { currentStting } from '../setting' import { currentStting } from '../setting'
import { setWindowSize } from '@renderer/utils' import { setWindowSize } from '@renderer/utils'
import apiSourceInfo from '@renderer/utils/music/api-source-info' import apiSourceInfo from '@renderer/utils/music/api-source-info'
import { useTimeout } from '@renderer/utils/timeoutStop'
import { getSystemFonts } from '@renderer/utils/tools'
import PlayTimeoutModal from './PlayTimeoutModal'
import UserApiModal from './UserApiModal' import UserApiModal from './UserApiModal'
export default { export default {
name: 'SettingBasic', name: 'SettingBasic',
components: { components: {
PlayTimeoutModal,
UserApiModal, UserApiModal,
}, },
setup() { setup() {
@@ -81,6 +94,9 @@ export default {
apiSource.value = visible apiSource.value = visible
}) })
const isShowPlayTimeoutModal = ref(false)
const { timeLabel } = useTimeout()
const isShowUserApiModal = ref(false) const isShowUserApiModal = ref(false)
const getApiStatus = () => { const getApiStatus = () => {
let status let status
@@ -134,16 +150,26 @@ export default {
] ]
}) })
const systemFontList = ref([])
const fontList = computed(() => {
return [{ id: '', label: t('setting__desktop_lyric_font_default') }, ...systemFontList.value]
})
getSystemFonts().then(fonts => {
systemFontList.value = fonts.map(f => ({ id: f, label: f.replace(/(^"|"$)/g, '') }))
})
return { return {
currentStting, currentStting,
themes, themes,
isShowPlayTimeoutModal,
timeLabel,
apiSources, apiSources,
isShowUserApiModal, isShowUserApiModal,
windowSizeList, windowSizeList,
langList, langList,
sourceNameTypes, sourceNameTypes,
controlBtnPositionList, controlBtnPositionList,
fontList,
} }
}, },
} }
@@ -5,6 +5,8 @@ dd
base-checkbox(id="setting_list_showSource_enable" v-model="currentStting.list.isShowSource" :label="$t('setting__list_source')") base-checkbox(id="setting_list_showSource_enable" v-model="currentStting.list.isShowSource" :label="$t('setting__list_source')")
.gap-top .gap-top
base-checkbox(id="setting_list_scroll_enable" v-model="currentStting.list.isSaveScrollLocation" :label="$t('setting__list_scroll')") base-checkbox(id="setting_list_scroll_enable" v-model="currentStting.list.isSaveScrollLocation" :label="$t('setting__list_scroll')")
.gap-top
base-checkbox(id="setting_list_clickAction_enable" v-model="currentStting.list.isClickPlayList" :label="$t('setting__list_click_action')")
dd(:tips="$t('setting__basic_sourcename_title')") dd(:tips="$t('setting__basic_sourcename_title')")
h3#list_addMusicLocationType {{$t('setting__list_add_music_location_type')}} h3#list_addMusicLocationType {{$t('setting__list_add_music_location_type')}}
div div
@@ -7,6 +7,8 @@ material-modal(:show="modelValue" bg-close @close="handleClose" teleport="#view"
div(:class="$style.listLeft") div(:class="$style.listLeft")
h3 {{api.name}} h3 {{api.name}}
p {{api.description}} p {{api.description}}
div
base-checkbox(:class="$style.checkbox" :id="`user_api_${api.id}`" v-model="api.allowShowUpdateAlert" @change="handleChangeAllowUpdateAlert(api, $event)" :label="$t('user_api__allow_show_update_alert')")
base-btn(:class="$style.listBtn" outline :tips="$t('user_api__btn_remove')" @click.stop="handleRemove(index)") base-btn(:class="$style.listBtn" outline :tips="$t('user_api__btn_remove')" @click.stop="handleRemove(index)")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 212.982 212.982' space='preserve' v-once) svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 212.982 212.982' space='preserve' v-once)
use(xlink:href='#icon-delete') use(xlink:href='#icon-delete')
@@ -29,6 +31,7 @@ import { promises as fsPromises } from 'fs'
import { selectDir, openUrl } from '@renderer/utils' import { selectDir, openUrl } from '@renderer/utils'
import apiSourceInfo from '@renderer/utils/music/api-source-info' import apiSourceInfo from '@renderer/utils/music/api-source-info'
import { userApi, apiSource } from '@renderer/core/share' import { userApi, apiSource } from '@renderer/core/share'
import { setAllowShowUserApiUpdateAlert } from '@renderer/utils/tools'
export default { export default {
props: { props: {
@@ -50,6 +53,13 @@ export default {
}, },
methods: { methods: {
handleImport() { handleImport() {
if (this.userApi.list.length > 20) {
this.$dialog({
message: this.$t('user_api__max_tip'),
confirmButtonText: this.$t('ok'),
})
return
}
selectDir({ selectDir({
title: this.$t('user_api__import_file'), title: this.$t('user_api__import_file'),
properties: ['openFile'], properties: ['openFile'],
@@ -84,6 +94,9 @@ export default {
handleOpenUrl(url) { handleOpenUrl(url) {
openUrl(url) openUrl(url)
}, },
handleChangeAllowUpdateAlert(api, enable) {
setAllowShowUserApiUpdateAlert(api.id, enable)
},
}, },
} }
</script> </script>
@@ -114,6 +127,12 @@ export default {
color: @color-theme; color: @color-theme;
} }
.checkbox {
margin-top: 3px;
font-size: 14px;
opacity: .86;
}
.content { .content {
flex: auto; flex: auto;
min-height: 100px; min-height: 100px;
+3
View File
@@ -8,6 +8,8 @@ export const currentStting = ref({
volume: 1, volume: 1,
mediaDeviceId: 'default', mediaDeviceId: 'default',
isMediaDeviceRemovedStopPlay: false, isMediaDeviceRemovedStopPlay: false,
waitPlayEndStop: true,
waitPlayEndStopTime: 0,
}, },
desktopLyric: { desktopLyric: {
enable: false, enable: false,
@@ -73,6 +75,7 @@ export const currentStting = ref({
langId: 'cns', langId: 'cns',
themeId: 0, themeId: 0,
sourceId: 0, sourceId: 0,
font: '',
isShowAnimation: true, isShowAnimation: true,
randomAnimate: true, randomAnimate: true,
isAgreePact: false, isAgreePact: false,
+5 -5
View File
@@ -11,10 +11,10 @@ div(:class="$style.container")
h3(:title="listDetail.info.name || selectListInfo.name") {{listDetail.info.name || selectListInfo.name}} h3(:title="listDetail.info.name || selectListInfo.name") {{listDetail.info.name || selectListInfo.name}}
p(:title="listDetail.info.desc || selectListInfo.desc") {{listDetail.info.desc || selectListInfo.desc}} p(:title="listDetail.info.desc || selectListInfo.desc") {{listDetail.info.desc || selectListInfo.desc}}
div(:class="$style.songListHeaderRight") div(:class="$style.songListHeaderRight")
base-btn(:class="$style.headerRightBtn" :disabled="detailLoading" @click="playSongListDetail") {{$t('list__play')}} base-btn(:class="$style.headerRightBtn" :disabled="detailLoading" @click="playSongListDetail()") {{$t('list__play')}}
base-btn(:class="$style.headerRightBtn" :disabled="detailLoading" @click="addSongListDetail") {{$t('list__collect')}} base-btn(:class="$style.headerRightBtn" :disabled="detailLoading" @click="addSongListDetail") {{$t('list__collect')}}
base-btn(:class="$style.headerRightBtn" @click="hideListDetail") {{$t('back')}} base-btn(:class="$style.headerRightBtn" @click="hideListDetail") {{$t('back')}}
material-online-list(ref="songList" @toggle-page="handleToggleListDetailPage" :page="listDetail.page" :limit="listDetail.limit" :total="listDetail.total" material-online-list(ref="songList" @play-list="playSongListDetail" @toggle-page="handleToggleListDetailPage" :page="listDetail.page" :limit="listDetail.limit" :total="listDetail.total"
:list="listDetail.list" :noItem="isGetDetailFailed ? $t('list__load_failed') : $t('list__loading')") :list="listDetail.list" :noItem="isGetDetailFailed ? $t('list__load_failed') : $t('list__loading')")
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut") transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
div(:class="$style.songListContainer" v-show="!isVisibleListDetail") div(:class="$style.songListContainer" v-show="!isVisibleListDetail")
@@ -315,14 +315,14 @@ export default {
sourceListId: this.listDetail.id, sourceListId: this.listDetail.id,
}) })
}, },
async playSongListDetail() { async playSongListDetail(index = 0) {
if (!this.listDetail.info.name) return if (!this.listDetail.info.name) return
const id = `${this.listDetail.source}__${this.listDetail.id}` const id = `${this.listDetail.source}__${this.listDetail.id}`
let isPlayingList = false let isPlayingList = false
if (this.listDetail.list?.length) { if (this.listDetail.list?.length) {
this.setTempList({ this.setTempList({
list: [...this.listDetail.list], list: [...this.listDetail.list],
index: 0, index,
id, id,
}) })
isPlayingList = true isPlayingList = true
@@ -339,7 +339,7 @@ export default {
} else { } else {
this.setTempList({ this.setTempList({
list, list,
index: 0, index,
id, id,
}) })
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 943 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

Some files were not shown because too many files have changed in this diff Show More