Merge branch 'dev' of github.com:lyswhut/lx-music-desktop into dev

pull/733/head
lyswhut 2021-11-09 19:34:55 +08:00
commit 6f72da87b1
121 changed files with 5005 additions and 4683 deletions

View File

@ -22,5 +22,6 @@
}, },
"settings": { "settings": {
"html/html-extensions": [".html", ".vue"] "html/html-extensions": [".html", ".vue"]
} },
"ignorePatterns": ["vendors", "*.min.js"]
} }

View File

@ -1,25 +0,0 @@
---
name: 功能请求(请先查看常见问题及搜索issue列表中有无你要提的问题)
about: 为这个项目提出一个想法
title: 例如添加xxx功能、优化xxx功能
labels: ''
assignees: ''
---
**解决方案检查**
<!-- 请确保你已从以下渠道寻找过解决方案,然后将 [ ] 替换成 [x] -->
- [ ] 我已阅读常见问题(<https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md>)
- [ ] 我已搜索issue列表(<https://github.com/lyswhut/lx-music-desktop/issues?utf8=✓&q=>)
**描述您想要的解决方案**
<!-- 简洁明了地描述您要发生的事情。 -->
**描述您考虑过的替代方案**
<!-- 对您考虑过的所有替代解决方案或功能的简洁明了的描述。 -->
**其他内容**
<!-- 在此处添加有关功能请求的任何其他上下文或屏幕截图(直接把图片拖到编辑框即可添加图片)。 -->

35
.github/ISSUE_TEMPLATE/----.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: ✨功能请求
description: 为这个项目提出一个想法请先查看常见问题及搜索issue列表中有无你要提的问题
title: "[Feature]: <title>"
body:
- type: checkboxes
attributes:
label: 解决方案检查
description: 请确保你已完成以下所有操作
options:
- label: 我已阅读常见问题(<https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md>),但没有找到解决方案
required: true
- label: 我已搜索issue列表(<https://github.com/lyswhut/lx-music-desktop/issues?utf8=✓&q=>),但没有发现类似的问题
required: true
- type: textarea
attributes:
label: 问题描述
description: 请添加清晰简洁的描述,说明你希望通过此功能请求解决的问题
validations:
required: true
- type: textarea
attributes:
label: 描述您想要的解决方案
description: 简洁明了地描述你要发生的事情
validations:
required: true
- type: textarea
attributes:
label: 描述您考虑过的替代方案
description: 对你考虑过的所有替代解决方案或功能的简洁明了的描述
validations:
required: false
- type: textarea
attributes:
label: 附加信息
description: 如果你的问题需要进一步解释,或者想要表达其他内容,请在此处添加更多信息。(直接把图片、视频拖到编辑框即可添加图片或视频)

View File

@ -1,42 +0,0 @@
---
name: 报告Bug(请先查看常见问题及搜索issue列表中有无你要提的问题)
about: 创建报告以帮助我们改进
title: 例如:音乐无法播放
labels: ''
assignees: ''
---
**解决方案检查**
<!-- 请确保你已从以下渠道寻找过解决方案,然后将 [ ] 替换成 [x] -->
- [ ] 我已阅读常见问题(<https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md>)
- [ ] 我已搜索issue列表(<https://github.com/lyswhut/lx-music-desktop/issues?utf8=✓&q=>)
**描述错误**
<!-- 清楚简洁地说明错误是什么。 -->
**重现**
重现行为的步骤:
1.转到“ ...”
2.点击“ ....”
3.向下滚动到“ ....”
4.看到错误
**预期行为**
<!-- 对您期望发生的事情的简洁明了的描述。 -->
**截图**
<!-- 如果适用,请添加屏幕截图以帮助解释您的问题(直接把图片拖到编辑框即可添加图片)。 -->
**环境:**
  -操作系统及版本:[例如Windows 10 64位 18362.156]
  -软件安装包及版本:[例如1.0.0 安装版]
**其他内容**
<!-- 在此处添加有关该问题的任何其他上下文。 -->

48
.github/ISSUE_TEMPLATE/--bug.yml vendored Normal file
View File

@ -0,0 +1,48 @@
name: 🐞报告Bug
description: 报告bug请先查看常见问题及搜索issue列表中有无你要提的问题
title: "[Bug]: <title>"
body:
- type: checkboxes
attributes:
label: 解决方案检查
description: 请确保你已完成以下所有操作
options:
- label: 我已阅读常见问题(<https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md>),并没有找到解决方案
required: true
- label: 我已搜索issue列表(<https://github.com/lyswhut/lx-music-desktop/issues?utf8=✓&q=>),并没有发现类似的问题
required: true
- type: textarea
attributes:
label: 预期行为
description: 对期望发生的事情的清晰简明描述
validations:
required: true
- type: textarea
attributes:
label: 实际行为
description: 对实际发生的事情的清晰简明描述
validations:
required: true
- type: input
attributes:
label: Lx Music 版本
description: 你使用什么版本的LX Music
placeholder: 1.15.0
validations:
required: true
- type: input
attributes:
label: 最后正常的版本
description: 如果有,请在此处填写最后正常的版本是多少?
placeholder: 1.15.0
- type: input
attributes:
label: 操作系统版本
description: 您使用的是什么操作系统版本?在 Windows 上,单击开始按钮 > 设置 > 系统 > 关于;在 macOS 上,单击 Apple 菜单 > 关于本机;在 Linux 上,使用 lsb_release 或 uname -a
placeholder: "例如 Windows 10 版本 1909、macOS Catalina 10.15.7 或 Ubuntu 20.04"
validations:
required: true
- type: textarea
attributes:
label: 附加信息
description: 如果你的问题需要进一步解释,或者你所遇到的问题不容易重现,请在此处添加更多信息。(直接把图片、视频拖到编辑框即可添加图片或视频)

View File

@ -3,6 +3,7 @@ module.exports = {
reject: [ reject: [
'vue-loader', 'vue-loader',
'webpack-dev-server', 'webpack-dev-server',
'eslint',
// 'eslint-config-standard' // 'eslint-config-standard'
] ]
} }

View File

@ -6,6 +6,87 @@ 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.15.2](https://github.com/lyswhut/lx-music-desktop/compare/v1.15.3...v1.15.2) - 2021-11-09
### 其他
- 降级electron到v13.4.0这修复了windows 7下播放歌曲时软件会崩溃的问题
## [1.15.3](https://github.com/lyswhut/lx-music-desktop/compare/v1.15.1...v1.15.3) - 2021-11-09
### 其他
- 降级electron到v13.4.0这修复了windows 7下播放歌曲时软件会崩溃的问题
## [1.15.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.15.0...v1.15.1) - 2021-11-09
### 优化
- 优化我的列表、下载列表等列表的滚动流畅度
- 优化下载功能的批量添加、删除、暂停任务时的流畅度,现在进行这些操作应该不会再觉得卡顿了
- 支持启动软件时恢复播放下载列表里的歌曲
- 添加媒体播放进度条的信息设置
### 修复
- 修复某些情况下获取URL失败时会意外切歌的问题
- 修复了某些情况下会列表同步失败,导致连接断开无限重连或一直卡在 `syncing...` 的问题
- 修复列表数据过大导致同步失败的问题
### 其他
- 更新electron到v15.3.1(这修复了媒体控制失效的问题)
## [1.15.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.14.1...v1.15.0) - 2021-10-29
### 新增
- 添加黑色托盘图标
- 自定义源新增`version`字段,新增`utils.buffer.bufToString`方法
### 优化
- 大幅优化我的列表、下载、歌单、排行榜列表性能,现在即使同一列表内的歌曲很多时也不会卡顿了
- 优化列表同步代码逻辑
- 优化开关评论时的动画性能
- 优化进入、离开播放详情页的性能
- 兼容桌面歌词以触摸的方式移动、调整大小
- 调整图标尺寸
### 修复
- 修复kg源的歌单链接无法打开的问题
- 修复同一首歌的URL、歌词等同时需要换源时的处理问题
### 其他
- 更新 Electron 到 v15.3.0
## [1.14.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.14.0...v1.14.1) - 2021-10-04
### 修复
- 修复我的列表搜索无法搜索小括号、中括号等字符的问题
- 修复v1.14.0出现的备份与恢复功能备份的数据无法恢复的问题同时兼容使用v1.14.0导出的存在问题的数据
## [1.14.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.13.0...v1.14.0) - 2021-10-02
### 新增
- 新增歌词简体中文转繁体中文,当软件语言被设置为繁体中文后,播放歌曲的歌词也将自动转成繁体中文显示
- 新增单个列表导入/导出功能,可以方便分享歌曲列表,可在右击“我的列表”里的列表名后弹出的菜单中使用
- 新增删除列表前的确认弹窗,防止误删列表
- 新增歌词文本选择复制功能,可在详情页进度条上方的歌词文本选择按钮进入歌词文本选择模式,选择完成后可鼠标右击或者使用系统快捷键复制
- 新增重复歌曲列表,可以方便移除我的列表中的重复歌曲,此列表会列出目标列表里歌曲名相同的歌曲,可在右击“我的列表”里的列表名后弹出的菜单中使用
### 修复
- 修复mg排行榜无法加载的问题
- 修复点击播放详情页的进度条跳进度时会出现偏移的问题
- 修复在有提示信息的地方长按鼠标按键时提示信息会闪烁的问题
- 修复下载歌曲时的歌词下载不尝试获取缓存歌词的问题
- 修复GNOME等桌面下每次打开应用时需重新设置歌词窗口置顶的问题
## [1.13.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.12.2...v1.13.0) - 2021-09-05 ## [1.13.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.12.2...v1.13.0) - 2021-09-05
如果你喜欢并经常使用洛雪音乐并想要第一时间尝鲜洛雪的新功能可以加入测试企鹅群768786588 如果你喜欢并经常使用洛雪音乐并想要第一时间尝鲜洛雪的新功能可以加入测试企鹅群768786588

29
FAQ.md
View File

@ -56,9 +56,11 @@
播放在线列表内的歌曲需要将它们都添加到我的列表才能播放,你可以全选列表内的歌曲然后添加到现有列表或者新创建的列表,然后去播放该列表内的歌曲。 播放在线列表内的歌曲需要将它们都添加到我的列表才能播放,你可以全选列表内的歌曲然后添加到现有列表或者新创建的列表,然后去播放该列表内的歌曲。
从v1.10.0起,你可以右击排行榜名字的弹出菜单中直接播放或收藏整个排行榜的歌曲。
## 无法打开外部歌单 ## 无法打开外部歌单
不支持源打开歌单,请**确认**你需要打开的歌单平台是否与软件标签所写的**歌单源**对应(不一样的话请通过右上角切换歌单源);<br> 不支持源打开歌单,请**确认**你需要打开的歌单平台是否与软件标签所写的**歌单源**对应(不一样的话请通过右上角切换歌单源);<br>
对于分享出来的歌单若打开失败可尝试先在浏览器中打开后再从浏览器地址栏复制URL地址到软件打开<br> 对于分享出来的歌单若打开失败可尝试先在浏览器中打开后再从浏览器地址栏复制URL地址到软件打开<br>
或者如果你知道歌单 id 也可以直接输入歌单 id 打开。<br> 或者如果你知道歌单 id 也可以直接输入歌单 id 打开。<br>
@ -128,12 +130,12 @@
由于软件默认使用了透明窗口根据Electron官方文档的[说明](https://electronjs.org/docs/api/frameless-window#%E5%B1%80%E9%99%90%E6%80%A7) 由于软件默认使用了透明窗口根据Electron官方文档的[说明](https://electronjs.org/docs/api/frameless-window#%E5%B1%80%E9%99%90%E6%80%A7)
> 在 windows 操作系统上, 当 DWM 被禁用时, 透明窗口将无法工作。 > 在 windows 操作系统上, 当 DWM 被禁用时, 透明窗口将无法工作。
因此,当 win7 没有使用**AERO**主题时界面将会显示异常开启AERO的方法请自行百度`win7开启aero效果`(开启后可看到任务栏变透明)。<br> 因此,当 win7 没有使用**Aero**主题时界面将会显示异常开启AERO的方法请自行百度`win7开启Aero效果`(开启后可看到任务栏变透明)。<br>
从`0.14.0`版本起不再强制要求开启透明效果,若你实在不想开启(若非电脑配置太低,墙裂建议开启!),可通过添加运行参数`-dt`来运行程序即可,例如:`.\lx-music-desktop.exe -dt`,添加方法可自行百度“给快捷方式加参数”,该参数的作用是用来控制程序是否使用非透明窗口运行。 从`0.14.0`版本起不再强制要求开启透明效果,若你实在不想开启(若非电脑配置太低,墙裂建议开启!),可通过添加运行参数`-dt`来运行程序即可,例如:`.\lx-music-desktop.exe -dt`,添加方法可自行百度“给快捷方式加参数”,该参数的作用是用来控制程序是否使用非透明窗口运行。
注:启用**AERO**主题后,若软件出现黑边框,则重启软件即可恢复正常。 注:启用**Aero**主题后,若软件出现黑边框,则重启软件即可恢复正常。
对于一些完全无法正常显示界面、开启了AERO后问题仍未解决的情况请阅读下面的 **软件启动后,界面无法显示** 解决。 对于一些完全无法正常显示界面、开启了AERO后问题仍未解决的情况请阅读下面的 **Window 7 下软件启动后,界面无法显示** 解决。
### Linux 下界面异常 ### Linux 下界面异常
@ -142,19 +144,17 @@
v1.6.0及之后的版本才支持`-dha`参数 v1.6.0及之后的版本才支持`-dha`参数
## 软件启动后,界面无法显示 ## Windows 7 下软件启动后,界面无法显示
对于软件启动后,可以在任务栏看到软件,但软件界面在桌面上无任何显示,或者整个界面偶尔闪烁的情况。<br> 对于软件启动后,可以在任务栏看到软件,但软件界面在桌面上无任何显示,或者整个界面偶尔闪烁的情况。<br>
原始问题看:<https://github.com/electron/electron/issues/19569#issuecomment-522231083><br> 原始问题看:<https://github.com/electron/electron/issues/19569#issuecomment-522231083><br>
解决办法:下载`.NET Framework 4.7.1`或**更高**版本安装即可(建议安装最新版,若安装过程中遇到问题可尝试自行百度解决)。<br> 解决办法:下载`.NET Framework 4.7.1`或**更高**版本安装即可(建议安装最新版,若安装过程中遇到问题可尝试自行百度解决)。<br>
微软官方下载地址:<https://dotnet.microsoft.com/download/dotnet-framework><br> 微软官方下载地址:<https://dotnet.microsoft.com/download/dotnet-framework><br>
下载`Runtime(运行时)`版即可,安装完成后可能需要重启才生效。 下载`Runtime(运行时)`版即可,安装完成后可能需要重启才生效**若出现闪烁的情况**,可阅读下面的**Windows 7 下整个界面闪烁**解决
若还是不行可尝试以下操作: ## Windows 7 下整个界面闪烁(消失又出现)
- 更新显卡驱动 可尝试在关掉软件后,在桌面空白处鼠标右击,在弹出的菜单中选择**个性化**,在弹出的窗口中**切换到系统内置的Aero主题**,然后再启动软件看是否解决。
- 添加启动参数`-dha`运行(添加的方法请自行百度“给快捷方式加参数”)
- 尝试将绿色版的软件放在**桌面**或**我的文档**运行
## Windows 7 下桌面歌词字体列表为空 ## Windows 7 下桌面歌词字体列表为空
@ -344,8 +344,8 @@ send(EVENT_NAMES.inited, {
``` ```
- `@name `:源的名字,建议不要过长,10个字符以内 - `@name `:源的名字,建议不要过长,24个字符以内
- `@description `:源的描述,建议不要过长,20个字符以内,可不填,不填时必须保留 @description - `@description `:源的描述,建议不要过长,36个字符以内,可不填,不填时必须保留 @description
- `@version`:源的版本号,可不填,不填时可以删除 @version - `@version`:源的版本号,可不填,不填时可以删除 @version
- `@author `:脚本作者名字,可不填,不填时可以删除 @author - `@author `:脚本作者名字,可不填,不填时可以删除 @author
- `@homepage `:脚本主页,可不填,不填时可以删除 @homepage - `@homepage `:脚本主页,可不填,不填时可以删除 @homepage
@ -354,6 +354,10 @@ send(EVENT_NAMES.inited, {
应用为脚本暴露的API对象。 应用为脚本暴露的API对象。
#### `window.lx.version`
自定义源API版本API变更时此版本号将会更改新增于v1.14.0之后)
#### `window.lx.EVENT_NAMES` #### `window.lx.EVENT_NAMES`
常量事件名称对象,发送、注册事件时传入事件名时使用,可用值: 常量事件名称对象,发送、注册事件时传入事件名时使用,可用值:
@ -409,6 +413,7 @@ const cancelHttp = window.lx.request(url, options, callback)
应用提供给脚本的工具方法: 应用提供给脚本的工具方法:
- `window.lx.utils.buffer.from`对应Node.js的 `Buffer.from` - `window.lx.utils.buffer.from`对应Node.js的 `Buffer.from`
- `window.lx.utils.buffer.bufToString`Buffer转字符串 `bufToString(buffer, format)``format`对应Node.js `Buffer.toString`的参数v1.14.0之后新增)
- `window.lx.utils.crypto.aesEncrypt`AES加密 `aesEncrypt(buffer, mode, key, iv)` - `window.lx.utils.crypto.aesEncrypt`AES加密 `aesEncrypt(buffer, mode, key, iv)`
- `window.lx.utils.crypto.md5`MD5加密 `md5(str)` - `window.lx.utils.crypto.md5`MD5加密 `md5(str)`
- `window.lx.utils.crypto.randomBytes`:生成随机字符串 `randomBytes(size)` - `window.lx.utils.crypto.randomBytes`:生成随机字符串 `randomBytes(size)`

View File

@ -36,7 +36,7 @@
所用技术栈: 所用技术栈:
- Electron 13 - Electron 15
- Vue 2 - Vue 2
已支持的平台: 已支持的平台:

View File

@ -1,4 +1,5 @@
const path = require('path') const path = require('path')
const ESLintPlugin = require('eslint-webpack-plugin')
module.exports = { module.exports = {
target: 'electron-main', target: 'electron-main',
@ -18,17 +19,6 @@ module.exports = {
}, },
module: { module: {
rules: [ rules: [
{
test: /\.js$/,
use: {
loader: 'eslint-loader',
options: {
formatter: require('eslint-formatter-friendly'),
},
},
exclude: /node_modules/,
enforce: 'pre',
},
{ {
test: /\.node$/, test: /\.node$/,
use: 'node-loader', use: 'node-loader',
@ -38,4 +28,7 @@ module.exports = {
performance: { performance: {
maxEntrypointSize: 300000, maxEntrypointSize: 300000,
}, },
plugins: [
new ESLintPlugin(),
],
} }

View File

@ -3,6 +3,7 @@ const VueLoaderPlugin = require('vue-loader/lib/plugin')
const HTMLPlugin = require('html-webpack-plugin') const HTMLPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin') const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CleanCSSPlugin = require('less-plugin-clean-css') const CleanCSSPlugin = require('less-plugin-clean-css')
const ESLintPlugin = require('eslint-webpack-plugin')
const vueLoaderConfig = require('../vue-loader.config') const vueLoaderConfig = require('../vue-loader.config')
const { mergeCSSLoader } = require('../utils') const { mergeCSSLoader } = require('../utils')
@ -32,18 +33,6 @@ module.exports = {
}, },
module: { module: {
rules: [ rules: [
{
test: /\.(vue|js)$/,
use: {
loader: 'eslint-loader',
options: {
formatter: require('eslint-formatter-friendly'),
emitWarning: isDev,
},
},
exclude: /node_modules/,
enforce: 'pre',
},
{ {
test: /\.node$/, test: /\.node$/,
use: 'node-loader', use: 'node-loader',
@ -147,5 +136,8 @@ module.exports = {
filename: isDev ? '[name].css' : '[name].[contenthash:8].css', filename: isDev ? '[name].css' : '[name].[contenthash:8].css',
chunkFilename: isDev ? '[id].css' : '[id].[contenthash:8].css', chunkFilename: isDev ? '[id].css' : '[id].[contenthash:8].css',
}), }),
new ESLintPlugin({
extensions: ['js', 'vue'],
}),
], ],
} }

View File

@ -3,6 +3,7 @@ const VueLoaderPlugin = require('vue-loader/lib/plugin')
const HTMLPlugin = require('html-webpack-plugin') const HTMLPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin') const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CleanCSSPlugin = require('less-plugin-clean-css') const CleanCSSPlugin = require('less-plugin-clean-css')
const ESLintPlugin = require('eslint-webpack-plugin')
const vueLoaderConfig = require('../vue-loader.config') const vueLoaderConfig = require('../vue-loader.config')
const { mergeCSSLoader } = require('../utils') const { mergeCSSLoader } = require('../utils')
@ -32,18 +33,6 @@ module.exports = {
}, },
module: { module: {
rules: [ rules: [
{
test: /\.(vue|js)$/,
use: {
loader: 'eslint-loader',
options: {
formatter: require('eslint-formatter-friendly'),
emitWarning: isDev,
},
},
exclude: /node_modules/,
enforce: 'pre',
},
{ {
test: /\.node$/, test: /\.node$/,
use: 'node-loader', use: 'node-loader',
@ -147,5 +136,8 @@ module.exports = {
filename: isDev ? '[name].css' : '[name].[contenthash:8].css', filename: isDev ? '[name].css' : '[name].[contenthash:8].css',
chunkFilename: isDev ? '[id].css' : '[id].[contenthash:8].css', chunkFilename: isDev ? '[id].css' : '[id].[contenthash:8].css',
}), }),
new ESLintPlugin({
extensions: ['js', 'vue'],
}),
], ],
} }

5722
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "lx-music-desktop", "name": "lx-music-desktop",
"version": "1.13.0", "version": "1.15.2",
"description": "一个免费的音乐查找助手", "description": "一个免费的音乐查找助手",
"main": "./dist/electron/main.js", "main": "./dist/electron/main.js",
"productName": "lx-music-desktop", "productName": "lx-music-desktop",
@ -70,7 +70,7 @@
"up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://localhost:1081 npm i" "up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://localhost:1081 npm i"
}, },
"browserslist": [ "browserslist": [
"Electron 13.3.0" "Electron 15.2.0"
], ],
"engines": { "engines": {
"node": ">= 14" "node": ">= 14"
@ -78,6 +78,7 @@
"build": { "build": {
"appId": "cn.toside.music.desktop", "appId": "cn.toside.music.desktop",
"directories": { "directories": {
"buildResources": "./resources",
"output": "./build" "output": "./build"
}, },
"files": [ "files": [
@ -90,12 +91,12 @@
"./licenses" "./licenses"
], ],
"win": { "win": {
"icon": "./resources/icons/256x256.ico", "icon": "./resources/icons/icon.ico",
"legalTrademarks": "lyswhut", "legalTrademarks": "lyswhut",
"artifactName": "${productName} v${version} ${env.ARCH} ${env.TARGET}.${ext}" "artifactName": "${productName} v${version} ${env.ARCH} ${env.TARGET}.${ext}"
}, },
"mac": { "mac": {
"icon": "./resources/icons/512x512.icns", "icon": "./resources/icons/icon.icns",
"category": "public.app-category.music" "category": "public.app-category.music"
}, },
"linux": { "linux": {
@ -166,51 +167,51 @@
}, },
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme", "homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
"devDependencies": { "devDependencies": {
"@babel/core": "^7.15.5", "@babel/core": "^7.16.0",
"@babel/plugin-proposal-class-properties": "^7.14.5", "@babel/plugin-proposal-class-properties": "^7.16.0",
"@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-modules-umd": "^7.14.5", "@babel/plugin-transform-modules-umd": "^7.16.0",
"@babel/plugin-transform-runtime": "^7.15.0", "@babel/plugin-transform-runtime": "^7.16.0",
"@babel/polyfill": "^7.12.1", "@babel/polyfill": "^7.12.1",
"@babel/preset-env": "^7.15.4", "@babel/preset-env": "^7.16.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"babel-loader": "^8.2.2", "babel-loader": "^8.2.3",
"babel-preset-minify": "^0.5.1", "babel-preset-minify": "^0.5.1",
"browserslist": "^4.16.8", "browserslist": "^4.17.6",
"cfonts": "^2.9.3", "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": "^9.0.1", "copy-webpack-plugin": "^9.0.1",
"core-js": "^3.17.2", "core-js": "^3.19.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"css-loader": "^6.2.0", "css-loader": "^6.5.1",
"css-minimizer-webpack-plugin": "^3.0.2", "css-minimizer-webpack-plugin": "^3.1.2",
"del": "^6.0.0", "del": "^6.0.0",
"electron": "^13.3.0", "electron": "^13.4.0",
"electron-builder": "^22.11.7", "electron-builder": "^22.11.7",
"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.3.830", "electron-to-chromium": "^1.3.891",
"eslint": "^7.32.0", "eslint": "^7.32.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-loader": "^4.0.2", "eslint-plugin-html": "^6.2.0",
"eslint-plugin-html": "^6.1.2", "eslint-plugin-import": "^2.25.2",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0", "eslint-plugin-promise": "^5.1.1",
"eslint-plugin-standard": "^4.1.0", "eslint-plugin-standard": "^4.1.0",
"eslint-webpack-plugin": "^3.1.0",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"friendly-errors-webpack-plugin": "^1.7.0", "friendly-errors-webpack-plugin": "^1.7.0",
"html-webpack-plugin": "^5.3.2", "html-webpack-plugin": "^5.5.0",
"less": "^4.1.1", "less": "^4.1.2",
"less-loader": "^10.0.1", "less-loader": "^10.2.0",
"less-plugin-clean-css": "^1.5.1", "less-plugin-clean-css": "^1.5.1",
"markdown-it": "^12.2.0", "markdown-it": "^12.2.0",
"mini-css-extract-plugin": "^2.2.2", "mini-css-extract-plugin": "^2.4.4",
"node-loader": "^2.0.0", "node-loader": "^2.0.0",
"postcss": "^8.3.6", "postcss": "^8.3.11",
"postcss-loader": "^6.1.1", "postcss-loader": "^6.2.0",
"postcss-pxtorem": "^6.0.0", "postcss-pxtorem": "^6.0.0",
"pug": "^3.0.2", "pug": "^3.0.2",
"pug-loader": "^2.4.0", "pug-loader": "^2.4.0",
@ -218,38 +219,36 @@
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"spinnies": "^0.5.1", "spinnies": "^0.5.1",
"terser-webpack-plugin": "^5.2.3", "terser-webpack-plugin": "^5.2.5",
"url-loader": "^4.1.1", "url-loader": "^4.1.1",
"vue-loader": "^15.9.8", "vue-loader": "^15.9.8",
"vue-template-compiler": "^2.6.14", "vue-template-compiler": "^2.6.14",
"webpack": "^5.52.0", "webpack": "^5.62.1",
"webpack-cli": "^4.8.0", "webpack-cli": "^4.9.1",
"webpack-dev-server": "^3.11.2", "webpack-dev-server": "^3.11.2",
"webpack-hot-middleware": "^2.25.0", "webpack-hot-middleware": "^2.25.1",
"webpack-merge": "^5.8.0" "webpack-merge": "^5.8.0"
}, },
"dependencies": { "dependencies": {
"bufferutil": "^4.0.3", "bufferutil": "^4.0.5",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"electron-log": "^4.4.1", "electron-log": "^4.4.1",
"electron-store": "^8.0.0", "electron-store": "^8.0.1",
"electron-updater": "^4.3.9", "electron-updater": "^4.3.9",
"font-list": "git+https://github.com/lyswhut/node-font-list.git#c6caf4060e471afe143a4aca30d554644522966d", "font-list": "git+https://github.com/lyswhut/node-font-list.git#2ed3b4ee42e8a43373e8a30d87760c840725843e",
"http-terminator": "^3.0.0", "http-terminator": "^3.0.3",
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"image-size": "^1.0.0", "image-size": "^1.0.0",
"koa": "^2.13.1", "koa": "^2.13.4",
"long": "^4.0.0", "long": "^5.1.0",
"lrc-file-parser": "^1.1.2",
"needle": "^3.0.0", "needle": "^3.0.0",
"node-id3": "^0.2.3", "node-id3": "^0.2.3",
"request": "^2.88.2", "request": "^2.88.2",
"socket.io": "^4.2.0", "socket.io": "^4.3.2",
"utf-8-validate": "^5.0.5", "utf-8-validate": "^5.0.7",
"vue": "^2.6.14", "vue": "^2.6.14",
"vue-i18n": "^8.25.0", "vue-i18n": "^8.26.7",
"vue-router": "^3.5.2", "vue-router": "^3.5.3",
"vuex": "^3.6.2", "vuex": "^3.6.2"
"vuex-router-sync": "^5.0.0"
} }
} }

View File

@ -15,7 +15,7 @@ module.exports = {
'*-height', '*-width', '*-height', '*-width',
'flex', '::-webkit-scrollbar', 'flex', '::-webkit-scrollbar',
'top', 'left', 'bottom', 'right', 'top', 'left', 'bottom', 'right',
'border-radius', 'border-radius', 'gap',
], ],
selectorBlackList: ['html', 'ignore-to-rem'], selectorBlackList: ['html', 'ignore-to-rem'],
replace: true, replace: true,

View File

@ -1,18 +1,3 @@
如果你喜欢并经常使用洛雪音乐并想要第一时间尝鲜洛雪的新功能可以加入测试企鹅群768786588 ### 其他
注意:测试版的功可能会不稳定,打算潜水的勿加。
### 新增 - 降级electron到v13.4.0这修复了windows 7下播放歌曲时软件会崩溃的问题
- 歌曲搜索框新增清理按钮,点击此按钮可以清理搜索框并返回初始搜索界面
- 新增“下载的歌词文件编码格式”设置,默认下载的歌词编码仍是`UTF-8`,对于某些在设备(如车机)上出现歌词中文乱码的用户可以尝试选择以`GBK`编码格式保存歌词文件
- 新增设置-桌面歌词-歌词字体设置此设置可用于设置桌面歌词的字体已知的问题Windows 7 下可能会出现字体列表为空的情况,这是当前系统的 Powershell 版本小于5.1导致的,请自行**尝试**看常见解决)
### 优化
- 支持网易源“我喜欢”歌单以注入token的方式打开。由于网易源的“我喜欢”歌单需要登录才能打开若你看不懂后半句就去阅读 常见问题-无法打开外部歌单),现若想要打开此类歌单,需要在歌单链接后面拼上 `###` 再加上有效的token拼接格式`[id|url]###token`例子最后面的xxxxxx替换成你的token`https://music.163.com/#/playlist?id=123456&userid=123456###xxxxxx`
- 软件内快捷键的最小化触发时,如果已启用托盘,则隐藏程序,否则最小化程序
### 修复
- 修复某些情况下同步功能会导致切歌混乱的问题
- 修复从电脑浏览器复制的企鹅歌单链接无法打开的问题

File diff suppressed because one or more lines are too long

BIN
resources/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
resources/icons/16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

BIN
resources/icons/256x256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

BIN
resources/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 901 B

BIN
resources/icons/48x48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

BIN
resources/icons/64x64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
resources/icons/icon.icns Normal file

Binary file not shown.

BIN
resources/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

BIN
resources/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -35,7 +35,6 @@ const defaultSetting = {
list: { list: {
isShowAlbumName: true, isShowAlbumName: true,
isShowSource: true, isShowSource: true,
prevSelectListId: 'default',
isSaveScrollLocation: true, isSaveScrollLocation: true,
addMusicLocationType: 'top', addMusicLocationType: 'top',
}, },

View File

@ -33,6 +33,8 @@ const names = {
restart_window: 'restart_window', restart_window: 'restart_window',
lang_s2t: 'lang_s2t',
handle_kw_decode_lyric: 'handle_kw_decode_lyric', handle_kw_decode_lyric: 'handle_kw_decode_lyric',
get_lyric_info: 'get_lyric_info', get_lyric_info: 'get_lyric_info',
set_lyric_info: 'set_lyric_info', set_lyric_info: 'set_lyric_info',
@ -74,6 +76,7 @@ const names = {
sync_generate_code: 'sync_generate_code', sync_generate_code: 'sync_generate_code',
sync_action_list: 'sync_action_list', sync_action_list: 'sync_action_list',
sync_list: 'sync_list', sync_list: 'sync_list',
}, },
winLyric: { winLyric: {
close: 'close', close: 'close',

View File

@ -153,8 +153,6 @@ exports.initSetting = isShowErrorAlert => {
// 迁移列表滚动位置设置 ~0.18.3 // 迁移列表滚动位置设置 ~0.18.3
if (setting.list.scroll) { if (setting.list.scroll) {
let scroll = setting.list.scroll let scroll = setting.list.scroll
electronStore_list.set('defaultList.location', scroll.locations.default || 0)
electronStore_list.set('loveList.location', scroll.locations.love || 0)
electronStore_config.delete('setting.list.scroll') electronStore_config.delete('setting.list.scroll')
electronStore_config.set('setting.list.isSaveScrollLocation', scroll.enable) electronStore_config.set('setting.list.isSaveScrollLocation', scroll.enable)
delete setting.list.scroll delete setting.list.scroll

View File

@ -46,7 +46,7 @@ const { isMac, isLinux, initHotKey } = require('../common/utils')
// https://github.com/electron/electron/issues/18397 // https://github.com/electron/electron/issues/18397
// 开发模式下为true时 多次引入native模块会导致渲染进程卡死 // 开发模式下为true时 多次引入native模块会导致渲染进程卡死
// https://github.com/electron/electron/issues/22791 // https://github.com/electron/electron/issues/22791
app.allowRendererProcessReuse = !isDev // app.allowRendererProcessReuse = !isDev
app.on('web-contents-created', (event, contents) => { app.on('web-contents-created', (event, contents) => {

View File

@ -5,6 +5,7 @@ const modules = require('../modules')
const { authCode, authConnect } = require('./auth') const { authCode, authConnect } = require('./auth')
const { getAddress, getServerId, generateCode, getClientKeyInfo } = require('./utils') const { getAddress, getServerId, generateCode, getClientKeyInfo } = require('./utils')
const syncList = require('./syncList') const syncList = require('./syncList')
const { log } = require('@common/utils')
let status = { let status = {
@ -71,7 +72,7 @@ const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
serveClient: false, serveClient: false,
connectTimeout: 10000, connectTimeout: 10000,
pingTimeout: 30000, pingTimeout: 30000,
maxHttpBufferSize: 3e6, maxHttpBufferSize: 1e9, // 1G
allowRequest: authConnection, allowRequest: authConnection,
transports: ['websocket'], transports: ['websocket'],
}) })
@ -88,7 +89,8 @@ const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
try { try {
await syncList(io, socket) await syncList(io, socket)
} catch (err) { } catch (err) {
console.log(err) // console.log(err)
log.warn(err)
return return
} }
status.devices.push(keyInfo) status.devices.push(keyInfo)

View File

@ -11,6 +11,22 @@ let io
let syncingId = null let syncingId = null
const wait = (time = 1000) => new Promise((resolve, reject) => setTimeout(resolve, time)) const wait = (time = 1000) => new Promise((resolve, reject) => setTimeout(resolve, time))
const patchListData = listData => {
return Object.assign({}, {
defaultList: {
id: 'default',
name: '试听列表',
list: [],
},
loveList: {
id: 'love',
name: '我的收藏',
list: [],
},
userList: [],
}, listData)
}
const getRemoteListData = socket => new Promise((resolve, reject) => { const getRemoteListData = socket => new Promise((resolve, reject) => {
console.log('getRemoteListData') console.log('getRemoteListData')
const handleError = reason => { const handleError = reason => {
@ -23,7 +39,7 @@ const getRemoteListData = socket => new Promise((resolve, reject) => {
const data = JSON.parse(decryptMsg(socket.data.keyInfo, enData)) const data = JSON.parse(decryptMsg(socket.data.keyInfo, enData))
if (!data) return reject(new Error('Get remote list data failed')) if (!data) return reject(new Error('Get remote list data failed'))
if (data.action != 'getData') return if (data.action != 'getData') return
resolve(data.data) resolve(patchListData(data.data))
} }
socket.on('disconnect', handleError) socket.on('disconnect', handleError)
@ -35,7 +51,7 @@ const getLocalListData = () => new Promise((resolve, reject) => {
const handleSuccess = ({ action, data }) => { const handleSuccess = ({ action, data }) => {
if (action !== 'getData') return if (action !== 'getData') return
global.lx_event.sync.off(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess) global.lx_event.sync.off(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
resolve(data) resolve(patchListData(data))
} }
global.lx_event.sync.on(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess) global.lx_event.sync.on(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
global.lx_event.sync.sync_list({ global.lx_event.sync.sync_list({
@ -87,10 +103,10 @@ const updateSnapshot = (path, data) => {
} }
const createListDataObj = listData => { const createUserListDataObj = listData => {
const listDataObj = {} const userListDataObj = {}
for (const list of listData.userList) listDataObj[list.id] = list for (const list of listData.userList) userListDataObj[list.id] = list
return listDataObj return userListDataObj
} }
const handleMergeList = (sourceList, targetList, addMusicLocationType) => { const handleMergeList = (sourceList, targetList, addMusicLocationType) => {
@ -137,11 +153,11 @@ const mergeList = (sourceListData, targetListData) => {
newListData.defaultList = handleMergeList(sourceListData.defaultList, targetListData.defaultList, addMusicLocationType) newListData.defaultList = handleMergeList(sourceListData.defaultList, targetListData.defaultList, addMusicLocationType)
newListData.loveList = handleMergeList(sourceListData.loveList, targetListData.loveList, addMusicLocationType) newListData.loveList = handleMergeList(sourceListData.loveList, targetListData.loveList, addMusicLocationType)
const listDataObj = createListDataObj(sourceListData) const userListDataObj = createUserListDataObj(sourceListData)
newListData.userList = [...sourceListData.userList] newListData.userList = [...sourceListData.userList]
for (const list of targetListData.userList) { for (const list of targetListData.userList) {
const targetList = listDataObj[list.id] const targetList = userListDataObj[list.id]
if (targetList) { if (targetList) {
targetList.list = handleMergeList(targetList, list, addMusicLocationType).list targetList.list = handleMergeList(targetList, list, addMusicLocationType).list
} else { } else {
@ -156,11 +172,11 @@ const overwriteList = (sourceListData, targetListData) => {
newListData.defaultList = sourceListData.defaultList newListData.defaultList = sourceListData.defaultList
newListData.loveList = sourceListData.loveList newListData.loveList = sourceListData.loveList
const listDataObj = createListDataObj(sourceListData) const userListDataObj = createUserListDataObj(sourceListData)
newListData.userList = [...sourceListData.userList] newListData.userList = [...sourceListData.userList]
for (const list of targetListData.userList) { for (const list of targetListData.userList) {
const targetList = listDataObj[list.id] const targetList = userListDataObj[list.id]
if (targetList) continue if (targetList) continue
newListData.userList.push(list) newListData.userList.push(list)
} }
@ -259,11 +275,10 @@ const mergeListDataFromSnapshot = (sourceList, targetList, snapshotList, addMusi
const targetListItemIds = new Set() const targetListItemIds = new Set()
for (const m of sourceList.list) sourceListItemIds.add(m.songmid) for (const m of sourceList.list) sourceListItemIds.add(m.songmid)
for (const m of targetList.list) targetListItemIds.add(m.songmid) for (const m of targetList.list) targetListItemIds.add(m.songmid)
for (const m of snapshotList.list) { if (snapshotList) {
if (!sourceListItemIds.has(m.songmid)) removedListIds.add(m.songmid) for (const m of snapshotList.list) {
} if (!sourceListItemIds.has(m.songmid) || !targetListItemIds.has(m.songmid)) removedListIds.add(m.songmid)
for (const m of snapshotList.list) { }
if (!targetListItemIds.has(m.songmid)) removedListIds.add(m.songmid)
} }
let newList let newList
@ -297,13 +312,12 @@ const mergeListDataFromSnapshot = (sourceList, targetList, snapshotList, addMusi
const handleMergeListDataFromSnapshot = async(socket, snapshot) => { const handleMergeListDataFromSnapshot = async(socket, snapshot) => {
const addMusicLocationType = global.appSetting.list.addMusicLocationType const addMusicLocationType = global.appSetting.list.addMusicLocationType
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()]) const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
console.log('handleMergeListDataFromSnapshot', 'remoteListData, localListData')
const newListData = {} const newListData = {}
newListData.defaultList = mergeListDataFromSnapshot(localListData.defaultList, remoteListData.defaultList, snapshot.defaultList, addMusicLocationType) newListData.defaultList = mergeListDataFromSnapshot(localListData.defaultList, remoteListData.defaultList, snapshot.defaultList, addMusicLocationType)
newListData.loveList = mergeListDataFromSnapshot(localListData.loveList, remoteListData.loveList, snapshot.loveList, addMusicLocationType) newListData.loveList = mergeListDataFromSnapshot(localListData.loveList, remoteListData.loveList, snapshot.loveList, addMusicLocationType)
const localUserListData = createListDataObj(localListData) const localUserListData = createUserListDataObj(localListData)
const remoteUserListData = createListDataObj(remoteListData) const remoteUserListData = createUserListDataObj(remoteListData)
const snapshotUserListData = createListDataObj(snapshot) const snapshotUserListData = createUserListDataObj(snapshot)
const removedListIds = new Set() const removedListIds = new Set()
const localUserListIds = new Set() const localUserListIds = new Set()
const remoteUserListIds = new Set() const remoteUserListIds = new Set()
@ -311,10 +325,7 @@ const handleMergeListDataFromSnapshot = async(socket, snapshot) => {
for (const l of remoteListData.userList) remoteUserListIds.add(l.id) for (const l of remoteListData.userList) remoteUserListIds.add(l.id)
for (const l of snapshot.userList) { for (const l of snapshot.userList) {
if (!localUserListIds.has(l.id)) removedListIds.add(l.id) if (!localUserListIds.has(l.id) || !remoteUserListIds.has(l.id)) removedListIds.add(l.id)
}
for (const l of snapshot.userList) {
if (!remoteUserListIds.has(l.id)) removedListIds.add(l.id)
} }
let newUserList = [] let newUserList = []
@ -353,7 +364,7 @@ const registerUpdateSnapshotTask = (socket, snapshot) => {
if (loveList != null) snapshot.loveList = loveList if (loveList != null) snapshot.loveList = loveList
if (userList != null) snapshot.userList = userList if (userList != null) snapshot.userList = userList
updateSnapshot(socket.data.snapshotFilePath, JSON.stringify(snapshot)) updateSnapshot(socket.data.snapshotFilePath, JSON.stringify(snapshot))
}, 10000) }, 2000)
global.lx_event.common.on(COMMON_EVENT_NAME.saveMyList, handleUpdateSnapshot) global.lx_event.common.on(COMMON_EVENT_NAME.saveMyList, handleUpdateSnapshot)
socket.on('disconnect', () => { socket.on('disconnect', () => {
global.lx_event.common.off(COMMON_EVENT_NAME.saveMyList, handleUpdateSnapshot) global.lx_event.common.off(COMMON_EVENT_NAME.saveMyList, handleUpdateSnapshot)
@ -373,7 +384,7 @@ const syncList = async socket => {
} }
console.log('isSyncRequired', isSyncRequired) console.log('isSyncRequired', isSyncRequired)
if (isSyncRequired) return handleSyncList(socket) if (isSyncRequired) return handleSyncList(socket)
return handleMergeListDataFromSnapshot(socket, fileData) return handleMergeListDataFromSnapshot(socket, patchListData(fileData))
} }
const checkSyncQueue = async() => { const checkSyncQueue = async() => {

View File

@ -1,5 +1,5 @@
const { app, Tray, Menu } = require('electron') const { app, Tray, Menu, nativeImage } = require('electron')
const { isWin } = require('../../common/utils') // const { isWin } = require('../../common/utils')
const { tray: TRAY_EVENT_NAME, common: COMMON_EVENT_NAME, mainWindow: MAIN_WINDOW_NAME } = require('../events/_name') const { tray: TRAY_EVENT_NAME, common: COMMON_EVENT_NAME, mainWindow: MAIN_WINDOW_NAME } = require('../events/_name')
const path = require('path') const path = require('path')
let isEnableTray = null let isEnableTray = null
@ -7,12 +7,17 @@ let themeId = null
const themeList = [ const themeList = [
{ {
id: 0, id: 0,
fileName: 'tray0Template', fileName: 'trayTemplate',
isNative: true, isNative: true,
}, },
{ {
id: 1, id: 1,
fileName: 'tray1Template', fileName: 'tray_origin',
isNative: false,
},
{
id: 2,
fileName: 'tray_black',
isNative: false, isNative: false,
}, },
] ]
@ -43,11 +48,11 @@ const createTray = () => {
if ((global.modules.tray && !global.modules.tray.isDestroyed()) || !global.appSetting.tray || !global.appSetting.tray.isShow) return if ((global.modules.tray && !global.modules.tray.isDestroyed()) || !global.appSetting.tray || !global.appSetting.tray.isShow) return
themeId = global.appSetting.tray.themeId themeId = global.appSetting.tray.themeId
let themeName = (themeList.find(item => item.id === themeId) || themeList[0]).fileName let theme = themeList.find(item => item.id === themeId) || themeList[0]
const iconPath = path.join(global.__static, 'images/tray', isWin ? themeName + '@2x.ico' : themeName + '.png') const iconPath = path.join(global.__static, 'images/tray', theme.fileName + '.png')
// 托盘 // 托盘
global.modules.tray = new Tray(iconPath) global.modules.tray = new Tray(nativeImage.createFromPath(iconPath))
global.modules.tray.setToolTip('洛雪音乐助手') global.modules.tray.setToolTip('洛雪音乐助手')
createMenu(global.modules.tray) createMenu(global.modules.tray)
@ -140,7 +145,7 @@ const createMenu = tray => {
const setTrayImage = themeId => { const setTrayImage = themeId => {
if (!global.modules.tray) return if (!global.modules.tray) return
let themeName = (themeList.find(item => item.id === themeId) || themeList[0]).fileName let theme = themeList.find(item => item.id === themeId) || themeList[0]
const iconPath = path.join(global.__static, 'images/tray', isWin ? themeName + '@2x.ico' : themeName + '.png') const iconPath = path.join(global.__static, 'images/tray', theme.fileName + '.png')
global.modules.tray.setImage(iconPath) global.modules.tray.setImage(nativeImage.createFromPath(iconPath))
} }

View File

@ -66,6 +66,7 @@ const handleRequest = (context, { requestKey, data }) => {
* *
* @param {*} context * @param {*} context
* @param {*} info { * @param {*} info {
* openDevTools: false,
* status: true, * status: true,
* message: 'xxx', * message: 'xxx',
* sources: { * sources: {
@ -184,7 +185,7 @@ contextBridge.exposeInMainWorld('lx', {
utils: { utils: {
crypto: { crypto: {
aesEncrypt(buffer, mode, key, iv) { aesEncrypt(buffer, mode, key, iv) {
const cipher = createCipheriv('aes-128-' + mode, key, iv) const cipher = createCipheriv(mode, key, iv)
return Buffer.concat([cipher.update(buffer), cipher.final()]) return Buffer.concat([cipher.update(buffer), cipher.final()])
}, },
rsaEncrypt(buffer, key) { rsaEncrypt(buffer, key) {
@ -202,8 +203,12 @@ contextBridge.exposeInMainWorld('lx', {
from(...args) { from(...args) {
return Buffer.from(...args) return Buffer.from(...args)
}, },
bufToString(buf, format) {
return Buffer.from(buf, 'binary').toString(format)
},
}, },
}, },
version: '1.1.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

View File

@ -19,9 +19,9 @@ exports.importApi = script => {
let name = scriptInfo[1] || '' let name = scriptInfo[1] || ''
let description = scriptInfo[2] || '' let description = scriptInfo[2] || ''
name = name.startsWith(' * @name ') ? name.replace(' * @name ', '').trim() : `user_api_${new Date().toLocaleString()}` name = name.startsWith(' * @name ') ? name.replace(' * @name ', '').trim() : `user_api_${new Date().toLocaleString()}`
if (name.length > 10) name = name.substring(0, 10) + '...' if (name.length > 24) name = name.substring(0, 24) + '...'
description = description.startsWith(' * @description ') ? description.replace(' * @description ', '').trim() : '' description = description.startsWith(' * @description ') ? description.replace(' * @description ', '').trim() : ''
if (description.length > 20) description = description.substring(0, 20) + '...' if (description.length > 36) description = description.substring(0, 36) + '...'
const apiInfo = { const apiInfo = {
id: `user_api_${Math.random().toString().substring(2, 5)}_${Date.now()}`, id: `user_api_${Math.random().toString().substring(2, 5)}_${Date.now()}`,
name, name,

View File

@ -1,7 +1,7 @@
const path = require('path') const path = require('path')
const { BrowserWindow } = require('electron') const { BrowserWindow } = require('electron')
const { winLyric: WIN_LYRIC_EVENT_NAME } = require('../../events/_name') const { winLyric: WIN_LYRIC_EVENT_NAME } = require('../../events/_name')
const { debounce } = require('../../../common/utils') const { debounce, isLinux } = require('../../../common/utils')
const { getLyricWindowBounds } = require('./utils') const { getLyricWindowBounds } = require('./utils')
require('./event') require('./event')
@ -66,6 +66,10 @@ const winEvent = lyricWindow => {
if (global.appSetting.desktopLyric.isLock) { if (global.appSetting.desktopLyric.isLock) {
global.modules.lyricWindow.setIgnoreMouseEvents(true, { forward: false }) global.modules.lyricWindow.setIgnoreMouseEvents(true, { forward: false })
} }
// linux下每次重开时貌似要重新设置置顶
if (isLinux && global.appSetting.desktopLyric.isAlwaysOnTop) {
global.modules.lyricWindow.setAlwaysOnTop(global.appSetting.desktopLyric.isAlwaysOnTop, 'screen-saver')
}
}) })
} }

View File

@ -25,3 +25,5 @@ require('./kw_decodeLyric')
require('./userApi') require('./userApi')
require('./sync') require('./sync')
require('./s2t')

View File

@ -0,0 +1,9 @@
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
const { tranditionalize } = require('../utils/simplify-chinese-main')
mainHandle(ipcMainWindowNames.lang_s2t, async(event, textBase64) => {
if (!global.modules.mainWindow) throw new Error('mainWindow is undefined')
const text = tranditionalize(Buffer.from(textBase64, 'base64').toString())
return Buffer.from(text).toString('base64')
})

View File

@ -0,0 +1,18 @@
todo.md
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
package-lock.json
tsconfig.tsbuildinfo
report.*.json
.eslintcache
.DS_Store
.idea
.vscode
.yarn
*.suo
*.ntvs*
*.njsproj
*.sln

View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2021 Shigma
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,10 @@
# simplify-chinese
Convert chinese characters between simplified form and tranditional form / 汉字简繁体转换工具。
```js
const { simplify, tranditionalize } = require('simplify-chinese')
console.log(simplify('窩窩頭')) // 窝窝头
console.log(tranditionalize('窝窝头')) // 窩窩頭
```

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
export function simplify(source: string): string
export function tranditionalize(source: string): string

View File

@ -0,0 +1,30 @@
const { simplified, traditional } = require('./chinese')
const stMap = new Map()
const tsMap = new Map()
simplified.split('').forEach((char, index) => {
stMap.set(char, traditional[index])
tsMap.set(traditional[index], char)
})
function simplify(source) {
let result = []
for (const char of source) {
result.push(tsMap.get(char) || char)
}
return result.join('')
}
function tranditionalize(source) {
let result = []
for (const char of source) {
result.push(stMap.get(char) || char)
}
return result.join('')
}
module.exports = {
simplify,
tranditionalize,
}

View File

@ -0,0 +1,10 @@
{
"name": "simplify-chinese",
"description": "Convert chinese characters between simplified form and tranditional form 汉字简繁体转换工具",
"version": "1.1.0",
"main": "index.js",
"typings": "index.d.ts",
"repository": "https://github.com/koishijs/simplify-chinese.git",
"author": "Shigma <1700011071@pku.edu.cn>",
"license": "MIT"
}

View File

@ -5,14 +5,14 @@
.control-bar(v-show="!lrcConfig.isLock") .control-bar(v-show="!lrcConfig.isLock")
core-control-bar(:lrcConfig="lrcConfig" :themes="themeList") core-control-bar(:lrcConfig="lrcConfig" :themes="themeList")
core-lyric(:lrcConfig="lrcConfig" :isPlayLxlrc="isPlayLxlrc" :isShowLyricTranslation="isShowLyricTranslation") core-lyric(:lrcConfig="lrcConfig" :isPlayLxlrc="isPlayLxlrc" :isShowLyricTranslation="isShowLyricTranslation")
div.resize-left(@mousedown.self="handleMouseDown('left', $event)") div.resize-left(@mousedown.self="handleMouseDown('left', $event)" @touchstart.self="handleTouchDown('left', $event)")
div.resize-top(@mousedown.self="handleMouseDown('top', $event)") div.resize-top(@mousedown.self="handleMouseDown('top', $event)" @touchstart.self="handleTouchDown('top', $event)")
div.resize-right(@mousedown.self="handleMouseDown('right', $event)") div.resize-right(@mousedown.self="handleMouseDown('right', $event)" @touchstart.self="handleTouchDown('right', $event)")
div.resize-bottom(@mousedown.self="handleMouseDown('bottom', $event)") div.resize-bottom(@mousedown.self="handleMouseDown('bottom', $event)" @touchstart.self="handleTouchDown('bottom', $event)")
div.resize-top-left(@mousedown.self="handleMouseDown('top-left', $event)") div.resize-top-left(@mousedown.self="handleMouseDown('top-left', $event)" @touchstart.self="handleTouchDown('top-left', $event)")
div.resize-top-right(@mousedown.self="handleMouseDown('top-right', $event)") div.resize-top-right(@mousedown.self="handleMouseDown('top-right', $event)" @touchstart.self="handleTouchDown('top-right', $event)")
div.resize-bottom-left(@mousedown.self="handleMouseDown('bottom-left', $event)") div.resize-bottom-left(@mousedown.self="handleMouseDown('bottom-left', $event)" @touchstart.self="handleTouchDown('bottom-left', $event)")
div.resize-bottom-right(@mousedown.self="handleMouseDown('bottom-right', $event)") div.resize-bottom-right(@mousedown.self="handleMouseDown('bottom-right', $event)" @touchstart.self="handleTouchDown('bottom-right', $event)")
core-icons core-icons
</template> </template>
@ -116,16 +116,34 @@ export default {
this.isPlayLxlrc = isPlayLxlrc this.isPlayLxlrc = isPlayLxlrc
if (this.$i18n.locale !== languageId && languageId != null) this.$i18n.locale = languageId if (this.$i18n.locale !== languageId && languageId != null) this.$i18n.locale = languageId
}, },
handleMouseDown(origin, event) { handleDown(origin, clientX, clientY) {
this.handleMouseUp() this.handleMouseUp()
this.resize.origin = origin this.resize.origin = origin
this.resize.msDownX = event.clientX this.resize.msDownX = clientX
this.resize.msDownY = event.clientY this.resize.msDownY = clientY
}, },
handleMouseUp() { handleMouseUp() {
this.resize.origin = null this.resize.origin = null
}, },
handleMouseDown(origin, event) {
this.handleDown(origin, event.clientX, event.clientY)
},
handleTouchDown(origin, event) {
if (event.changedTouches.length) {
const touch = event.changedTouches[0]
this.handleDown(origin, touch.clientX, touch.clientY)
}
},
handleMouseMove(event) { handleMouseMove(event) {
this.handleMove(event.clientX, event.clientY)
},
handleTouchMove(event) {
if (event.changedTouches.length) {
const touch = event.changedTouches[0]
this.handleMove(touch.clientX, touch.clientY)
}
},
handleMove(clientX, clientY) {
if (!this.resize.origin) return if (!this.resize.origin) return
// if (!event.target.classList.contains('resize-' + this.resize.origin)) return // if (!event.target.classList.contains('resize-' + this.resize.origin)) return
// console.log(event.target) // console.log(event.target)
@ -136,49 +154,49 @@ export default {
let temp let temp
switch (this.resize.origin) { switch (this.resize.origin) {
case 'left': case 'left':
temp = event.clientX - this.resize.msDownX temp = clientX - this.resize.msDownX
bounds.w = -temp bounds.w = -temp
bounds.x = temp bounds.x = temp
break break
case 'right': case 'right':
bounds.w = event.clientX - this.resize.msDownX bounds.w = clientX - this.resize.msDownX
this.resize.msDownX += bounds.w this.resize.msDownX += bounds.w
break break
case 'top': case 'top':
temp = event.clientY - this.resize.msDownY temp = clientY - this.resize.msDownY
bounds.y = temp bounds.y = temp
bounds.h = -temp bounds.h = -temp
break break
case 'bottom': case 'bottom':
bounds.h = event.clientY - this.resize.msDownY bounds.h = clientY - this.resize.msDownY
this.resize.msDownY += bounds.h this.resize.msDownY += bounds.h
break break
case 'top-left': case 'top-left':
temp = event.clientX - this.resize.msDownX temp = clientX - this.resize.msDownX
bounds.w = -temp bounds.w = -temp
bounds.x = temp bounds.x = temp
temp = event.clientY - this.resize.msDownY temp = clientY - this.resize.msDownY
bounds.y = temp bounds.y = temp
bounds.h = -temp bounds.h = -temp
break break
case 'top-right': case 'top-right':
temp = event.clientY - this.resize.msDownY temp = clientY - this.resize.msDownY
bounds.y = temp bounds.y = temp
bounds.h = -temp bounds.h = -temp
bounds.w = event.clientX - this.resize.msDownX bounds.w = clientX - this.resize.msDownX
this.resize.msDownX += bounds.w this.resize.msDownX += bounds.w
break break
case 'bottom-left': case 'bottom-left':
temp = event.clientX - this.resize.msDownX temp = clientX - this.resize.msDownX
bounds.w = -temp bounds.w = -temp
bounds.x = temp bounds.x = temp
bounds.h = event.clientY - this.resize.msDownY bounds.h = clientY - this.resize.msDownY
this.resize.msDownY += bounds.h this.resize.msDownY += bounds.h
break break
case 'bottom-right': case 'bottom-right':
bounds.w = event.clientX - this.resize.msDownX bounds.w = clientX - this.resize.msDownX
this.resize.msDownX += bounds.w this.resize.msDownX += bounds.w
bounds.h = event.clientY - this.resize.msDownY bounds.h = clientY - this.resize.msDownY
this.resize.msDownY += bounds.h this.resize.msDownY += bounds.h
break break
} }
@ -187,9 +205,9 @@ export default {
bounds.h = window.innerHeight + bounds.h bounds.h = window.innerHeight + bounds.h
rendererSend(NAMES.winLyric.set_win_bounds, bounds) rendererSend(NAMES.winLyric.set_win_bounds, bounds)
}, },
handleMouseOver() { // handleMouseOver() {
// this.handleMouseUp() // // this.handleMouseUp()
}, // },
}, },
} }
</script> </script>

View File

@ -1,5 +1,6 @@
<template lang="pug"> <template lang="pug">
div(:class="[$style.lyric, { [$style.draging]: lyricEvent.isMsDown }, { [$style.lrcActiveZoom]: lrcConfig.style.isZoomActiveLrc } ]" :style="lrcStyles" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric") div(:class="[$style.lyric, { [$style.draging]: lyricEvent.isMsDown }, { [$style.lrcActiveZoom]: lrcConfig.style.isZoomActiveLrc } ]"
:style="lrcStyles" @wheel="handleWheel" @mousedown="handleLyricMouseDown" @touchstart="handleLyricTouchStart" ref="dom_lyric")
div(:class="$style.lyricSpace") div(:class="$style.lyricSpace")
div(:class="[$style.lyricText]" ref="dom_lyric_text") div(:class="[$style.lyricText]" ref="dom_lyric_text")
//- div(v-for="(info, index) in lyricLines" :key="index" :class="[$style.lineContent, lyric.line == index ? (lrcConfig.style.isZoomActiveLrc ? $style.lrcActiveZoom : $style.lrcActive) : null]") //- div(v-for="(info, index) in lyricLines" :key="index" :class="[$style.lineContent, lyric.line == index ? (lrcConfig.style.isZoomActiveLrc ? $style.lrcActiveZoom : $style.lrcActive) : null]")
@ -167,12 +168,16 @@ export default {
mounted() { mounted() {
document.addEventListener('mousemove', this.handleMouseMsMove) document.addEventListener('mousemove', this.handleMouseMsMove)
document.addEventListener('mouseup', this.handleMouseMsUp) document.addEventListener('mouseup', this.handleMouseMsUp)
document.addEventListener('touchmove', this.handleTouchMove)
document.addEventListener('touchend', this.handleMouseMsUp)
rendererSend(NAMES.winLyric.get_lyric_info, 'info') rendererSend(NAMES.winLyric.get_lyric_info, 'info')
}, },
beforeDestroy() { beforeDestroy() {
this.clearLyricScrollTimeout() this.clearLyricScrollTimeout()
document.removeEventListener('mousemove', this.handleMouseMsMove) document.removeEventListener('mousemove', this.handleMouseMsMove)
document.removeEventListener('mouseup', this.handleMouseMsUp) document.removeEventListener('mouseup', this.handleMouseMsUp)
document.removeEventListener('touchmove', this.handleTouchMove)
document.removeEventListener('touchend', this.handleMouseMsUp)
}, },
methods: { methods: {
handleSetInfo({ type, data }) { handleSetInfo({ type, data }) {
@ -234,49 +239,59 @@ export default {
let dom_p = this.dom_lines[this.lyric.line] let dom_p = this.dom_lines[this.lyric.line]
cancelScrollFn = scrollTo(this.$refs.dom_lyric, dom_p ? (dom_p.offsetTop - this.$refs.dom_lyric.clientHeight * 0.5 + dom_p.clientHeight / 2) : 0) cancelScrollFn = scrollTo(this.$refs.dom_lyric, dom_p ? (dom_p.offsetTop - this.$refs.dom_lyric.clientHeight * 0.5 + dom_p.clientHeight / 2) : 0)
}, },
handleLyricMouseDown(e) { handleLyricDown(target, x, y) {
if (e.target.classList.contains('font') || if (target.classList.contains('font') ||
e.target.parentNode.classList.contains('font') || target.parentNode.classList.contains('font') ||
e.target.classList.contains('translation') || target.classList.contains('translation') ||
e.target.parentNode.classList.contains('translation')) { target.parentNode.classList.contains('translation')) {
this.lyricEvent.isMsDown = true this.lyricEvent.isMsDown = true
this.lyricEvent.msDownY = e.clientY this.lyricEvent.msDownY = y
this.lyricEvent.msDownScrollY = this.$refs.dom_lyric.scrollTop this.lyricEvent.msDownScrollY = this.$refs.dom_lyric.scrollTop
} else { } else {
this.winEvent.isMsDown = true this.winEvent.isMsDown = true
this.winEvent.msDownX = e.clientX this.winEvent.msDownX = x
this.winEvent.msDownY = e.clientY this.winEvent.msDownY = y
}
},
handleLyricMouseDown(e) {
this.handleLyricDown(e.target, e.clientX, e.clientY)
},
handleLyricTouchStart(e) {
if (e.changedTouches.length) {
const touch = e.changedTouches[0]
this.handleLyricDown(e.target, touch.clientX, touch.clientY)
} }
}, },
handleMouseMsUp(e) { handleMouseMsUp(e) {
this.lyricEvent.isMsDown = false this.lyricEvent.isMsDown = false
this.winEvent.isMsDown = false this.winEvent.isMsDown = false
}, },
handleMouseMsMove(e) { handleMove(x, y) {
if (this.lyricEvent.isMsDown) { if (this.lyricEvent.isMsDown) {
if (!this.lyricEvent.isStopScroll) this.lyricEvent.isStopScroll = true if (!this.lyricEvent.isStopScroll) this.lyricEvent.isStopScroll = true
if (cancelScrollFn) { if (cancelScrollFn) {
cancelScrollFn() cancelScrollFn()
cancelScrollFn = null cancelScrollFn = null
} }
this.$refs.dom_lyric.scrollTop = this.lyricEvent.msDownScrollY + this.lyricEvent.msDownY - e.clientY this.$refs.dom_lyric.scrollTop = this.lyricEvent.msDownScrollY + this.lyricEvent.msDownY - y
this.startLyricScrollTimeout() this.startLyricScrollTimeout()
} else if (this.winEvent.isMsDown) { } else if (this.winEvent.isMsDown) {
rendererSend(NAMES.winLyric.set_win_bounds, { rendererSend(NAMES.winLyric.set_win_bounds, {
x: e.clientX - this.winEvent.msDownX, x: x - this.winEvent.msDownX,
y: e.clientY - this.winEvent.msDownY, y: y - this.winEvent.msDownY,
w: window.innerWidth, w: window.innerWidth,
h: window.innerHeight, h: window.innerHeight,
}) })
} }
},
// if (this.volumeEvent.isMsDown) { handleTouchMove(e) {
// let val = this.volumeEvent.msDownValue + (e.clientX - this.volumeEvent.msDownX) / 70 if (e.changedTouches.length) {
// this.volume = val < 0 ? 0 : val > 1 ? 1 : val const touch = e.changedTouches[0]
// if (this.audio) this.audio.volume = this.volume this.handleMove(touch.clientX, touch.clientY)
// } }
},
// console.log(val) handleMouseMsMove(e) {
this.handleMove(e.clientX, e.clientY)
}, },
startLyricScrollTimeout() { startLyricScrollTimeout() {
this.clearLyricScrollTimeout() this.clearLyricScrollTimeout()

View File

@ -29,6 +29,7 @@ import music from './utils/music'
import { throttle, openUrl, compareVer, getPlayList, parseUrlParams, saveSetting } from './utils' import { throttle, openUrl, compareVer, getPlayList, parseUrlParams, saveSetting } from './utils'
import { base as eventBaseName, sync as eventSyncName } from './event/names' import { base as eventBaseName, sync as eventSyncName } from './event/names'
import apiSourceInfo from './utils/music/api-source-info' import apiSourceInfo from './utils/music/api-source-info'
import { initListPosition, initListPrevSelectId } from '@renderer/utils/data'
window.ELECTRON_DISABLE_SECURITY_WARNINGS = process.env.ELECTRON_DISABLE_SECURITY_WARNINGS window.ELECTRON_DISABLE_SECURITY_WARNINGS = process.env.ELECTRON_DISABLE_SECURITY_WARNINGS
@ -147,7 +148,7 @@ export default {
}, },
downloadList: { downloadList: {
handler(n) { handler(n) {
this.saveDownloadList(n) this.saveDownloadList(window.downloadListFull)
}, },
deep: true, deep: true,
}, },
@ -338,6 +339,8 @@ export default {
return Promise.all([ return Promise.all([
this.initMyList(), // this.initMyList(), //
this.initSearchHistoryList(), // this.initSearchHistoryList(), //
initListPosition(), //
initListPrevSelectId(), //
]) ])
// this.initDownloadList() // // this.initDownloadList() //
}, },
@ -370,12 +373,17 @@ export default {
}, },
initDownloadList(downloadList) { initDownloadList(downloadList) {
if (downloadList) { if (downloadList) {
downloadList.forEach(item => { downloadList = downloadList.filter(item => item && item.key && item.musicInfo)
for (const item of downloadList) {
if (item.name == null) {
item.name = `${item.musicInfo.name} - ${item.musicInfo.singer}`
item.songmid = item.musicInfo.songmid
}
if (item.status == this.downloadStatus.RUN || item.status == this.downloadStatus.WAITING) { if (item.status == this.downloadStatus.RUN || item.status == this.downloadStatus.WAITING) {
item.status = this.downloadStatus.PAUSE item.status = this.downloadStatus.PAUSE
item.statusText = '暂停下载' item.statusText = '暂停下载'
} }
}) }
this.updateDownloadList(downloadList) this.updateDownloadList(downloadList)
} }
}, },
@ -396,10 +404,17 @@ export default {
if (!info) return if (!info) return
if (info.index < 0) return if (info.index < 0) return
if (info.listId) { if (info.listId) {
const list = window.allList[info.listId] if (info.listId == 'download') {
// console.log(list) const list = this.downloadList
if (!list || !list.list[info.index]) return // console.log(list)
info.list = list.list if (!list || !list[info.index]) return
info.list = list
} else {
const list = window.allList[info.listId]
// console.log(list)
if (!list || !list.list[info.index]) return
info.list = list.list
}
} }
if (!info.list || !info.list[info.index]) return if (!info.list || !info.list[info.index]) return
window.restorePlayInfo = info window.restorePlayInfo = info

View File

@ -1,5 +1,7 @@
@import './reset.less'; @import './reset.less';
@import './animate.less'; @import './animate.less';
@import './layout.less';
*, *::after, *::before { *, *::after, *::before {
-webkit-user-drag: none; -webkit-user-drag: none;
} }
@ -72,6 +74,45 @@ table {
} }
} }
.list {
width: 100%;
overflow: hidden;
color: @color-theme_2-font;
.list-item {
height: 100%;
display: flex;
flex-flow: row nowrap;
align-items: center;
// border-top: 1px solid rgba(0, 0, 0, 0.12);
transition: background-color 0.2s ease;
border-bottom: 1px solid @color-theme_2-line;
box-sizing: border-box;
&:hover {
background-color: @color-theme_2-hover;
}
&.active {
background-color: @color-theme_2-active;
}
&.selected {
background-color: @color-theme_2-hover;
}
.list-item-cell {
flex: none;
padding: 0 6px;
position: relative;
transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
font-size: 13px;
line-height: 16px;
vertical-align: middle;
box-sizing: border-box;
.mixin-ellipsis-1;
&.auto {
flex: auto;
}
}
}
}
.badge { .badge {
display: inline-block; display: inline-block;
@ -240,6 +281,22 @@ each(@themes, {
} }
} }
.list {
color: ~'@{color-@{value}-theme_2-font}';
.list-item {
border-bottom-color: ~'@{color-@{value}-theme_2-line}';
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&.active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
&.selected {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
}
}
input, textarea { input, textarea {
&::placeholder { &::placeholder {
color: ~'@{color-@{value}-theme_2-font-label}'; color: ~'@{color-@{value}-theme_2-font-label}';

View File

@ -35,7 +35,7 @@ div(:class="$style.aside")
dl dl
//- dt {{$t('core.aside.my_music')}} //- dt {{$t('core.aside.my_music')}}
dd dd
router-link(:active-class="$style.active" :tips="$t('core.aside.my_list')" :to="`list?id=${setting.list.prevSelectListId || defaultList.id}`") router-link(:active-class="$style.active" to="list" :tips="$t('core.aside.my_list')")
div(:class="$style.icon") div(:class="$style.icon")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 444.87 391.18' space='preserve') svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 444.87 391.18' space='preserve')
use(xlink:href='#icon-love') use(xlink:href='#icon-love')

View File

@ -217,5 +217,10 @@ svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/19
g#icon-comment(fill='currentColor') g#icon-comment(fill='currentColor')
// 0 0 24 24 // 0 0 24 24
path(d='M16 11H8V9H16V11M22 4V16C22 17.11 21.11 18 20 18H13.9L10.2 21.71C10 21.9 9.75 22 9.5 22H9C8.45 22 8 21.55 8 21V18H4C2.9 18 2 17.11 2 16V4C2 2.89 2.9 2 4 2H20C21.11 2 22 2.9 22 4M20 4H4V16H10V19.08L13.08 16H20V4') path(d='M16 11H8V9H16V11M22 4V16C22 17.11 21.11 18 20 18H13.9L10.2 21.71C10 21.9 9.75 22 9.5 22H9C8.45 22 8 21.55 8 21V18H4C2.9 18 2 17.11 2 16V4C2 2.89 2.9 2 4 2H20C21.11 2 22 2.9 22 4M20 4H4V16H10V19.08L13.08 16H20V4')
g#icon-text(fill='currentColor')
// 0 0 24 24
path(fill='currentColor', d='M21,6V8H3V6H21M3,18H12V16H3V18M3,13H21V11H3V13Z')
</template> </template>

View File

@ -5,7 +5,7 @@ div(:class="$style.player")
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='102%' width='100%' viewBox='0 0 60 60' space='preserve') svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='102%' width='100%' viewBox='0 0 60 60' space='preserve')
use(:xlink:href='`#${$style.iconPic}`') use(:xlink:href='`#${$style.iconPic}`')
div(:class="$style.middle") div(:class="$style.middle")
div(:class="$style.middleContainer" v-if="!isShowPlayerDetail") div(:class="$style.middleContainer")
div(:class="$style.column1") div(:class="$style.column1")
div(:class="$style.container") div(:class="$style.container")
div(:class="$style.title" @click="handleCopy(title)" :tips="title + $t('core.player.copy_title')") {{title}} div(:class="$style.title" @click="handleCopy(title)" :tips="title + $t('core.player.copy_title')") {{title}}
@ -64,14 +64,14 @@ div(:class="$style.player")
//- transition(enter-active-class="animated lightSpeedIn" //- transition(enter-active-class="animated lightSpeedIn"
transition(enter-active-class="animated lightSpeedIn" transition(enter-active-class="animated lightSpeedIn"
leave-active-class="animated slideOutDown") leave-active-class="animated slideOutDown")
core-player-detail(v-if="isShowPlayerDetail" :musicInfo="listId == 'download' ? targetSong.musicInfo : targetSong" core-player-detail(v-if="isShowPlayerDetail" :visible.sync="isShowPlayerDetail" :musicInfo="currentMusicInfo"
:lyric="lyric" :list="list" :listId="listId" :lyric="lyric" :list="list" :listId="listId"
:playInfo="{ nowPlayTimeStr, maxPlayTimeStr, progress, nowPlayTime, status }" :playInfo="{ nowPlayTimeStr, maxPlayTimeStr, progress, nowPlayTime, status }"
:isPlay="isPlay" @action="handlePlayDetailAction" :isPlay="isPlay" @action="handlePlayDetailAction"
:nextTogglePlayName="nextTogglePlayName" :nextTogglePlayName="nextTogglePlayName"
@toggle-next-play-mode="toggleNextPlayMode" @add-music-to="addMusicTo") @toggle-next-play-mode="toggleNextPlayMode" @add-music-to="addMusicTo")
material-list-add-modal(:show="isShowAddMusicTo" :musicInfo="listId == 'download' ? targetSong.musicInfo : targetSong" @close="isShowAddMusicTo = false") material-list-add-modal(:show="isShowAddMusicTo" :musicInfo="currentMusicInfo" @close="isShowAddMusicTo = false")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' style="display: none;") svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' style="display: none;")
defs defs
g(:id="$style.iconPic") g(:id="$style.iconPic")
@ -87,7 +87,7 @@ div(:class="$style.player")
<script> <script>
import Lyric from '@renderer/utils/lyric-font-player' import Lyric from '@renderer/utils/lyric-font-player'
import { rendererSend, rendererOn, NAMES } from '../../../common/ipc' import { rendererSend, rendererOn, NAMES, rendererInvoke } from '../../../common/ipc'
import { formatPlayTime2, getRandom, checkPath, setTitle, clipboardWriteText, debounce, throttle, assertApiSupport } from '../../utils' import { formatPlayTime2, getRandom, checkPath, setTitle, clipboardWriteText, debounce, throttle, assertApiSupport } from '../../utils'
import { mapGetters, mapActions, mapMutations } from 'vuex' import { mapGetters, mapActions, mapMutations } from 'vuex'
import { requestMsg } from '../../utils/message' import { requestMsg } from '../../utils/message'
@ -121,6 +121,7 @@ export default {
singer: '', singer: '',
album: '', album: '',
}, },
currentMusicInfo: {},
pregessWidth: 0, pregessWidth: 0,
lyric: { lyric: {
lines: [], lines: [],
@ -141,11 +142,12 @@ export default {
playTime: 0, playTime: 0,
}, },
isShowAddMusicTo: false, isShowAddMusicTo: false,
isShowPlayerDetail: false,
} }
}, },
computed: { computed: {
...mapGetters(['setting']), ...mapGetters(['setting']),
...mapGetters('player', ['list', 'changePlay', 'playMusicInfo', 'isShowPlayerDetail', 'playInfo', 'playedList']), ...mapGetters('player', ['list', 'changePlay', 'playMusicInfo', 'playInfo', 'playedList']),
// pic() { // pic() {
// return this.musicInfo.img ? this.musicInfo.img : '' // return this.musicInfo.img ? this.musicInfo.img : ''
// }, // },
@ -340,7 +342,6 @@ export default {
'setPlayMusicInfo', 'setPlayMusicInfo',
'setPlayIndex', 'setPlayIndex',
'resetChangePlay', 'resetChangePlay',
'visiblePlayerDetail',
'clearPlayedList', 'clearPlayedList',
'setPlayedList', 'setPlayedList',
]), ]),
@ -395,7 +396,7 @@ export default {
// console.log(this.retryNum) // console.log(this.retryNum)
if (!this.restorePlayTime) this.restorePlayTime = audio.currentTime // if (!this.restorePlayTime) this.restorePlayTime = audio.currentTime //
this.retryNum++ this.retryNum++
this.setUrl(this.targetSong, true) this.setUrl(this.currentMusicInfo, true)
this.status = this.statusText = this.$t('core.player.refresh_url') this.status = this.statusText = this.$t('core.player.refresh_url')
return return
} }
@ -416,6 +417,8 @@ export default {
if (!this.targetSong.interval && this.listId != 'download') { if (!this.targetSong.interval && this.listId != 'download') {
this.updateMusicInfo({ listId: this.listId, id: this.targetSong.songmid, musicInfo: this.targetSong, data: { interval: formatPlayTime2(this.maxPlayTime) } }) this.updateMusicInfo({ listId: this.listId, id: this.targetSong.songmid, musicInfo: this.targetSong, data: { interval: formatPlayTime2(this.maxPlayTime) } })
} }
this.updatePositionState()
}) })
audio.addEventListener('loadstart', () => { audio.addEventListener('loadstart', () => {
console.log('loadstart') console.log('loadstart')
@ -430,6 +433,7 @@ export default {
audio.currentTime = playTime audio.currentTime = playTime
} }
this.clearBufferTimeout() this.clearBufferTimeout()
this.updatePositionState()
// if (this.musicInfo.lrc) window.lrc.play(audio.currentTime * 1000) // if (this.musicInfo.lrc) window.lrc.play(audio.currentTime * 1000)
this.status = this.statusText = '' this.status = this.statusText = ''
@ -483,9 +487,8 @@ export default {
}, },
async play() { async play() {
this.clearDelayNextTimeout() this.clearDelayNextTimeout()
this.updateMediaSessionInfo()
const targetSong = this.targetSong let targetSong = this.targetSong
if (this.setting.player.togglePlayMethod == 'random' && !this.playMusicInfo.isTempPlay) this.setPlayedList(this.playMusicInfo) if (this.setting.player.togglePlayMethod == 'random' && !this.playMusicInfo.isTempPlay) this.setPlayedList(this.playMusicInfo)
this.retryNum = 0 this.retryNum = 0
@ -497,24 +500,26 @@ export default {
if (!await checkPath(filePath) || !targetSong.isComplate || /\.ape$/.test(filePath)) { if (!await checkPath(filePath) || !targetSong.isComplate || /\.ape$/.test(filePath)) {
return this.list.length == 1 ? null : this.playNext() return this.list.length == 1 ? null : this.playNext()
} }
this.musicInfo.songmid = targetSong.musicInfo.songmid this.currentMusicInfo = targetSong = window.downloadListFullMap.get(targetSong.key).musicInfo
this.musicInfo.singer = targetSong.musicInfo.singer this.musicInfo.songmid = targetSong.songmid
this.musicInfo.name = targetSong.musicInfo.name this.musicInfo.singer = targetSong.singer
this.musicInfo.name = targetSong.name
this.musicInfo.album = targetSong.albumName this.musicInfo.album = targetSong.albumName
audio.src = filePath audio.src = filePath
// console.log(filePath) // console.log(filePath)
this.setImg(targetSong.musicInfo)
this.setLrc(targetSong.musicInfo)
} else { } else {
// if (!this.assertApiSupport(targetSong.source)) return this.playNext() // if (!this.assertApiSupport(targetSong.source)) return this.playNext()
this.currentMusicInfo = targetSong
this.musicInfo.songmid = targetSong.songmid this.musicInfo.songmid = targetSong.songmid
this.musicInfo.singer = targetSong.singer this.musicInfo.singer = targetSong.singer
this.musicInfo.name = targetSong.name this.musicInfo.name = targetSong.name
this.musicInfo.album = targetSong.albumName this.musicInfo.album = targetSong.albumName
this.setUrl(targetSong) this.setUrl(targetSong)
this.setImg(targetSong)
this.setLrc(targetSong)
} }
this.updateMediaSessionInfo()
this.setImg(targetSong)
this.setLrc(targetSong)
this.handleUpdateWinLyricInfo('music_info', { this.handleUpdateWinLyricInfo('music_info', {
songmid: this.musicInfo.songmid, songmid: this.musicInfo.songmid,
singer: this.musicInfo.singer, singer: this.musicInfo.singer,
@ -592,11 +597,25 @@ export default {
window.getComputedStyle(this.$refs.dom_progress, null).width, window.getComputedStyle(this.$refs.dom_progress, null).width,
) )
}, },
togglePlay() { async togglePlay() {
if (!audio.src) { if (!audio.src) {
if (this.restorePlayTime != null) { if (this.restorePlayTime != null) {
// if (!this.assertApiSupport(this.targetSong.source)) return this.playNext() if (this.listId == 'download') {
this.setUrl(this.targetSong) const filePath = path.join(this.setting.download.savePath, this.targetSong.fileName)
// console.log(filePath)
if (!await checkPath(filePath) || !this.targetSong.isComplate || /\.ape$/.test(filePath)) {
if (this.list.length == 1) {
this.handleRemoveMusic()
} else {
this.playNext()
}
return
}
audio.src = filePath
} else {
// if (!this.assertApiSupport(this.targetSong.source)) return this.playNext()
this.setUrl(this.targetSong)
}
} }
return return
} }
@ -634,6 +653,7 @@ export default {
audio.src = this.musicInfo.url = url audio.src = this.musicInfo.url = url
}).catch(err => { }).catch(err => {
// console.log('err', err.message) // console.log('err', err.message)
if (targetSong !== this.targetSong || this.isPlay) return
if (err.message == requestMsg.cancelRequest) return if (err.message == requestMsg.cancelRequest) return
if (!isRetryed) return this.setUrl(targetSong, isRefresh, true) if (!isRetryed) return this.setUrl(targetSong, isRefresh, true)
this.status = this.statusText = err.message this.status = this.statusText = err.message
@ -655,12 +675,31 @@ export default {
setLrc(targetSong) { setLrc(targetSong) {
this.getLrc(targetSong).then(({ lyric, tlyric, lxlyric }) => { this.getLrc(targetSong).then(({ lyric, tlyric, lxlyric }) => {
if (targetSong.songmid !== this.musicInfo.songmid) return if (targetSong.songmid !== this.musicInfo.songmid) return
this.musicInfo.lrc = lyric return (
this.musicInfo.tlrc = tlyric global.i18n.locale == 'zh-tw'
this.musicInfo.lxlrc = lxlyric ? Promise.all([
}).catch(() => { lyric
? rendererInvoke(NAMES.mainWindow.lang_s2t, Buffer.from(lyric).toString('base64')).then(b64 => Buffer.from(b64, 'base64').toString())
: Promise.resolve(''),
tlyric
? rendererInvoke(NAMES.mainWindow.lang_s2t, Buffer.from(tlyric).toString('base64')).then(b64 => Buffer.from(b64, 'base64').toString())
: Promise.resolve(''),
lxlyric
? rendererInvoke(NAMES.mainWindow.lang_s2t, Buffer.from(lxlyric).toString('base64')).then(b64 => Buffer.from(b64, 'base64').toString())
: Promise.resolve(''),
])
: Promise.resolve([lyric, tlyric, lxlyric])
).then(([lyric, tlyric, lxlyric]) => {
this.musicInfo.lrc = lyric
this.musicInfo.tlrc = tlyric
this.musicInfo.lxlrc = lxlyric
})
}).catch((err) => {
console.log(err)
if (targetSong.songmid !== this.musicInfo.songmid) return
this.status = this.statusText = this.$t('core.player.lyric_error') this.status = this.statusText = this.$t('core.player.lyric_error')
}).finally(() => { }).finally(() => {
if (targetSong.songmid !== this.musicInfo.songmid) return
this.handleUpdateWinLyricInfo('lyric', { lrc: this.musicInfo.lrc, tlrc: this.musicInfo.tlrc, lxlrc: this.musicInfo.lxlrc }) this.handleUpdateWinLyricInfo('lyric', { lrc: this.musicInfo.lrc, tlrc: this.musicInfo.tlrc, lxlrc: this.musicInfo.lxlrc })
this.setLyric() this.setLyric()
}) })
@ -684,6 +723,7 @@ export default {
this.lyric.text = 0 this.lyric.text = 0
this.handleUpdateWinLyricInfo('lines', []) this.handleUpdateWinLyricInfo('lines', [])
this.handleUpdateWinLyricInfo('line', 0) this.handleUpdateWinLyricInfo('line', 0)
this.currentMusicInfo = {}
}, },
sendProgressEvent(status, mode) { sendProgressEvent(status, mode) {
// console.log(status) // console.log(status)
@ -740,7 +780,7 @@ export default {
this.setProgressWidth() this.setProgressWidth()
}, },
handleToMusicLocation() { handleToMusicLocation() {
if (!this.listId || this.listId == '__temp__' || this.listId == 'download') return if (!this.listId || this.listId == '__temp__' || this.listId == 'download' || !this.currentMusicInfo.songmid) return
if (this.playIndex == -1) return if (this.playIndex == -1) return
this.$router.push({ this.$router.push({
path: 'list', path: 'list',
@ -751,8 +791,8 @@ export default {
}) })
}, },
showPlayerDetail() { showPlayerDetail() {
if (!this.targetSong) return if (!this.currentMusicInfo.songmid) return
this.visiblePlayerDetail(true) this.isShowPlayerDetail = true
}, },
handleTransitionEnd(e) { handleTransitionEnd(e) {
// console.log(e) // console.log(e)
@ -849,7 +889,7 @@ export default {
this.playNext() this.playNext()
break break
case 'progress': case 'progress':
this.handleSetProgress(data) this.setProgress(data * this.maxPlayTime)
break break
case 'volume': case 'volume':
break break
@ -895,8 +935,18 @@ export default {
if (!this.musicInfo.songmid) return if (!this.musicInfo.songmid) return
this.isShowAddMusicTo = true this.isShowAddMusicTo = true
}, },
handleRestorePlay(restorePlayInfo) { async handleRestorePlay(restorePlayInfo) {
let musicInfo = this.list[restorePlayInfo.index] let musicInfo
if (this.listId == 'download') {
this.currentMusicInfo = musicInfo = window.downloadListFullMap.get(this.list[restorePlayInfo.index].key).musicInfo
// console.log(filePath)
} else {
// if (!this.assertApiSupport(targetSong.source)) return this.playNext()
musicInfo = this.list[restorePlayInfo.index]
this.currentMusicInfo = musicInfo
}
this.musicInfo.songmid = musicInfo.songmid this.musicInfo.songmid = musicInfo.songmid
this.musicInfo.singer = musicInfo.singer this.musicInfo.singer = musicInfo.singer
this.musicInfo.name = musicInfo.name this.musicInfo.name = musicInfo.name
@ -919,13 +969,20 @@ export default {
}, },
updateMediaSessionInfo() { updateMediaSessionInfo() {
const mediaMetadata = { const mediaMetadata = {
title: this.targetSong.name, title: this.currentMusicInfo.name,
artist: this.targetSong.singer, artist: this.currentMusicInfo.singer,
album: this.targetSong.albumName, album: this.currentMusicInfo.albumName,
} }
if (this.targetSong.img) mediaMetadata.artwork = [{ src: this.targetSong.img }] if (this.currentMusicInfo.img) mediaMetadata.artwork = [{ src: this.currentMusicInfo.img }]
navigator.mediaSession.metadata = new window.MediaMetadata(mediaMetadata) navigator.mediaSession.metadata = new window.MediaMetadata(mediaMetadata)
}, },
updatePositionState() {
navigator.mediaSession.setPositionState({
duration: audio.duration,
playbackRate: audio.playbackRate,
position: audio.currentTime,
})
},
registerMediaSessionHandler() { registerMediaSessionHandler() {
// navigator.mediaSession.setActionHandler('play', () => { // navigator.mediaSession.setActionHandler('play', () => {
// if (this.isPlay || !this.playMusicInfo) return // if (this.isPlay || !this.playMusicInfo) return

View File

@ -4,7 +4,7 @@
//- div(:class="$style.bg2") //- div(:class="$style.bg2")
div(:class="$style.header") div(:class="$style.header")
div(:class="$style.controBtn") div(:class="$style.controBtn")
button(type="button" :class="$style.hide" :tips="$t('core.player.hide_detail')" @click="visiblePlayerDetail(false)") button(type="button" :class="$style.hide" :tips="$t('core.player.hide_detail')" @click="hide")
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='80%' viewBox='0 0 30.727 30.727' space='preserve') svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='80%' viewBox='0 0 30.727 30.727' space='preserve')
use(xlink:href='#icon-window-hide') use(xlink:href='#icon-window-hide')
button(type="button" :class="$style.min" :tips="$t('core.toolbar.min')" @click="min") button(type="button" :class="$style.min" :tips="$t('core.toolbar.min')" @click="min")
@ -27,17 +27,28 @@
p(v-if="musicInfo.album") {{$t('core.player.album')}}{{musicInfo.album}} p(v-if="musicInfo.album") {{$t('core.player.album')}}{{musicInfo.album}}
div(:class="$style.right") div(:class="$style.right")
div(:class="[$style.lyric, lyricEvent.isMsDown ? $style.draging : null]" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric") div(:class="[$style.lyric, { [$style.draging]: lyricEvent.isMsDown }]" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric")
div(:class="$style.lyricSpace") div(:class="$style.lyricSpace")
div(:class="[$style.lyricText]" ref="dom_lyric_text") div(:class="[$style.lyricText]" ref="dom_lyric_text")
//- p(v-for="(info, index) in lyricLines" :key="index" :class="lyric.line == index ? $style.lrcActive : null") {{info.text}} //- p(v-for="(info, index) in lyricLines" :key="index" :class="lyric.line == index ? $style.lrcActive : null") {{info.text}}
div(:class="$style.lyricSpace") div(:class="$style.lyricSpace")
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.lyricSpace")
div(v-for="(info, index) in lyricLines" :key="index" :class="[$style.lyricSelectline, { [$style.lrcActive]: lyric.line == index }]")
span {{info.text}}
br(v-if="info.translation")
span(:class="$style.lyricSelectlineTransition") {{info.translation}}
//- div(:class="$style.lyricSpace")
material-music-comment(:class="$style.comment" :titleFormat="this.setting.download.fileName" :musicInfo="musicInfo" v-model="isShowComment") material-music-comment(:class="$style.comment" :titleFormat="this.setting.download.fileName" :musicInfo="musicInfo" v-model="isShowComment")
div(:class="$style.footer") div(:class="$style.footer")
div(:class="$style.footerLeft") div(:class="$style.footerLeft")
div(:class="$style.footerLeftControlBtns") div(:class="$style.footerLeftControlBtns")
div(:class="[$style.footerLeftControlBtn, { [$style.active]: isShowLrcSelectContent }]" @click="isShowLrcSelectContent = !isShowLrcSelectContent" :tips="$t('core.player.lyric_select')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='95%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-text')
div(:class="[$style.footerLeftControlBtn, isShowComment ? $style.active : null]" @click="isShowComment = !isShowComment" :tips="$t('core.player.comment_show')") div(:class="[$style.footerLeftControlBtn, isShowComment ? $style.active : null]" @click="isShowComment = !isShowComment" :tips="$t('core.player.comment_show')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='95%' 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' width='95%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-comment') use(xlink:href='#icon-comment')
@ -85,14 +96,18 @@
<script> <script>
import { mapGetters, mapMutations } from 'vuex' import { mapGetters } from 'vuex'
import { base as eventBaseName } from '../../event/names' import { base as eventBaseName } from '../../event/names'
import { scrollTo } from '../../utils' import { clipboardWriteText, scrollTo } from '../../utils'
let cancelScrollFn = null let cancelScrollFn = null
export default { export default {
props: { props: {
visible: {
type: Boolean,
required: true,
},
musicInfo: { musicInfo: {
type: Object, type: Object,
default() { default() {
@ -230,32 +245,40 @@ export default {
lyricLines: [], lyricLines: [],
isSetedLines: false, isSetedLines: false,
isShowComment: false, isShowComment: false,
isShowLrcSelectContent: false,
} }
}, },
mounted() { mounted() {
this.$nextTick(() => { this.$nextTick(() => {
this.setProgressWidth() this.setProgressWidth()
}) })
document.addEventListener('mousemove', this.handleMouseMsMove) this.listenEvent()
document.addEventListener('mouseup', this.handleMouseMsUp)
window.addEventListener('resize', this.handleResize)
// console.log('object', this.$refs.dom_lyric_text) // console.log('object', this.$refs.dom_lyric_text)
this.setLyric(this.lyricLines) this.setLyric(this.lyricLines)
}, },
beforeDestroy() { beforeDestroy() {
this.unlistenEvent()
this.clearLyricScrollTimeout() this.clearLyricScrollTimeout()
document.removeEventListener('mousemove', this.handleMouseMsMove)
document.removeEventListener('mouseup', this.handleMouseMsUp)
window.removeEventListener('resize', this.handleResize)
}, },
computed: { computed: {
...mapGetters(['setting']), ...mapGetters(['setting']),
...mapGetters('player', ['isShowPlayerDetail']),
}, },
methods: { methods: {
...mapMutations('player', [ hide() {
'visiblePlayerDetail', this.$emit('update:visible', false)
]), },
listenEvent() {
document.addEventListener('mousemove', this.handleMouseMsMove)
document.addEventListener('mouseup', this.handleMouseMsUp)
window.addEventListener('resize', this.handleResize)
},
unlistenEvent() {
document.removeEventListener('mousemove', this.handleMouseMsMove)
document.removeEventListener('mouseup', this.handleMouseMsUp)
window.removeEventListener('resize', this.handleResize)
},
setLyric(lines) { setLyric(lines) {
const dom_lines = document.createDocumentFragment() const dom_lines = document.createDocumentFragment()
for (const line of lines) { for (const line of lines) {
@ -282,7 +305,7 @@ export default {
setProgress(event) { setProgress(event) {
this.$emit('action', { this.$emit('action', {
type: 'progress', type: 'progress',
data: event, data: event.offsetX / this.pregessWidth,
}) })
}, },
setProgressWidth() { setProgressWidth() {
@ -296,7 +319,7 @@ export default {
return return
} }
this.clickTime = 0 this.clickTime = 0
this.visiblePlayerDetail(false) this.hide()
}, },
handleLyricMouseDown(e) { handleLyricMouseDown(e) {
// console.log(e) // console.log(e)
@ -359,6 +382,12 @@ export default {
close() { close() {
window.eventHub.$emit(eventBaseName.close) window.eventHub.$emit(eventBaseName.close)
}, },
handleCopySelectText() {
let str = window.getSelection().toString()
str = str.trim()
if (!str.length) return
clipboardWriteText(str)
},
}, },
} }
</script> </script>
@ -485,7 +514,8 @@ export default {
min-height: 0; min-height: 0;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
padding: 0 30px; margin: 0 30px;
position: relative;
&.showComment { &.showComment {
.left { .left {
@ -501,13 +531,13 @@ export default {
} }
} }
.comment { .comment {
flex-basis: 50%;
opacity: 1; opacity: 1;
transform: scaleX(1);
} }
} }
} }
.left { .left {
flex: 40%; flex: 0 0 40%;
display: flex; display: flex;
flex-flow: column nowrap; flex-flow: column nowrap;
align-items: center; align-items: center;
@ -635,19 +665,51 @@ export default {
// transition: @transition-theme !important; // transition: @transition-theme !important;
// transition-property: color, font-size; // 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 { .lyric-space {
height: 70%; height: 70%;
} }
.lrc-active {
color: @color-theme;
font-size: 1.2em;
}
.comment { .comment {
flex: 0 0 0; position: absolute;
opacity: 0; right: 0;
top: 0;
width: 50%;
height: 100%;
opacity: 1;
margin-left: 10px; margin-left: 10px;
transform: scaleX(0);
} }
.footer { .footer {
@ -853,6 +915,13 @@ each(@themes, {
// .lrc-active { // .lrc-active {
// color: ~'@{color-@{value}-theme}'; // 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 { .footerLeftControlBtns {
color: ~'@{color-@{value}-theme_2-font}'; color: ~'@{color-@{value}-theme_2-font}';
} }

View File

@ -0,0 +1,257 @@
<template lang="pug">
Modal(:show="visible" @close="$emit('update:visible', false)" bg-close)
div(:class="$style.header")
h2 {{listInfo.name}}
main.scroll(:class="$style.main")
ul(ref="dom_list" v-if="duplicateList.length" :class="$style.list")
li(v-for="(item, index) in duplicateList" :key="item.songmid" :class="$style.listItem")
div(:class="$style.num") {{item.index + 1}}
div(:class="$style.text")
h3(:class="$style.text") {{item.musicInfo.name}} - {{item.musicInfo.singer}}
h3(v-if="item.musicInfo.albumName" :class="[$style.text, $style.albumName]") {{item.musicInfo.albumName}}
div(:class="$style.label") {{item.musicInfo.source}}
div(:class="$style.label") {{item.musicInfo.interval}}
div(:class="$style.btns")
button(type="button" @click="handlePlay(index)" :class="$style.btn")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='50%' viewBox='0 0 287.386 287.386' space='preserve' v-once)
use(xlink:href='#icon-testPlay')
button(type="button" @click="handleRemove(index)" :class="$style.btn")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='50%' viewBox='0 0 212.982 212.982' space='preserve' v-once)
use(xlink:href='#icon-delete')
div(:class="$style.noItem" v-else)
p(v-text="$t('view.list.no_item')")
</template>
<script>
import { mapMutations } from 'vuex'
import Modal from './Modal'
import Btn from './Btn'
export default {
props: {
visible: {
type: Boolean,
default: false,
},
listInfo: {
type: Object,
required: true,
},
},
model: {
prop: 'visible',
event: 'visible',
},
components: {
Modal,
Btn,
},
watch: {
visible(n) {
if (n) this.handleFilterList()
},
},
data() {
return {
duplicateList: [],
}
},
mounted() {
if (this.listInfo.list) this.handleFilterList()
},
methods: {
...mapMutations('list', [
'listRemove',
]),
...mapMutations('player', {
setPlayList: 'setList',
}),
handleFilterList() {
const listMap = new Map()
const duplicateList = []
this.listInfo.list.forEach((musicInfo, index) => {
const musicInfoName = musicInfo.name.toLowerCase().trim()
if (listMap.has(musicInfoName)) {
const targetMusicInfo = listMap.get(musicInfoName)
duplicateList.push({
index: this.listInfo.list.indexOf(targetMusicInfo),
musicInfo: targetMusicInfo,
}, {
index,
musicInfo,
})
} else {
listMap.set(musicInfoName, musicInfo)
}
})
this.duplicateList = duplicateList
},
handleRemove(index) {
const { musicInfo: targetMusicInfo } = this.duplicateList.splice(index, 1)[0]
// let duplicates = []
// for (let index = 0; index < this.duplicateList.length; index++) {
// const { musicInfo } = this.duplicateList[index]
// if (musicInfo.name == targetMusicInfo.name) {
// duplicates.push(index)
// if (duplicates.length > 1) break
// }
// }
// console.log(duplicates)
// if (duplicates.length < 2) this.duplicateList.splice(duplicates[0], 1)
this.listRemove({ listId: this.listInfo.id, id: targetMusicInfo.songmid })
this.handleFilterList()
},
handlePlay(index) {
const { index: musicInfoIndex } = this.duplicateList[index]
this.setPlayList({ list: this.listInfo, index: musicInfoIndex })
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.header {
flex: none;
padding: 15px;
text-align: center;
}
.main {
min-height: 200px;
width: 460px;
}
.list {
// background-color: @color-search-form-background;
font-size: 13px;
transition-property: height;
position: relative;
.listItem {
position: relative;
padding: 8px 5px;
transition: background-color .2s ease;
line-height: 1.3;
// overflow: hidden;
display: flex;
flex-flow: row nowrap;
align-items: center;
&:hover {
background-color: @color-theme_2-hover;
}
// border-radius: 4px;
// &:last-child {
// border-bottom-left-radius: 4px;
// border-bottom-right-radius: 4px;
// }
}
}
.num {
flex: none;
font-size: 12px;
width: 30px;
text-align: center;
color: @color-theme_2-font-label;
}
.text {
flex: auto;
padding-left: 5px;
.mixin-ellipsis-1;
}
.albumName {
font-size: 12px;
opacity: 0.6;
.mixin-ellipsis-1;
}
.label {
flex: none;
font-size: 12px;
opacity: 0.5;
padding: 0 5px;
display: flex;
align-items: center;
// transform: rotate(45deg);
// background-color:
}
.btns {
flex: none;
font-size: 12px;
padding: 0 5px;
display: flex;
align-items: center;
}
.btn {
background-color: transparent;
border: none;
border-radius: @form-radius;
margin-right: 5px;
cursor: pointer;
padding: 4px 7px;
color: @color-btn;
outline: none;
transition: background-color 0.2s ease;
line-height: 0;
&:last-child {
margin-right: 0;
}
svg {
height: 16px;
}
&:hover {
background-color: @color-theme_2-hover;
}
&:active {
background-color: @color-theme_2-active;
}
}
.no-item {
position: relative;
height: 200px;
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: center;
p {
font-size: 16px;
color: @color-theme_2-font-label;
}
}
each(@themes, {
:global(#container.@{value}) {
.listItem {
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
}
.num {
color: ~'@{color-@{value}-theme_2-font-label}';
}
.btn {
color: ~'@{color-@{value}-btn}';
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&:active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
.no-item {
p {
color: ~'@{color-@{value}-theme_2-font-label}';
}
}
}
})
</style>

View File

@ -3,7 +3,9 @@ transition(enter-active-class="animated fadeIn"
leave-active-class="animated fadeOut") leave-active-class="animated fadeOut")
div(:class="$style.modal" v-show="show" @click="bgClose && close()") div(:class="$style.modal" v-show="show" @click="bgClose && close()")
transition(:enter-active-class="inClass" transition(:enter-active-class="inClass"
:leave-active-class="outClass") :leave-active-class="outClass"
@after-leave="$emit('after-leave', $event)"
)
div(:class="$style.content" v-show="show" @click.stop) div(:class="$style.content" v-show="show" @click.stop)
header(:class="$style.header") header(:class="$style.header")
button(type="button" @click="close" v-if="closeBtn") button(type="button" @click="close" v-if="closeBtn")
@ -141,6 +143,7 @@ export default {
overflow: hidden; overflow: hidden;
max-height: 80%; max-height: 80%;
max-width: 76%; max-width: 76%;
min-width: 220px;
position: relative; position: relative;
display: flex; display: flex;
flex-flow: column nowrap; flex-flow: column nowrap;

View File

@ -185,7 +185,8 @@ export default {
display: flex; display: flex;
flex-flow: column nowrap; flex-flow: column nowrap;
transition: @transition-theme; transition: @transition-theme;
transition-property: flex-basis opacity; transition-property: transform,opacity;
transform-origin: 100%;
overflow: hidden; overflow: hidden;
} }
.commentHeader { .commentHeader {

View File

@ -96,6 +96,12 @@ export default {
...mapMutations(['setAgreePact']), ...mapMutations(['setAgreePact']),
handleClick() { handleClick() {
this.setAgreePact() this.setAgreePact()
setTimeout(() => {
this.$dialog({
message: Buffer.from('e69cace8bdafe4bbb6e5ae8ce585a8e5858de8b4b9e4b894e5bc80e6ba90efbc8ce5a682e69e9ce4bda0e698afe88ab1e992b1e8b4ade4b9b0e79a84efbc8ce8afb7e79bb4e68ea5e7bb99e5b7aee8af84efbc810a0a5468697320736f667477617265206973206672656520616e64206f70656e20736f757263652e', 'hex').toString(),
confirmButtonText: Buffer.from('e5a5bde79a8420284f4b29', 'hex').toString(),
})
}, 2000)
}, },
handleClose(isExit) { handleClose(isExit) {
if (isExit) return rendererSend(NAMES.mainWindow.close, true) if (isExit) return rendererSend(NAMES.mainWindow.close, true)

View File

@ -239,7 +239,7 @@ export default {
handleSearch() { handleSearch() {
if (!this.text.length) return this.resultList = [] if (!this.text.length) return this.resultList = []
let list = [] let list = []
let rxp = new RegExp(this.text.split('').join('.*') + '.*', 'i') let rxp = new RegExp(this.text.split('').map(s => s.replace(/[.*+?^${}()|[\]\\]/, '\\$&')).join('.*') + '.*', 'i')
for (const item of this.list) { for (const item of this.list) {
if (rxp.test(`${item.name}${item.singer}${item.albumName ? item.albumName : ''}`)) list.push(item) if (rxp.test(`${item.name}${item.singer}${item.albumName ? item.albumName : ''}`)) list.push(item)
} }
@ -374,6 +374,7 @@ export default {
.albumName { .albumName {
font-size: 12px; font-size: 12px;
opacity: 0.6; opacity: 0.6;
.mixin-ellipsis-1;
} }
.source { .source {
flex: none; flex: none;

View File

@ -5,7 +5,7 @@
div.icon(:class="$style.icon") div.icon(:class="$style.icon")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.847 451.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 451.847 451.847' space='preserve')
use(xlink:href='#icon-down') use(xlink:href='#icon-down')
ul.list.scroll(:class="$style.list" :style="listStyles" ref="dom_list") ul.selection-list.scroll(:class="$style.list" :style="listStyles" ref="dom_list")
li(v-for="item in list" :class="(itemKey ? item[itemKey] : item) == value ? $style.active : null" @click="handleClick(item)" :tips="itemName ? item[itemName] : item") {{itemName ? item[itemName] : item}} li(v-for="item in list" :class="(itemKey ? item[itemKey] : item) == value ? $style.active : null" @click="handleClick(item)" :tips="itemName ? item[itemName] : item") {{itemName ? item[itemName] : item}}
</template> </template>

View File

@ -13,7 +13,31 @@ div(:class="$style.songList")
th.nobreak(:style="{ width: rowWidth.r5 }") {{$t('material.song_list.time')}} th.nobreak(:style="{ width: rowWidth.r5 }") {{$t('material.song_list.time')}}
th.nobreak(:style="{ width: rowWidth.r6 }") {{$t('material.song_list.action')}} th.nobreak(:style="{ width: rowWidth.r6 }") {{$t('material.song_list.action')}}
div(:class="$style.content") div(:class="$style.content")
div.scroll(v-show="list.length" :class="$style.tbody" ref="dom_scrollContent") div(v-if="list.length" :class="$style.content" ref="dom_listContent")
material-virtualized-list(:list="list" key-name="songmid" ref="list" :item-height="listItemHeight"
containerClass="scroll" contentClass="list" @contextmenu.native.capture="handleContextMenu")
template(#default="{ item, index }")
div.list-item(@click="handleDoubleClick($event, index)" @contextmenu="handleListItemRigthClick($event, index)"
:class="[{ selected: selectedIndex == index }, { active: selectdList.includes(item) }]")
div.list-item-cell.nobreak.center(:style="{ width: rowWidth.r1 }" style="padding-left: 3px; padding-right: 3px;" :class="$style.noSelect" @click.stop) {{index + 1}}
div.list-item-cell.auto(:style="{ width: rowWidth.r2 }" :tips="item.name + ((item._types.ape || item._types.flac || item._types.wav) ? ` - ${$t('material.song_list.lossless')}` : item._types['320k'] ? ` - ${$t('material.song_list.high_quality')}` : '')")
span.select {{item.name}}
span.badge.badge-theme-success(:class="[$style.labelQuality, $style.noSelect]" v-if="item._types.ape || item._types.flac || item._types.wav") {{$t('material.song_list.lossless')}}
span.badge.badge-theme-info(:class="[$style.labelQuality, $style.noSelect]" v-else-if="item._types['320k']") {{$t('material.song_list.high_quality')}}
div.list-item-cell(:style="{ width: rowWidth.r3 }" :tips="item.singer")
span.select {{item.singer}}
div.list-item-cell(:style="{ width: rowWidth.r4 }" :tips="item.albumName")
span.select {{item.albumName}}
div.list-item-cell(:style="{ width: rowWidth.r5 }")
span(:class="[$style.time, $style.noSelect]") {{item.interval || '--/--'}}
div.list-item-cell(:style="{ width: rowWidth.r6 }" style="padding-left: 0; padding-right: 0;")
material-list-buttons(:index="index" :class="$style.btns"
:remove-btn="false" @btn-click="handleListBtnClick"
:download-btn="assertApiSupport(item.source)")
template(#footer)
div(:class="$style.pagination")
material-pagination(:count="total" :limit="limit" :page="page" @btn-click="handleTogglePage")
//- div.scroll(v-show="list.length" :class="$style.tbody" ref="dom_scrollContent")
table table
tbody(@contextmenu.capture="handleContextMenu" ref="dom_tbody") tbody(@contextmenu.capture="handleContextMenu" ref="dom_tbody")
tr(v-for='(item, index) in list' :key='item.songmid' @contextmenu="handleListItemRigthClick($event, index)" @click="handleDoubleClick($event, index)") tr(v-for='(item, index) in list' :key='item.songmid' @contextmenu="handleListItemRigthClick($event, index)" @click="handleDoubleClick($event, index)")
@ -48,6 +72,7 @@ div(:class="$style.songList")
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import { scrollTo, clipboardWriteText, assertApiSupport } from '../../utils' import { scrollTo, clipboardWriteText, assertApiSupport } from '../../utils'
import musicSdk from '../../utils/music' import musicSdk from '../../utils/music'
import { windowSizeList } from '@common/config'
export default { export default {
name: 'MaterialSongList', name: 'MaterialSongList',
model: { model: {
@ -138,6 +163,9 @@ export default {
}, },
] ]
}, },
listItemHeight() {
return parseInt(windowSizeList.find(item => item.id == this.setting.windowSizeId).fontSize) / 16 * 37
},
}, },
watch: { watch: {
// selectdList(n) { // selectdList(n) {
@ -175,7 +203,9 @@ export default {
isModDown: false, isModDown: false,
}, },
lastSelectIndex: 0, lastSelectIndex: 0,
selectedIndex: -1,
listMenu: { listMenu: {
rightClickItemIndex: -1,
isShowItemMenu: false, isShowItemMenu: false,
itemMenuControl: { itemMenuControl: {
play: true, play: true,
@ -233,7 +263,7 @@ export default {
this.handleSelectAllData() this.handleSelectAllData()
}, },
handleDoubleClick(event, index) { handleDoubleClick(event, index) {
if (event.target.classList.contains('select')) return if (this.listMenu.rightClickItemIndex > -1) return
this.handleSelectData(event, index) this.handleSelectData(event, index)
@ -264,14 +294,8 @@ export default {
} }
this.selectdList = this.list.slice(lastSelectIndex, clickIndex + 1) this.selectdList = this.list.slice(lastSelectIndex, clickIndex + 1)
if (isNeedReverse) this.selectdList.reverse() if (isNeedReverse) this.selectdList.reverse()
let nodes = this.$refs.dom_tbody.childNodes
do {
nodes[lastSelectIndex].classList.add('active')
lastSelectIndex++
} while (lastSelectIndex <= clickIndex)
} }
} else { } else {
event.currentTarget.classList.add('active')
this.selectdList.push(this.list[clickIndex]) this.selectdList.push(this.list[clickIndex])
this.lastSelectIndex = clickIndex this.lastSelectIndex = clickIndex
} }
@ -281,10 +305,8 @@ export default {
let index = this.selectdList.indexOf(item) let index = this.selectdList.indexOf(item)
if (index < 0) { if (index < 0) {
this.selectdList.push(item) this.selectdList.push(item)
event.currentTarget.classList.add('active')
} else { } else {
this.selectdList.splice(index, 1) this.selectdList.splice(index, 1)
event.currentTarget.classList.remove('active')
} }
} else if (this.selectdList.length) { } else if (this.selectdList.length) {
this.removeAllSelect() this.removeAllSelect()
@ -293,12 +315,6 @@ export default {
}, },
removeAllSelect() { removeAllSelect() {
this.selectdList = [] this.selectdList = []
let dom_tbody = this.$refs.dom_tbody
if (!dom_tbody) return
let nodes = dom_tbody.querySelectorAll('.active')
for (const node of nodes) {
if (node.parentNode == dom_tbody) node.classList.remove('active')
}
}, },
handleListBtnClick(info) { handleListBtnClick(info) {
this.emitEvent('listBtnClick', info) this.emitEvent('listBtnClick', info)
@ -306,10 +322,6 @@ export default {
handleSelectAllData() { handleSelectAllData() {
this.removeAllSelect() this.removeAllSelect()
this.selectdList = [...this.list] this.selectdList = [...this.list]
let nodes = this.$refs.dom_tbody.childNodes
for (const node of nodes) {
node.classList.add('active')
}
this.$emit('input', [...this.selectdList]) this.$emit('input', [...this.selectdList])
}, },
handleTogglePage(page) { handleTogglePage(page) {
@ -327,12 +339,12 @@ export default {
handleContextMenu(event) { handleContextMenu(event) {
if (!event.target.classList.contains('select')) return if (!event.target.classList.contains('select')) return
event.stopImmediatePropagation() event.stopImmediatePropagation()
let classList = this.$refs.dom_scrollContent.classList let classList = this.$refs.dom_listContent.classList
classList.add(this.$style.copying) classList.add(this.$style.copying)
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
let str = window.getSelection().toString() let str = window.getSelection().toString()
classList.remove(this.$style.copying) classList.remove(this.$style.copying)
str = str.trim() str = str.split(/\n\n/).map(s => s.replace(/\n/g, ' ')).join('\n').trim()
if (!str.length) return if (!str.length) return
clipboardWriteText(str) clipboardWriteText(str)
}) })
@ -344,23 +356,27 @@ export default {
this.listMenu.itemMenuControl.sourceDetail = !!musicSdk[this.list[index].source].getMusicDetailPageUrl this.listMenu.itemMenuControl.sourceDetail = !!musicSdk[this.list[index].source].getMusicDetailPageUrl
// this.listMenu.itemMenuControl.play = // this.listMenu.itemMenuControl.play =
// this.listMenu.itemMenuControl.playLater = // this.listMenu.itemMenuControl.playLater =
this.listMenu.itemMenuControl.download = this.listMenu.itemMenuControl.download = this.assertApiSupport(this.list[index].source)
this.assertApiSupport(this.list[index].source) let dom_container = event.target.closest('.' + this.$style.songList)
let dom_selected = this.$refs.dom_tbody.querySelector('tr.selected') const getOffsetValue = (target, x = 0, y = 0) => {
if (dom_selected) dom_selected.classList.remove('selected') if (target === dom_container) return { x, y }
this.$refs.dom_tbody.querySelectorAll('tr')[index].classList.add('selected') if (!target) return { x: 0, y: 0 }
let dom_td = event.target.closest('td') x += target.offsetLeft
y += target.offsetTop
return getOffsetValue(target.offsetParent, x, y)
}
this.listMenu.rightClickItemIndex = index this.listMenu.rightClickItemIndex = index
this.listMenu.menuLocation.x = dom_td.offsetLeft + event.offsetX this.selectedIndex = index
this.listMenu.menuLocation.y = dom_td.offsetParent.offsetTop + dom_td.offsetTop + event.offsetY - this.$refs.dom_scrollContent.scrollTop let { x, y } = getOffsetValue(event.target)
this.listMenu.menuLocation.x = x + event.offsetX
this.listMenu.menuLocation.y = y + event.offsetY - this.$refs.list.getScrollTop()
this.hideListsMenu() this.hideListsMenu()
this.$nextTick(() => { this.$nextTick(() => {
this.listMenu.isShowItemMenu = true this.listMenu.isShowItemMenu = true
}) })
}, },
hideListMenu() { hideListMenu() {
let dom_selected = this.$refs.dom_tbody && this.$refs.dom_tbody.querySelector('tr.selected') this.selectedIndex = -1
if (dom_selected) dom_selected.classList.remove('selected')
this.listMenu.isShowItemMenu = false this.listMenu.isShowItemMenu = false
this.listMenu.rightClickItemIndex = -1 this.listMenu.rightClickItemIndex = -1
}, },
@ -406,24 +422,7 @@ export default {
flex: auto; flex: auto;
min-height: 0; min-height: 0;
position: relative; position: relative;
}
.tbody {
height: 100%; height: 100%;
overflow-y: auto;
td {
font-size: 12px;
:global(.badge) {
margin-left: 3px;
}
&:first-child {
// padding-left: 10px;
font-size: 11px;
color: @color-theme_2-font-label;
}
}
:global(.badge) {
opacity: .85;
}
&.copying { &.copying {
.no-select { .no-select {
@ -431,6 +430,24 @@ export default {
} }
} }
} }
:global(.list) {
height: 100%;
overflow-y: auto;
:global(.list-item-cell) {
font-size: 12px !important;
:global(.badge) {
margin-left: 3px;
}
&:first-child {
// padding-left: 10px;
font-size: 11px !important;
color: @color-theme_2-font-label !important;
}
}
:global(.badge) {
opacity: .85;
}
}
.pagination { .pagination {
text-align: center; text-align: center;
padding: 15px 0; padding: 15px 0;
@ -456,10 +473,10 @@ export default {
each(@themes, { each(@themes, {
:global(#container.@{value}) { :global(#container.@{value}) {
.tbody { :global(.list) {
td { :global(.list-item-cell) {
&:first-child { &:first-child {
color: ~'@{color-@{value}-theme_2-font-label}'; color: ~'@{color-@{value}-theme_2-font-label}' !important;
} }
} }
} }

View File

@ -0,0 +1,240 @@
<template>
<component :is="containerEl" :class="containerClass" ref="dom_scrollContainer" style="height: 100%; overflow: auto; position: relative; display: block;">
<component :is="contentEl" :class="contentClass" :style="contentStyle">
<div v-for="item in views" :key="item.key" :style="item.style">
<slot name="default" v-bind="{ item: item.item, index: item.index }" />
</div>
</component>
<slot name="footer" />
</component>
</template>
<script>
const easeInOutQuad = (t, b, c, d) => {
t /= d / 2
if (t < 1) return (c / 2) * t * t + b
t--
return (-c / 2) * (t * (t - 2) - 1) + b
}
const handleScroll = (element, to, duration = 300, callback = () => {}, onCancel = () => {}) => {
if (!element) return callback()
const start = element.scrollTop || element.scrollY || 0
let cancel = false
if (to > start) {
let maxScrollTop = element.scrollHeight - element.clientHeight
if (to > maxScrollTop) to = maxScrollTop
} else if (to < start) {
if (to < 0) to = 0
} else return callback()
const change = to - start
const increment = 10
if (!change) return callback()
let currentTime = 0
let val
let cancelCallback
const animateScroll = () => {
currentTime += increment
val = parseInt(easeInOutQuad(currentTime, start, change, duration))
if (element.scrollTo) {
element.scrollTo(0, val)
} else {
element.scrollTop = val
}
if (currentTime < duration) {
if (cancel) {
cancelCallback()
onCancel()
return
}
setTimeout(animateScroll, increment)
} else {
callback()
}
}
animateScroll()
return (callback) => {
cancelCallback = callback
cancel = true
}
}
export default {
name: 'VirtualizedList',
props: {
containerEl: {
type: String,
default: 'div',
},
containerClass: {
type: String,
default: 'virtualized-list',
},
contentEl: {
type: String,
default: 'div',
},
contentClass: {
type: String,
default: 'virtualized-list-content',
},
outsideNum: {
type: Number,
default: 1,
},
itemHeight: {
type: Number,
required: true,
},
keyName: {
type: String,
require: true,
},
list: {
type: Array,
require: true,
},
},
data() {
return {
views: [],
startIndex: -1,
endIndex: -1,
scrollTop: 0,
cachedList: [],
cancelScroll: null,
isScrolling: false,
scrollToValue: 0,
}
},
computed: {
contentStyle() {
return {
display: 'block',
height: this.list.length * this.itemHeight + 'px',
}
},
},
watch: {
itemHeight() {
this.updateView()
},
list() {
this.cachedList = Array(this.list.length)
this.startIndex = -1
this.endIndex = -1
this.updateView()
},
},
mounted() {
this.$refs.dom_scrollContainer.addEventListener('scroll', this.onScroll, false)
this.cachedList = Array(this.list.length)
this.startIndex = -1
this.endIndex = -1
this.updateView()
},
beforeDestroy() {
this.$refs.dom_scrollContainer.removeEventListener('scroll', this.onScroll)
if (this.cancelScroll) this.cancelScroll()
},
methods: {
onScroll(event) {
const currentScrollTop = this.$refs.dom_scrollContainer.scrollTop
if (Math.abs(currentScrollTop - this.scrollTop) > this.itemHeight * this.outsideNum * 0.6) {
this.updateView(currentScrollTop)
}
this.$emit('scroll', event)
},
createList(startIndex, endIndex) {
const cache = this.cachedList.slice(startIndex, endIndex)
const list = this.list.slice(startIndex, endIndex).map((item, i) => {
if (cache[i]) return cache[i]
const top = (startIndex + i) * this.itemHeight
const index = startIndex + i
return this.cachedList[index] = {
item,
top,
style: { position: 'absolute', left: 0, right: 0, top: top + 'px', height: this.itemHeight + 'px' },
index,
key: item[this.keyName],
}
})
return list
},
updateView(currentScrollTop = this.$refs.dom_scrollContainer.scrollTop) {
// const currentScrollTop = this.$refs.dom_scrollContainer.scrollTop
const currentStartIndex = Math.floor(currentScrollTop / this.itemHeight)
const scrollContainerHeight = this.$refs.dom_scrollContainer.clientHeight
const currentEndIndex = currentStartIndex + Math.ceil(scrollContainerHeight / this.itemHeight)
const continuous = currentStartIndex <= this.endIndex && currentEndIndex >= this.startIndex
const currentStartRenderIndex = Math.max(Math.floor(currentScrollTop / this.itemHeight) - this.outsideNum, 0)
const currentEndRenderIndex = currentStartIndex + Math.ceil(scrollContainerHeight / this.itemHeight) + this.outsideNum
// console.log(continuous)
// debugger
if (continuous) {
// if (Math.abs(currentScrollTop - this.scrollTop) < this.itemHeight * this.outsideNum * 0.6) return
// console.log('update')
if (currentScrollTop > this.scrollTop) { // scroll down
// console.log('scroll down')
const list = this.createList(currentStartRenderIndex, currentEndRenderIndex)
this.views.push(...list.slice(list.indexOf(this.views[this.views.length - 1]) + 1))
// if (this.views.length > 100) {
this.$nextTick(() => {
this.views.splice(0, this.views.indexOf(list[0]))
})
// }
} else if (currentScrollTop < this.scrollTop) { // scroll up
// console.log('scroll up')
this.views = this.createList(currentStartRenderIndex, currentEndRenderIndex)
} else return
} else {
this.views = this.createList(currentStartRenderIndex, currentEndRenderIndex)
}
this.startIndex = currentStartIndex
this.endIndex = currentEndIndex
this.scrollTop = currentScrollTop
},
scrollTo(scrollTop, animate = false) {
return new Promise(resolve => {
if (this.cancelScroll) {
this.cancelScroll(resolve)
} else {
resolve()
}
}).then(() => {
return new Promise((resolve, reject) => {
if (animate) {
this.isScrolling = true
this.scrollToValue = scrollTop
this.cancelScroll = handleScroll(this.$refs.dom_scrollContainer, scrollTop, 300, () => {
this.cancelScroll = null
this.isScrolling = false
resolve()
}, () => {
this.cancelScroll = null
this.isScrolling = false
reject('canceled')
})
} else {
this.$refs.dom_scrollContainer.scrollTop = scrollTop
}
})
})
},
scrollToIndex(index, offset = 0, animate = false) {
return this.scrollTo(Math.max(index * this.itemHeight + offset, 0), animate)
},
getScrollTop() {
return this.isScrolling ? this.scrollToValue : this.$refs.dom_scrollContainer.scrollTop
},
},
}
</script>

View File

@ -1,5 +1,7 @@
{ {
"date_format_second": "{num} seconds ago", "cancel_button_text": "Cancel",
"confirm_button_text": "OK",
"date_format_hour": "{num} hours ago",
"date_format_minute": "{num} minutes ago", "date_format_minute": "{num} minutes ago",
"date_format_hour": "{num} hours ago" "date_format_second": "{num} seconds ago"
} }

View File

@ -1,43 +1,43 @@
{ {
"copy_title": " (Click to copy)",
"volume": "Volume: ",
"pause": "Pause",
"play": "Play",
"prev": "Prev",
"next": "Next",
"playing": "Now playing...",
"stop": "Paused",
"end": "Stopped",
"refresh_url": "Music URL expired, refreshing...",
"error": "Error loading music. Switch to next song after 5 seconds",
"loading": "Music loading...",
"buffering": "Buffering...",
"geting_url": "Getting music link...",
"lyric_error": "Failed to get lyrics",
"hide_detail": "Hide detail page (Right-click in the view to quickly hide the details page)",
"name": "Name: ",
"singer": "Artist: ",
"album": "Album: ",
"add_music_to": "Add the current song to...", "add_music_to": "Add the current song to...",
"desktop_lyric_on": "Open Desktop Lyrics", "album": "Album: ",
"desktop_lyric_off": "Close Desktop Lyrics", "buffering": "Buffering...",
"desktop_lyric_lock": "Right click to lock lyrics",
"desktop_lyric_unlock": "Right click to unlock lyrics",
"play_toggle_mode_list_loop": "List Loop",
"play_toggle_mode_random": "List Random",
"play_toggle_mode_list": "Play in order",
"play_toggle_mode_single_loop": "Single Loop",
"play_toggle_mode_off": "Disable",
"pic_tip": "Right click to locate the currently playing song in \"My List\"",
"comment_show": "Song comments",
"comment_hot_loading": "Hot comments are loading",
"comment_new_loading": "Latest comments are loading",
"comment_hot_load_error": "Hot comments failed to load, click to try to reload", "comment_hot_load_error": "Hot comments failed to load, click to try to reload",
"comment_new_load_error": "The latest comment failed to load, click to try to reload", "comment_hot_loading": "Hot comments are loading",
"comment_refresh": "Refresh comments",
"comment_no_content": "No comments yet",
"comment_hot_title": "Hot Comment", "comment_hot_title": "Hot Comment",
"comment_new_load_error": "The latest comment failed to load, click to try to reload",
"comment_new_loading": "Latest comments are loading",
"comment_new_title": "Latest comment", "comment_new_title": "Latest comment",
"comment_title": "{name} comment" "comment_no_content": "No comments yet",
"comment_refresh": "Refresh comments",
"comment_show": "Song comments",
"comment_title": "{name} comment",
"copy_title": " (Click to copy)",
"desktop_lyric_lock": "Right click to lock lyrics",
"desktop_lyric_off": "Close Desktop Lyrics",
"desktop_lyric_on": "Open Desktop Lyrics",
"desktop_lyric_unlock": "Right click to unlock lyrics",
"end": "Stopped",
"error": "Error loading music. Switch to next song after 5 seconds",
"geting_url": "Getting music link...",
"hide_detail": "Hide detail page (Right-click in the view to quickly hide the details page)",
"loading": "Music loading...",
"lyric_error": "Failed to get lyrics",
"lyric_select": "Lyric text selection",
"name": "Name: ",
"next": "Next",
"pause": "Pause",
"pic_tip": "Right click to locate the currently playing song in \"My List\"",
"play": "Play",
"play_toggle_mode_list": "Play in order",
"play_toggle_mode_list_loop": "List Loop",
"play_toggle_mode_off": "Disable",
"play_toggle_mode_random": "List Random",
"play_toggle_mode_single_loop": "Single Loop",
"playing": "Now playing...",
"prev": "Prev",
"refresh_url": "Music URL expired, refreshing...",
"singer": "Artist: ",
"stop": "Paused",
"volume": "Volume: "
} }

View File

@ -1,28 +1,38 @@
{ {
"lists_new_list_btn": "Create list", "action": "Manage",
"lists_new_list_input": "New list...", "album": "Album",
"lists_rename": "Rename", "default_list": "Recently Played",
"lists_moveup": "Move Up", "list_add_to": "Add to ...",
"lists_movedown": "Move Down", "list_copy_name": "Copy name",
"lists_sync": "Update", "list_download": "Download",
"lists_remove": "Remove", "list_move_to": "Move to ...",
"list_play": "Play", "list_play": "Play",
"list_play_later": "Play later", "list_play_later": "Play later",
"list_copy_name": "Copy name",
"list_add_to": "Add to ...",
"list_move_to": "Move to ...",
"list_sort": "Adjust position",
"list_download": "Download",
"list_search": "Search",
"list_remove": "Remove", "list_remove": "Remove",
"list_search": "Search",
"list_sort": "Adjust position",
"list_source_detail": "Song Page", "list_source_detail": "Song Page",
"default_list": "Recently Played", "lists_duplicate": "Duplicate song",
"lists_export": "Export",
"lists_export_part_desc": "Choose where to save the list file",
"lists_import": "Import",
"lists_import_part_button_cancel": "No",
"lists_import_part_button_confirm": "Overwrite",
"lists_import_part_confirm": "The imported list ({importName}) has the same ID as the local list ({localName}). Do you overwrite the local list?",
"lists_import_part_desc": "Select list file",
"lists_movedown": "Move Down",
"lists_moveup": "Move Up",
"lists_new_list_btn": "Create list",
"lists_new_list_input": "New list...",
"lists_remove": "Remove",
"lists_remove_tip": "Do you really want to remove {name}?",
"lists_remove_tip_button": "Yes, that's right",
"lists_rename": "Rename",
"lists_sync": "Update",
"loding_list": "Loading...",
"love_list": "Favorites", "love_list": "Favorites",
"name": "Name", "name": "Name",
"no_item": "Nothing's here...",
"singer": "Artist", "singer": "Artist",
"album": "Album", "time": "Length"
"action": "Manage",
"time": "Length",
"loding_list": "Loading...",
"no_item": "Nothing's here..."
} }

View File

@ -120,6 +120,7 @@
"other_resource_cache_clear_btn": "Clear resource cache", "other_resource_cache_clear_btn": "Clear resource cache",
"other_resource_cache_label": "The software has used cache size: ", "other_resource_cache_label": "The software has used cache size: ",
"other_tray_theme": "Tray Icon Style", "other_tray_theme": "Tray Icon Style",
"other_tray_theme_black": "Black Color",
"other_tray_theme_native": "Solid Color", "other_tray_theme_native": "Solid Color",
"other_tray_theme_origin": "Primary Color", "other_tray_theme_origin": "Primary Color",
"play": "Play", "play": "Play",

View File

@ -1,5 +1,7 @@
{ {
"date_format_second": "{num}秒前", "cancel_button_text": "我不",
"confirm_button_text": "好的",
"date_format_hour": "{num}小时前",
"date_format_minute": "{num}分钟前", "date_format_minute": "{num}分钟前",
"date_format_hour": "{num}小时前" "date_format_second": "{num}秒前"
} }

View File

@ -1,43 +1,43 @@
{ {
"copy_title": "(点击复制)",
"volume": "当前音量:",
"pause": "暂停",
"play": "播放",
"prev": "上一首",
"next": "下一首",
"playing": "播放中...",
"stop": "暂停播放",
"end": "播放完毕",
"refresh_url": "URL过期正在刷新URL...",
"error": "音频加载出错5 秒后切换下一首",
"loading": "音乐加载中...",
"buffering": "缓冲中...",
"geting_url": "歌曲链接获取中...",
"lyric_error": "歌词获取失败",
"hide_detail": "隐藏详情页(界面内右键双击可快速隐藏详情页)",
"name": "歌曲名:",
"singer": "艺术家:",
"album": "专辑名:",
"add_music_to": "添加当前歌曲到...", "add_music_to": "添加当前歌曲到...",
"desktop_lyric_on": "开启桌面歌词", "album": "专辑名:",
"desktop_lyric_off": "关闭桌面歌词", "buffering": "缓冲中...",
"desktop_lyric_lock": "右击锁定歌词",
"desktop_lyric_unlock": "右击解锁歌词",
"play_toggle_mode_list_loop": "列表循环",
"play_toggle_mode_random": "列表随机",
"play_toggle_mode_list": "顺序播放",
"play_toggle_mode_single_loop": "单曲循环",
"play_toggle_mode_off": "禁用",
"pic_tip": "右击在“我的列表”定位当前播放的歌曲",
"comment_show": "歌曲评论",
"comment_hot_loading": "热门评论加载中",
"comment_new_loading": "最新评论加载中",
"comment_hot_load_error": "热门评论加载失败,点击尝试重新加载", "comment_hot_load_error": "热门评论加载失败,点击尝试重新加载",
"comment_new_load_error": "最新评论加载失败,点击尝试重新加载", "comment_hot_loading": "热门评论加载中",
"comment_refresh": "刷新评论",
"comment_no_content": "暂无评论",
"comment_hot_title": "热门评论", "comment_hot_title": "热门评论",
"comment_new_load_error": "最新评论加载失败,点击尝试重新加载",
"comment_new_loading": "最新评论加载中",
"comment_new_title": "最新评论", "comment_new_title": "最新评论",
"comment_title": "{name} 的评论" "comment_no_content": "暂无评论",
"comment_refresh": "刷新评论",
"comment_show": "歌曲评论",
"comment_title": "{name} 的评论",
"copy_title": "(点击复制)",
"desktop_lyric_lock": "右击锁定歌词",
"desktop_lyric_off": "关闭桌面歌词",
"desktop_lyric_on": "开启桌面歌词",
"desktop_lyric_unlock": "右击解锁歌词",
"end": "播放完毕",
"error": "音频加载出错5 秒后切换下一首",
"geting_url": "歌曲链接获取中...",
"hide_detail": "隐藏详情页(界面内右键双击可快速隐藏详情页)",
"loading": "音乐加载中...",
"lyric_error": "歌词获取失败",
"lyric_select": "歌词文本选择",
"name": "歌曲名:",
"next": "下一首",
"pause": "暂停",
"pic_tip": "右击在“我的列表”定位当前播放的歌曲",
"play": "播放",
"play_toggle_mode_list": "顺序播放",
"play_toggle_mode_list_loop": "列表循环",
"play_toggle_mode_off": "禁用",
"play_toggle_mode_random": "列表随机",
"play_toggle_mode_single_loop": "单曲循环",
"playing": "播放中...",
"prev": "上一首",
"refresh_url": "URL过期正在刷新URL...",
"singer": "艺术家:",
"stop": "暂停播放",
"volume": "当前音量:"
} }

View File

@ -1,28 +1,38 @@
{ {
"lists_new_list_btn": "新建列表", "action": "操作",
"lists_new_list_input": "新列表...", "album": "专辑",
"lists_rename": "重命名", "default_list": "试听列表",
"lists_moveup": "上移", "list_add_to": "添加到...",
"lists_movedown": "下移", "list_copy_name": "复制歌曲名",
"lists_sync": "更新", "list_download": "下载",
"lists_remove": "删除", "list_move_to": "移动到...",
"list_play": "播放", "list_play": "播放",
"list_play_later": "稍后播放", "list_play_later": "稍后播放",
"list_copy_name": "复制歌曲名",
"list_source_detail": "歌曲详情页",
"list_add_to": "添加到...",
"list_move_to": "移动到...",
"list_sort": "调整位置",
"list_download": "下载",
"list_search": "搜索",
"list_remove": "删除", "list_remove": "删除",
"default_list": "试听列表", "list_search": "搜索",
"list_sort": "调整位置",
"list_source_detail": "歌曲详情页",
"lists_duplicate": "重复歌曲",
"lists_export": "导出",
"lists_export_part_desc": "选择列表文件保存位置",
"lists_import": "导入",
"lists_import_part_button_cancel": "不要啊",
"lists_import_part_button_confirm": "覆盖掉",
"lists_import_part_confirm": "导入的列表({importName})与本地列表({localName}的ID相同是否覆盖本地列表",
"lists_import_part_desc": "选择列表文件",
"lists_movedown": "下移",
"lists_moveup": "上移",
"lists_new_list_btn": "新建列表",
"lists_new_list_input": "新列表...",
"lists_remove": "删除",
"lists_remove_tip": "你真的想要移除 {name} 吗?",
"lists_remove_tip_button": "是的 没错",
"lists_rename": "重命名",
"lists_sync": "更新",
"loding_list": "加载中...",
"love_list": "收藏", "love_list": "收藏",
"name": "歌曲名", "name": "歌曲名",
"no_item": "列表竟然是空的...",
"singer": "歌手", "singer": "歌手",
"album": "专辑", "time": "时长"
"action": "操作",
"time": "时长",
"loding_list": "加载中...",
"no_item": "列表竟然是空的..."
} }

View File

@ -120,6 +120,7 @@
"other_resource_cache_clear_btn": "清理资源缓存", "other_resource_cache_clear_btn": "清理资源缓存",
"other_resource_cache_label": "软件已使用缓存大小:", "other_resource_cache_label": "软件已使用缓存大小:",
"other_tray_theme": "托盘图标样式", "other_tray_theme": "托盘图标样式",
"other_tray_theme_black": "黑色",
"other_tray_theme_native": "纯色", "other_tray_theme_native": "纯色",
"other_tray_theme_origin": "原色", "other_tray_theme_origin": "原色",
"play": "播放设置", "play": "播放设置",

View File

@ -1,5 +1,7 @@
{ {
"date_format_second": "{num}秒前", "cancel_button_text": "取消",
"confirm_button_text": "好的",
"date_format_hour": "{num}小時前",
"date_format_minute": "{num}分鐘前", "date_format_minute": "{num}分鐘前",
"date_format_hour": "{num}小時前" "date_format_second": "{num}秒前"
} }

View File

@ -1,43 +1,43 @@
{ {
"copy_title": "(點擊複製)",
"volume": "當前音量:",
"pause": "暫停",
"play": "播放",
"prev": "上一首",
"next": "下一首",
"playing": "播放中...",
"stop": "暫停播放",
"end": "播放完畢",
"refresh_url": "URL過期正在刷新URL...",
"error": "音頻加載出錯5 秒後切換下一首",
"loading": "音樂加載中...",
"buffering": "緩衝中...",
"geting_url": "歌曲鏈接獲取中...",
"lyric_error": "歌詞獲取失敗",
"hide_detail": "隱藏詳情頁(界面內右鍵雙擊可快速隱藏詳情頁)",
"name": "歌曲名:",
"singer": "藝術家:",
"album": "專輯名:",
"add_music_to": "添加當前歌曲到...", "add_music_to": "添加當前歌曲到...",
"desktop_lyric_on": "開啟桌面歌詞", "album": "專輯名:",
"desktop_lyric_off": "關閉桌面歌詞", "buffering": "緩衝中...",
"desktop_lyric_lock": "右擊鎖定歌詞",
"desktop_lyric_unlock": "右擊解鎖歌詞",
"play_toggle_mode_list_loop": "列表循環",
"play_toggle_mode_random": "列表隨機",
"play_toggle_mode_list": "順序播放",
"play_toggle_mode_single_loop": "單曲循環",
"play_toggle_mode_off": "禁用",
"pic_tip": "右擊在“我的列表”定位當前播放的歌曲",
"comment_show": "歌曲評論",
"comment_hot_loading": "熱門評論加載中",
"comment_new_loading": "最新評論加載中",
"comment_hot_load_error": "熱門評論加載失敗,點擊嘗試重新加載", "comment_hot_load_error": "熱門評論加載失敗,點擊嘗試重新加載",
"comment_new_load_error": "最新評論加載失敗,點擊嘗試重新加載", "comment_hot_loading": "熱門評論加載中",
"comment_refresh": "刷新評論",
"comment_no_content": "暫無評論",
"comment_hot_title": "熱門評論", "comment_hot_title": "熱門評論",
"comment_new_load_error": "最新評論加載失敗,點擊嘗試重新加載",
"comment_new_loading": "最新評論加載中",
"comment_new_title": "最新評論", "comment_new_title": "最新評論",
"comment_title": "{name} 的評論" "comment_no_content": "暫無評論",
"comment_refresh": "刷新評論",
"comment_show": "歌曲評論",
"comment_title": "{name} 的評論",
"copy_title": "(點擊複製)",
"desktop_lyric_lock": "右擊鎖定歌詞",
"desktop_lyric_off": "關閉桌面歌詞",
"desktop_lyric_on": "開啟桌面歌詞",
"desktop_lyric_unlock": "右擊解鎖歌詞",
"end": "播放完畢",
"error": "音頻加載出錯5 秒後切換下一首",
"geting_url": "歌曲鏈接獲取中...",
"hide_detail": "隱藏詳情頁(界面內右鍵雙擊可快速隱藏詳情頁)",
"loading": "音樂加載中...",
"lyric_error": "歌詞獲取失敗",
"lyric_select": "歌詞文本選擇",
"name": "歌曲名:",
"next": "下一首",
"pause": "暫停",
"pic_tip": "右擊在“我的列表”定位當前播放的歌曲",
"play": "播放",
"play_toggle_mode_list": "順序播放",
"play_toggle_mode_list_loop": "列表循環",
"play_toggle_mode_off": "禁用",
"play_toggle_mode_random": "列表隨機",
"play_toggle_mode_single_loop": "單曲循環",
"playing": "播放中...",
"prev": "上一首",
"refresh_url": "URL過期正在刷新URL...",
"singer": "藝術家:",
"stop": "暫停播放",
"volume": "當前音量:"
} }

View File

@ -1,28 +1,38 @@
{ {
"lists_new_list_btn": "新建列表", "action": "操作",
"lists_new_list_input": "新列表...", "album": "專輯",
"lists_rename": "重命名", "default_list": "試聽列表",
"lists_moveup": "上移", "list_add_to": "添加到...",
"lists_movedown": "下移", "list_copy_name": "複製歌曲名",
"lists_sync": "更新", "list_download": "下載",
"lists_remove": "刪除", "list_move_to": "移動到...",
"list_play": "播放", "list_play": "播放",
"list_play_later": "稍後播放", "list_play_later": "稍後播放",
"list_copy_name": "複製歌曲名",
"list_add_to": "添加到...",
"list_move_to": "移動到...",
"list_sort": "調整位置",
"list_download": "下載",
"list_search": "搜索",
"list_remove": "刪除", "list_remove": "刪除",
"list_search": "搜索",
"list_sort": "調整位置",
"list_source_detail": "歌曲詳情頁", "list_source_detail": "歌曲詳情頁",
"default_list": "試聽列表", "lists_duplicate": "重複歌曲",
"lists_export": "導出",
"lists_export_part_desc": "選擇列表文件保存位置",
"lists_import": "導入",
"lists_import_part_button_cancel": "不要啊",
"lists_import_part_button_confirm": "覆蓋掉",
"lists_import_part_confirm": "導入的列表({importName})與本地列表({localName}的ID相同是否覆蓋本地列表",
"lists_import_part_desc": "選擇列表文件",
"lists_movedown": "下移",
"lists_moveup": "上移",
"lists_new_list_btn": "新建列表",
"lists_new_list_input": "新列表...",
"lists_remove": "刪除",
"lists_remove_tip": "你真的想要移除 {name} 嗎?",
"lists_remove_tip_button": "是的 沒錯",
"lists_rename": "重命名",
"lists_sync": "更新",
"loding_list": "加載中...",
"love_list": "收藏列表", "love_list": "收藏列表",
"name": "歌曲名", "name": "歌曲名",
"no_item": "列表竟然是空的...",
"singer": "歌手", "singer": "歌手",
"album": "專輯", "time": "時長"
"action": "操作",
"time": "時長",
"loding_list": "加載中...",
"no_item": "列表竟然是空的..."
} }

View File

@ -120,6 +120,7 @@
"other_resource_cache_clear_btn": "清理資源緩存", "other_resource_cache_clear_btn": "清理資源緩存",
"other_resource_cache_label": "軟件已使用緩存大小:", "other_resource_cache_label": "軟件已使用緩存大小:",
"other_tray_theme": "托盤圖標樣式", "other_tray_theme": "托盤圖標樣式",
"other_tray_theme_black": "黑色",
"other_tray_theme_native": "純色", "other_tray_theme_native": "純色",
"other_tray_theme_origin": "原色", "other_tray_theme_origin": "原色",
"play": "播放設置", "play": "播放設置",

View File

@ -1,5 +1,5 @@
import Vue from 'vue' import Vue from 'vue'
import { sync } from 'vuex-router-sync' // import { sync } from 'vuex-router-sync'
import './event' import './event'
@ -20,7 +20,7 @@ import { getSetting } from './utils'
import languageList from '@renderer/lang/languages.json' import languageList from '@renderer/lang/languages.json'
import { rendererSend, NAMES } from '../common/ipc' import { rendererSend, NAMES } from '../common/ipc'
sync(store, router) // sync(store, router)
Vue.config.productionTip = false Vue.config.productionTip = false
Vue.config.devtools = process.env.NODE_ENV === 'development' Vue.config.devtools = process.env.NODE_ENV === 'development'

View File

@ -0,0 +1,75 @@
<template>
<Modal :show="visible" @close="handleCancel" @after-leave="afterLeave" :closeBtn="false">
<main :class="$style.main">{{message}}</main>
<footer :class="$style.footer">
<Btn :class="$style.btn" v-if="showCancel" @click="handleCancel">{{cancelBtnText}}</Btn>
<Btn :class="$style.btn" @click="handleComfirm">{{confirmBtnText}}</Btn>
</footer>
</Modal>
</template>
<script>
import Modal from '@renderer/components/material/Modal'
import Btn from '@renderer/components/material/Btn'
export default {
components: {
Modal,
Btn,
},
data() {
return {
visible: false,
message: '',
showCancel: false,
cancelButtonText: '',
confirmButtonText: '',
}
},
computed: {
cancelBtnText() {
return this.cancelButtonText || this.$t('base.cancel_button_text')
},
confirmBtnText() {
return this.confirmButtonText || this.$t('base.confirm_button_text')
},
},
beforeDestroy() {
const el = this.$el
el.parentNode.removeChild(el)
},
methods: {
afterLeave(el, done) {
this.$destroy()
},
handleCancel() {
},
handleComfirm() {
},
},
}
</script>
<style lang="less" module>
.main {
flex: auto;
min-height: 40px;
padding: 15px;
font-size: 14px;
max-width: 320px;
min-width: 220px;
line-height: 1.5;
white-space: pre-line;
}
.footer {
flex: none;
padding: 0 15px 15px;
display: flex;
flex-flow: row nowrap;
justify-content: flex-end;
gap: 15px;
}
</style>

View File

@ -0,0 +1,54 @@
import Dialog from './Dialog'
import i18n from '../i18n'
import store from '@renderer/store'
import Vue from 'vue'
const defaultOptions = {
message: '',
showCancel: false,
cancelButtonText: '',
confirmButtonText: '',
}
const dialog = {
install(Vue, options) {
const DialogConstructor = Vue.extend(Dialog)
const dialog = function Dialog(options) {
const { message, showCancel, cancelButtonText, confirmButtonText } =
Object.assign({}, defaultOptions, typeof options == 'string' ? { message: options } : options || {})
return new Promise((resolve, reject) => {
let instance = new DialogConstructor({ i18n, store }).$mount(document.createElement('div'))
// 属性设置
instance.visible = true
instance.message = message
instance.showCancel = showCancel
instance.cancelButtonText = cancelButtonText
instance.confirmButtonText = confirmButtonText
// 挂载
document.getElementById('container').appendChild(instance.$el)
instance.handleCancel = () => {
instance.visible = false
resolve(false)
}
instance.handleComfirm = () => {
instance.visible = false
resolve(true)
}
})
}
dialog.confirm = options => dialog(
typeof options == 'string'
? { message: options, showCancel: true }
: { ...options, showCancel: true },
)
Vue.prototype.$dialog = dialog
},
}
Vue.use(dialog)

View File

@ -3,6 +3,8 @@ import { debounce } from '../../utils'
let instance let instance
let prevTips let prevTips
let prevX = 0
let prevY = 0
const getTips = el => const getTips = el =>
el el
@ -53,6 +55,9 @@ const updateTips = event => {
} }
document.body.addEventListener('mousemove', event => { document.body.addEventListener('mousemove', event => {
if (event.x == prevX && event.y == prevY) return
prevX = event.x
prevY = event.y
hideTips() hideTips()
showTips(event) showTips(event)
}) })

View File

@ -1,2 +1,3 @@
// import './axios' // import './axios'
import './Dialog'
import './Tips' import './Tips'

View File

@ -11,9 +11,13 @@ import {
getMusicUrl as getMusicUrlFormStorage, getMusicUrl as getMusicUrlFormStorage,
setMusicUrl, setMusicUrl,
assertApiSupport, assertApiSupport,
filterFileName,
} from '../../utils' } from '../../utils'
import { NAMES, rendererInvoke } from '@common/ipc'
window.downloadList = [] window.downloadList = []
window.downloadListFull = []
window.downloadListFullMap = new Map()
// state // state
const state = { const state = {
list: window.downloadList, list: window.downloadList,
@ -30,9 +34,7 @@ const state = {
const dls = {} const dls = {}
const tryNum = {} const tryNum = {}
let isRuningActionTask = false
const filterFileName = /[\\/:*?#"<>|]/g
// getters // getters
const getters = { const getters = {
@ -71,7 +73,7 @@ const getExt = type => {
} }
} }
const checkList = (list, musicInfo, type, ext) => list.some(s => s.musicInfo.songmid === musicInfo.songmid && (s.type === type || s.ext === ext)) const checkList = (list, musicInfo, type, ext) => list.some(s => s.songmid === musicInfo.songmid && (s.type === type || s.ext === ext))
const getStartTask = (list, downloadStatus, maxDownloadNum) => { const getStartTask = (list, downloadStatus, maxDownloadNum) => {
let downloadCount = 0 let downloadCount = 0
@ -80,82 +82,6 @@ const getStartTask = (list, downloadStatus, maxDownloadNum) => {
return downloadCount < maxDownloadNum ? waitList.shift() || null : false return downloadCount < maxDownloadNum ? waitList.shift() || null : false
} }
const awaitRequestAnimationFrame = () => new Promise(resolve => window.requestAnimationFrame(() => resolve()))
const addTasks = async(store, list, type) => {
if (list.length == 0) return
let num = 3
while (num-- > 0) {
let item = list.shift()
if (!item) return
store.dispatch('createDownload', {
musicInfo: item,
type: getMusicType(item, type),
})
}
await awaitRequestAnimationFrame()
await addTasks(store, list, type)
}
const removeTasks = async(store, list) => {
let num = 20
while (num-- > 0) {
let item = list.pop()
if (!item) return
let index = store.state.list.indexOf(item)
if (index < 0) continue
store.dispatch('removeTask', item)
}
await awaitRequestAnimationFrame()
await removeTasks(store, list)
}
const startTasks = async(store, list) => {
let num = 5
while (num-- > 0) {
let item = list.shift()
if (!item) return
if (item.isComplate || item.status == state.downloadStatus.RUN || item.status == state.downloadStatus.WAITING) continue
let index = store.state.list.indexOf(item)
if (index < 0) continue
store.dispatch('startTask', item)
}
await awaitRequestAnimationFrame()
await startTasks(store, list)
}
const pauseTasks = async(store, list, runs = []) => {
let num = 6
let index
let stateList = store.state.list
while (num-- > 0) {
let item = list.shift()
if (item) {
if (item.isComplate) continue
switch (item.status) {
case state.downloadStatus.RUN:
runs.push(item)
continue
case state.downloadStatus.WAITING:
index = stateList.indexOf(item)
if (index < 0) return
store.dispatch('pauseTask', item)
continue
default:
continue
}
} else {
for (const item of runs) {
index = stateList.indexOf(item)
if (index < 0) return
await store.dispatch('pauseTask', item)
}
return
}
}
await awaitRequestAnimationFrame()
await pauseTasks(store, list, runs)
}
const handleGetMusicUrl = function(musicInfo, type, retryedSource = [], originMusic) { const handleGetMusicUrl = function(musicInfo, type, retryedSource = [], originMusic) {
// console.log(musicInfo.source) // console.log(musicInfo.source)
if (!originMusic) originMusic = musicInfo if (!originMusic) originMusic = musicInfo
@ -182,6 +108,7 @@ const handleGetMusicUrl = function(musicInfo, type, retryedSource = [], originMu
} }
const getMusicUrl = async function(downloadInfo, isUseOtherSource, isRefresh) { const getMusicUrl = async function(downloadInfo, isUseOtherSource, isRefresh) {
downloadInfo = window.downloadListFullMap.get(downloadInfo.key)
const cachedUrl = await getMusicUrlFormStorage(downloadInfo.musicInfo, downloadInfo.type) const cachedUrl = await getMusicUrlFormStorage(downloadInfo.musicInfo, downloadInfo.type)
if (!downloadInfo.musicInfo._types[downloadInfo.type]) { if (!downloadInfo.musicInfo._types[downloadInfo.type]) {
// 兼容旧版酷我源搜索列表过滤128k音质的bug // 兼容旧版酷我源搜索列表过滤128k音质的bug
@ -224,7 +151,8 @@ const getPic = function(musicInfo, retryedSource = [], originMusic) {
}) })
}) })
} }
const getLyric = function(musicInfo, retryedSource = [], originMusic) {
const handleGetLyric = function(musicInfo, retryedSource = [], originMusic) {
if (!originMusic) originMusic = musicInfo if (!originMusic) originMusic = musicInfo
let reqPromise let reqPromise
try { try {
@ -248,6 +176,33 @@ const getLyric = function(musicInfo, retryedSource = [], originMusic) {
}) })
} }
const getLyric = function(musicInfo, isUseOtherSource) {
return getLyricFromStorage(musicInfo).then(lrcInfo => {
return (
lrcInfo.lyric
? Promise.resolve({ lyric: lrcInfo.lyric, tlyric: lrcInfo.tlyric || '' })
: (
isUseOtherSource
? handleGetLyric.call(this, musicInfo)
: music[musicInfo.source].getLyric(musicInfo).promise
).then(({ lyric, tlyric, lxlyric }) => {
setLyric(musicInfo, { lyric, tlyric, lxlyric })
return { lyric, tlyric, lxlyric }
}).catch(err => {
console.log(err)
return null
})
).then(lrcs => {
if (!lrcs) return lrcs
if (global.i18n.locale != 'zh-tw') return lrcs
return rendererInvoke(NAMES.mainWindow.lang_s2t, Buffer.from(lrcs.lyric).toString('base64')).then(b64 => Buffer.from(b64, 'base64').toString()).then(lyric => {
lrcs.lyric = lyric
return lrcs
})
})
})
}
// 修复 1.1.x版本 酷狗源歌词格式 // 修复 1.1.x版本 酷狗源歌词格式
const fixKgLyric = lrc => /\[00:\d\d:\d\d.\d+\]/.test(lrc) ? lrc.replace(/(?:\[00:(\d\d:\d\d.\d+\]))/gm, '[$1') : lrc const fixKgLyric = lrc => /\[00:\d\d:\d\d.\d+\]/.test(lrc) ? lrc.replace(/(?:\[00:(\d\d:\d\d.\d+\]))/gm, '[$1') : lrc
@ -259,6 +214,7 @@ const fixKgLyric = lrc => /\[00:\d\d:\d\d.\d+\]/.test(lrc) ? lrc.replace(/(?:\[0
*/ */
const saveMeta = function(downloadInfo, filePath, isUseOtherSource, isEmbedPic, isEmbedLyric) { const saveMeta = function(downloadInfo, filePath, isUseOtherSource, isEmbedPic, isEmbedLyric) {
if (downloadInfo.type === 'ape') return if (downloadInfo.type === 'ape') return
downloadInfo = window.downloadListFullMap.get(downloadInfo.key)
const tasks = [ const tasks = [
isEmbedPic isEmbedPic
? downloadInfo.musicInfo.img ? downloadInfo.musicInfo.img
@ -273,21 +229,7 @@ const saveMeta = function(downloadInfo, filePath, isUseOtherSource, isEmbedPic,
}) })
: Promise.resolve(), : Promise.resolve(),
isEmbedLyric isEmbedLyric
? getLyricFromStorage(downloadInfo.musicInfo).then(lrcInfo => { ? getLyric.call(this, downloadInfo.musicInfo, isUseOtherSource)
return lrcInfo.lyric
? Promise.resolve({ lyric: lrcInfo.lyric, tlyric: lrcInfo.tlyric || '' })
: (
isUseOtherSource
? getLyric.call(this, downloadInfo.musicInfo)
: music[downloadInfo.musicInfo.source].getLyric(downloadInfo.musicInfo).promise
).then(({ lyric, tlyric, lxlyric }) => {
setLyric(downloadInfo.musicInfo, { lyric, tlyric, lxlyric })
return { lyric, tlyric, lxlyric }
}).catch(err => {
console.log(err)
return null
})
})
: Promise.resolve(), : Promise.resolve(),
] ]
Promise.all(tasks).then(([imgUrl, lyrics = {}]) => { Promise.all(tasks).then(([imgUrl, lyrics = {}]) => {
@ -307,17 +249,10 @@ const saveMeta = function(downloadInfo, filePath, isUseOtherSource, isEmbedPic,
* @param {*} downloadInfo * @param {*} downloadInfo
* @param {*} filePath * @param {*} filePath
*/ */
const downloadLyric = (downloadInfo, filePath, lrcFormat) => { const downloadLyric = function(downloadInfo, isUseOtherSource, filePath, lrcFormat) {
const promise = getLyric(downloadInfo.musicInfo).then(lrcInfo => { downloadInfo = window.downloadListFullMap.get(downloadInfo.key)
return lrcInfo.lyric getLyric.call(this, downloadInfo.musicInfo, isUseOtherSource).then(lrcs => {
? Promise.resolve({ lyric: lrcInfo.lyric, tlyric: lrcInfo.tlyric || '' }) if (lrcs?.lyric) {
: music[downloadInfo.musicInfo.source].getLyric(downloadInfo.musicInfo).promise.then(({ lyric, tlyric, lxlyric }) => {
setLyric(downloadInfo.musicInfo, { lyric, tlyric, lxlyric })
return { lyric, tlyric, lxlyric }
})
})
promise.then(lrcs => {
if (lrcs.lyric) {
lrcs.lyric = fixKgLyric(lrcs.lyric) lrcs.lyric = fixKgLyric(lrcs.lyric)
saveLrc(filePath.replace(/(mp3|flac|ape|wav)$/, 'lrc'), lrcs.lyric, lrcFormat) saveLrc(filePath.replace(/(mp3|flac|ape|wav)$/, 'lrc'), lrcs.lyric, lrcFormat)
} }
@ -351,7 +286,7 @@ const refreshUrl = function(commit, downloadInfo, isUseOtherSource) {
*/ */
const deleteFile = path => new Promise((resolve, reject) => { const deleteFile = path => new Promise((resolve, reject) => {
fs.access(path, fs.constants.F_OK, err => { fs.access(path, fs.constants.F_OK, err => {
if (err) return reject(err) if (err) return err.code == 'ENOENT' ? resolve() : reject(err)
fs.unlink(path, err => { fs.unlink(path, err => {
if (err) return reject(err) if (err) return reject(err)
resolve() resolve()
@ -359,54 +294,91 @@ const deleteFile = path => new Promise((resolve, reject) => {
}) })
}) })
const createDownloadInfo = ({ musicInfo, type, list, fileName, savePath }) => {
type = getMusicType(musicInfo, type)
let ext = getExt(type)
const key = `${musicInfo.songmid}${ext}`
if (checkList(list, musicInfo, type, ext)) return null
const downloadInfo = {
isComplate: false,
status: state.downloadStatus.WAITING,
statusText: '待下载',
url: null,
songmid: musicInfo.songmid,
fileName: filterFileName(`${fileName
.replace('歌名', musicInfo.name)
.replace('歌手', musicInfo.singer)}.${ext}`),
progress: {
downloaded: 0,
total: 0,
progress: 0,
},
type,
ext,
name: `${musicInfo.name} - ${musicInfo.singer}`,
key,
}
downloadInfo.filePath = path.join(savePath, downloadInfo.fileName)
// commit('addTask', downloadInfo)
// 删除同路径下的同名文件
deleteFile(downloadInfo.filePath)
// .catch(err => {
// if (err.code !== 'ENOENT') return commit('setStatusText', { downloadInfo, text: '文件删除失败' })
// })
if (dls[downloadInfo.key]) {
const dl = dls[downloadInfo.key]
delete dls[downloadInfo.key]
dl.stop()
}
return downloadInfo
}
// let waitingUpdateTasks = {}
// const delayUpdateProgress = throttle(function(commit) {
// commit('setProgressDelay')
// }, 1000)
// actions // actions
const actions = { const actions = {
async createDownload({ state, rootState, commit, dispatch }, { musicInfo, type }) { async createDownload({ state, rootState, commit, dispatch }, { musicInfo, type }) {
let ext = getExt(type) const downloadInfo = createDownloadInfo({
if (checkList(state.list, musicInfo, type, ext)) return
const downloadInfo = {
isComplate: false,
status: state.downloadStatus.WAITING,
statusText: '待下载',
url: null,
// songmid: musicInfo.songmid,
fileName: `${rootState.setting.download.fileName
.replace('歌名', musicInfo.name)
.replace('歌手', musicInfo.singer)}.${ext}`.replace(filterFileName, ''),
progress: {
downloaded: 0,
total: 0,
progress: 0,
},
type,
ext,
musicInfo, musicInfo,
key: `${musicInfo.songmid}${ext}`, type,
} fileName: rootState.setting.download.fileName,
downloadInfo.filePath = path.join(rootState.setting.download.savePath, downloadInfo.fileName) savePath: rootState.setting.download.savePath,
commit('addTask', downloadInfo) list: state.list,
try { // 删除同路径下的同名文件 })
await deleteFile(downloadInfo.filePath) if (!downloadInfo) return
} catch (err) { commit('addTask', { downloadInfo, musicInfo, addMusicLocationType: rootState.setting.list.addMusicLocationType })
if (err.code !== 'ENOENT') return commit('setStatusText', { downloadInfo, text: '文件删除失败' }) let result = getStartTask(state.list, state.downloadStatus, rootState.setting.download.maxDownloadNum)
} while (result) {
if (dls[downloadInfo.key]) { dispatch('startTask', result)
dls[downloadInfo.key].stop().finally(() => { result = getStartTask(state.list, state.downloadStatus, rootState.setting.download.maxDownloadNum)
delete dls[downloadInfo.key]
dispatch('startTask', downloadInfo)
})
} else {
// console.log(downloadInfo)
dispatch('startTask', downloadInfo)
} }
}, },
createDownloadMultiple(store, { list, type }) { createDownloadMultiple({ state, rootState, commit, dispatch }, { list, type }) {
if (!list.length || isRuningActionTask) return if (!list.length) return
isRuningActionTask = true const downloadList = []
return addTasks(store, [...list], type).finally(() => { for (const musicInfo of list) {
isRuningActionTask = false const downloadInfo = createDownloadInfo({
}) musicInfo,
type,
fileName: rootState.setting.download.fileName,
savePath: rootState.setting.download.savePath,
list: state.list,
})
if (downloadInfo) downloadList.push({ downloadInfo, musicInfo })
}
commit('addTasks', { list: downloadList, addMusicLocationType: rootState.setting.list.addMusicLocationType })
let result = getStartTask(state.list, state.downloadStatus, rootState.setting.download.maxDownloadNum)
while (result) {
dispatch('startTask', result)
result = getStartTask(state.list, state.downloadStatus, rootState.setting.download.maxDownloadNum)
}
}, },
async handleStartTask({ commit, dispatch, rootState }, downloadInfo) { async handleStartTask({ commit, dispatch, rootState }, downloadInfo) {
// 开始任务 // 开始任务
@ -416,7 +388,7 @@ const actions = {
await checkPath(rootState.setting.download.savePath) await checkPath(rootState.setting.download.savePath)
} catch (error) { } catch (error) {
commit('onError', { downloadInfo, errorMsg: error.message }) commit('onError', { downloadInfo, errorMsg: error.message })
commit('setStatusText', '检查下载目录出错: ' + error.message) commit('setStatusText', { downloadInfo, text: '检查下载目录出错: ' + error.message })
await dispatch('startTask') await dispatch('startTask')
return return
} }
@ -436,13 +408,13 @@ const actions = {
dispatch('startTask') dispatch('startTask')
saveMeta.call(_this, downloadInfo, downloadInfo.filePath, rootState.setting.download.isUseOtherSource, rootState.setting.download.isEmbedPic, rootState.setting.download.isEmbedLyric) saveMeta.call(_this, downloadInfo, downloadInfo.filePath, rootState.setting.download.isUseOtherSource, rootState.setting.download.isEmbedPic, rootState.setting.download.isEmbedLyric)
if (rootState.setting.download.isDownloadLrc) downloadLyric(downloadInfo, downloadInfo.filePath, rootState.setting.download.lrcFormat) if (rootState.setting.download.isDownloadLrc) downloadLyric.call(_this, downloadInfo, rootState.setting.download.isUseOtherSource, downloadInfo.filePath, rootState.setting.download.lrcFormat)
console.log('on complate') console.log('on complate')
}, },
onError(err) { onError(err) {
// console.log(err) console.log(err)
if (err.code == 'EPERM') { if (err.code == 'EPERM') {
commit('onError', { downloadInfo, errorMsg: '歌曲下载目录没有写入权限,请尝试更改歌曲保存路径' }) commit('onError', { downloadInfo, errorMsg: '歌曲保存位置被占用或没有写入权限,请尝试更改歌曲保存目录或重启软件或重启电脑,错误详情:' + err.message })
return return
} }
// console.log(tryNum[downloadInfo.key]) // console.log(tryNum[downloadInfo.key])
@ -531,28 +503,38 @@ const actions = {
await dispatch('startTask') await dispatch('startTask')
} }
}, },
removeTasks(store, list) { removeTasks({ rootState, commit, dispatch }, list) {
let { rootState, state } = store for (const item of list) {
if (isRuningActionTask) return if (dls[item.key]) {
isRuningActionTask = true if (item.status == state.downloadStatus.RUN) {
return removeTasks(store, [...list]).finally(() => { dls[item.key].stop().finally(() => {
let result = getStartTask(state.list, state.downloadStatus, rootState.setting.download.maxDownloadNum) delete dls[item.key]
while (result) { })
store.dispatch('startTask', result) } else {
result = getStartTask(state.list, state.downloadStatus, rootState.setting.download.maxDownloadNum) delete dls[item.key]
}
} }
isRuningActionTask = false if (item.status != state.downloadStatus.COMPLETED) {
}) deleteFile(item.filePath).catch(_ => _)
}
}
commit('removeTasks', list)
let result = getStartTask(state.list, state.downloadStatus, rootState.setting.download.maxDownloadNum)
while (result) {
dispatch('startTask', result)
result = getStartTask(state.list, state.downloadStatus, rootState.setting.download.maxDownloadNum)
}
}, },
async startTask({ state, rootState, commit, dispatch }, downloadInfo) { async startTask({ state, rootState, commit, dispatch }, downloadInfo) {
// 检查是否可以开始任务 // 检查是否可以开始任务
let result = getStartTask(state.list, state.downloadStatus, rootState.setting.download.maxDownloadNum)
if (downloadInfo && !downloadInfo.isComplate && downloadInfo.status != state.downloadStatus.RUN) { if (downloadInfo && !downloadInfo.isComplate && downloadInfo.status != state.downloadStatus.RUN) {
const result = getStartTask(state.list, state.downloadStatus, rootState.setting.download.maxDownloadNum)
if (result === false) { if (result === false) {
commit('setStatus', { downloadInfo, status: state.downloadStatus.WAITING }) commit('setStatus', { downloadInfo, status: state.downloadStatus.WAITING })
return return
} }
} else { } else {
const result = getStartTask(state.list, state.downloadStatus, rootState.setting.download.maxDownloadNum)
if (!result) return if (!result) return
downloadInfo = result downloadInfo = result
} }
@ -575,52 +557,152 @@ const actions = {
await dispatch('handleStartTask', downloadInfo) await dispatch('handleStartTask', downloadInfo)
} }
}, },
startTasks(store, list) { startTasks({ commit, rootState, dispatch }, list) {
if (isRuningActionTask) return list = list.filter(item => !(item.isComplate || item.status == state.downloadStatus.RUN || item.status == state.downloadStatus.WAITING))
isRuningActionTask = true commit('setStatus', { list, status: state.downloadStatus.WAITING })
return startTasks(store, list.filter(item => !(item.isComplate || item.status == state.downloadStatus.RUN || item.status == state.downloadStatus.WAITING))).finally(() => { let result = getStartTask(state.list, state.downloadStatus, rootState.setting.download.maxDownloadNum)
isRuningActionTask = false while (result) {
}) dispatch('startTask', result)
result = getStartTask(state.list, state.downloadStatus, rootState.setting.download.maxDownloadNum)
}
}, },
async pauseTask(store, item) { async pauseTask({ commit }, item) {
if (item.isComplate) return if (item.isComplate) return
let dl = dls[item.key] let dl = dls[item.key]
if (dl) { if (dl) dl.stop()
try { commit('setStatus', { downloadInfo: item, status: state.downloadStatus.PAUSE })
await dl.stop()
} catch (_) {}
}
store.commit('pauseTask', item)
}, },
pauseTasks(store, list) { pauseTasks({ commit, rootState, dispatch }, list) {
if (isRuningActionTask) return const waitingTasks = list.filter(item => item.status == state.downloadStatus.WAITING)
isRuningActionTask = true commit('setStatus', { list: waitingTasks, status: state.downloadStatus.PAUSE })
return pauseTasks(store, [...list]).finally(() => { const runningTasks = list.filter(item => item.status == state.downloadStatus.RUN)
isRuningActionTask = false for (const item of runningTasks) {
}) if (item.isComplate) return
let dl = dls[item.key]
if (dl) dl.stop()
}
commit('setStatus', { list: runningTasks, status: state.downloadStatus.PAUSE })
}, },
} }
// mitations // mitations
const mutations = { const mutations = {
addTask(state, downloadInfo) { addTask(state, { downloadInfo, musicInfo, addMusicLocationType }) {
state.list.unshift(downloadInfo) const downloadInfoFull = { ...downloadInfo, musicInfo }
window.downloadListFullMap.set(downloadInfo.key, downloadInfoFull)
switch (addMusicLocationType) {
case 'top':
window.downloadListFull.unshift(downloadInfoFull)
state.list.unshift(downloadInfo)
break
case 'bottom':
default:
window.downloadListFull.push(downloadInfoFull)
state.list.push(downloadInfo)
break
}
},
addTasks(state, { list, addMusicLocationType }) {
const downloadInfoList = []
const curDownloadListFull = []
for (const { downloadInfo, musicInfo } of list) {
downloadInfoList.push(downloadInfo)
curDownloadListFull.push({ ...downloadInfo, musicInfo })
}
let newList
let newListFull
const map = {}
const fullMap = {}
const ids = []
switch (addMusicLocationType) {
case 'top':
newList = [...downloadInfoList, ...state.list]
newListFull = [...curDownloadListFull, ...window.downloadListFull]
for (let i = newList.length - 1; i > -1; i--) {
const item = newList[i]
if (map[item.key]) continue
ids.unshift(item.key)
map[item.key] = item
fullMap[item.key] = newListFull[i]
}
break
case 'bottom':
default:
newList = [...state.list, ...downloadInfoList]
newListFull = [...window.downloadListFull, ...curDownloadListFull]
newList.forEach((item, index) => {
if (map[item.key]) return
ids.push(item.key)
map[item.key] = item
fullMap[item.key] = newListFull[index]
})
break
}
window.downloadListFullMap.clear()
window.downloadListFull = ids.map(id => {
const info = fullMap[id]
window.downloadListFullMap.set(info.key, info)
return info
})
state.list.splice(0, state.list.length, ...ids.map(id => map[id]))
}, },
removeTask({ list }, downloadInfo) { removeTask({ list }, downloadInfo) {
list.splice(list.indexOf(downloadInfo), 1) const index = list.findIndex(m => m.key == downloadInfo.key)
if (index < 0) return
window.downloadListFull.splice(index, 1)
window.downloadListFullMap.delete(downloadInfo.key)
list.splice(index, 1)
},
removeTasks(state, list) {
let map = {}
let ids = []
for (const item of state.list) {
ids.push(item.key)
map[item.key] = item
}
for (const { key } of list) {
if (map[key]) delete map[key]
}
let newList = []
let newListFull = []
for (const id of ids) {
if (map[id]) {
newList.push(map[id])
newListFull.push(window.downloadListFullMap.get(id))
}
}
window.downloadListFull = newListFull
window.downloadListFullMap.clear()
for (const item of newListFull) {
window.downloadListFullMap.set(item.key, item)
}
state.list.splice(0, state.list.length, ...newList)
}, },
pauseTask(state, downloadInfo) { pauseTask(state, downloadInfo) {
const index = state.list.findIndex(m => m.key == downloadInfo.key)
if (index < 0) return
const downloadInfoFull = window.downloadListFull[index]
downloadInfoFull.status = state.downloadStatus.PAUSE
downloadInfoFull.statusText = '暂停下载'
downloadInfo.status = state.downloadStatus.PAUSE downloadInfo.status = state.downloadStatus.PAUSE
downloadInfo.statusText = '暂停下载' downloadInfo.statusText = '暂停下载'
}, },
setStatusText(state, { downloadInfo, index, text }) { // 设置状态文本 setStatusText(state, { downloadInfo, index, text }) { // 设置状态文本
if (downloadInfo) { if (downloadInfo) {
const index = state.list.findIndex(m => m.key == downloadInfo.key)
if (index < 0) return
const downloadInfoFull = window.downloadListFull[index]
if (downloadInfoFull) downloadInfoFull.statusText = text
downloadInfo.statusText = text downloadInfo.statusText = text
} else { } else {
state.list[index].statusText = text state.list[index].statusText = text
const downloadInfoFull = window.downloadListFull[index]
if (downloadInfoFull) downloadInfoFull.statusText = text
} }
}, },
setStatus(state, { downloadInfo, index, status }) { // 设置状态及状态文本 setStatus(state, { downloadInfo, index, status, list }) { // 设置状态及状态文本
let text let text
switch (status) { switch (status) {
case state.downloadStatus.RUN: case state.downloadStatus.RUN:
@ -639,43 +721,107 @@ const mutations = {
text = '下载完成' text = '下载完成'
break break
} }
if (downloadInfo) { if (list) {
downloadInfo.statusText = text for (const downloadInfo of list) {
downloadInfo.status = status const index = state.list.findIndex(m => m.key == downloadInfo.key)
if (index < 0) return
const downloadInfoFull = window.downloadListFull[index]
downloadInfoFull.statusText = text
downloadInfoFull.status = status
downloadInfo.statusText = text
downloadInfo.status = status
}
} else { } else {
state.list[index].statusText = text if (downloadInfo) {
state.list[index].status = status const index = state.list.findIndex(m => m.key == downloadInfo.key)
if (index < 0) return
const downloadInfoFull = window.downloadListFull[index]
downloadInfoFull.statusText = text
downloadInfoFull.status = status
downloadInfo.statusText = text
downloadInfo.status = status
} else {
const downloadInfoFull = window.downloadListFull[index]
downloadInfoFull.statusText = text
downloadInfoFull.status = status
state.list[index].statusText = text
state.list[index].status = status
}
} }
}, },
onCompleted(state, downloadInfo) { onCompleted(state, downloadInfo) {
const index = state.list.findIndex(m => m.key == downloadInfo.key)
console.log(index)
if (index < 0) return
const downloadInfoFull = window.downloadListFull[index]
downloadInfoFull.isComplate = true
downloadInfoFull.status = state.downloadStatus.COMPLETED
downloadInfoFull.statusText = '下载完成'
downloadInfo.isComplate = true downloadInfo.isComplate = true
downloadInfo.status = state.downloadStatus.COMPLETED downloadInfo.status = state.downloadStatus.COMPLETED
downloadInfo.statusText = '下载完成' downloadInfo.statusText = '下载完成'
}, },
onError(state, { downloadInfo, errorMsg }) { onError(state, { downloadInfo, errorMsg }) {
const index = state.list.findIndex(m => m.key == downloadInfo.key)
if (index < 0) return
const downloadInfoFull = window.downloadListFull[index]
downloadInfoFull.status = state.downloadStatus.ERROR
downloadInfoFull.statusText = errorMsg || '任务出错'
downloadInfo.status = state.downloadStatus.ERROR downloadInfo.status = state.downloadStatus.ERROR
downloadInfo.statusText = errorMsg || '任务出错' downloadInfo.statusText = errorMsg || '任务出错'
}, },
onStart(state, downloadInfo) { onStart(state, downloadInfo) {
const index = state.list.findIndex(m => m.key == downloadInfo.key)
if (index < 0) return
const downloadInfoFull = window.downloadListFull[index]
downloadInfoFull.status = state.downloadStatus.RUN
downloadInfoFull.statusText = '正在下载'
downloadInfo.status = state.downloadStatus.RUN downloadInfo.status = state.downloadStatus.RUN
downloadInfo.statusText = '正在下载' downloadInfo.statusText = '正在下载'
}, },
onProgress(state, { downloadInfo, status }) { onProgress(state, { downloadInfo, status }) {
const index = state.list.findIndex(m => m.key == downloadInfo.key)
if (index < 0) return
const downloadInfoFull = window.downloadListFull[index]
downloadInfoFull.progress.progress = status.progress
downloadInfoFull.progress.downloaded = status.downloaded
downloadInfoFull.progress.total = status.total
downloadInfo.progress.progress = status.progress downloadInfo.progress.progress = status.progress
downloadInfo.progress.downloaded = status.downloaded downloadInfo.progress.downloaded = status.downloaded
downloadInfo.progress.total = status.total downloadInfo.progress.total = status.total
}, },
setTotal(state, { order, downloadInfo }) {
downloadInfo.order = order
},
updateDownloadList(state, list) { updateDownloadList(state, list) {
state.list = window.downloadList = list window.downloadListFullMap.clear()
const stateList = list.map(downloadInfoFull => {
window.downloadListFullMap.set(downloadInfoFull.key, downloadInfoFull)
const downloadInfo = { ...downloadInfoFull }
delete downloadInfo.musicInfo
return downloadInfo
})
window.downloadListFull = list
state.list = window.downloadList = stateList
}, },
updateUrl(state, { downloadInfo, url }) { updateUrl(state, { downloadInfo, url }) {
const index = state.list.findIndex(m => m.key == downloadInfo.key)
if (index < 0) return
const downloadInfoFull = window.downloadListFull[index]
downloadInfoFull.url = url
downloadInfo.url = url downloadInfo.url = url
}, },
updateFilePath(state, { downloadInfo, filePath }) { updateFilePath(state, { downloadInfo, filePath }) {
if (downloadInfo.filePath === filePath) return if (downloadInfo.filePath === filePath) return
const index = state.list.findIndex(m => m.key == downloadInfo.key)
if (index < 0) return
const downloadInfoFull = window.downloadListFull[index]
downloadInfoFull.filePath = filePath
downloadInfo.filePath = filePath downloadInfo.filePath = filePath
}, },
} }

View File

@ -9,9 +9,7 @@ for (const source of music.sources) {
sources.push(source) sources.push(source)
} }
// state const listInfo = {
const state = {
boards: sourceList,
list: [], list: [],
total: 0, total: 0,
page: 1, page: 1,
@ -19,6 +17,11 @@ const state = {
key: null, key: null,
} }
// state
const state = {
boards: sourceList,
}
// getters // getters
const getters = { const getters = {
sources(state, getters, rootState, { sourceNames }) { sources(state, getters, rootState, { sourceNames }) {
@ -27,16 +30,6 @@ const getters = {
boards(state) { boards(state) {
return state.boards return state.boards
}, },
list(state) {
return state.list
},
info(state) {
return {
total: state.total,
limit: state.limit,
page: state.page,
}
},
} }
// actions // actions
@ -47,7 +40,6 @@ const actions = {
// let tabId = rootState.setting.leaderboard.tabId // let tabId = rootState.setting.leaderboard.tabId
// let key = `${source}${tabId}${page}` // let key = `${source}${tabId}${page}`
// if (state.list.length && state.key == key) return true // if (state.list.length && state.key == key) return true
// commit('clearList')
if (state.boards[source].length) return if (state.boards[source].length) return
return music[source].leaderboard.getBoards().then(result => commit('setBoardsList', { boards: result, source })) return music[source].leaderboard.getBoards().then(result => commit('setBoardsList', { boards: result, source }))
}, },
@ -56,14 +48,22 @@ const actions = {
let tabId = rootState.setting.leaderboard.tabId let tabId = rootState.setting.leaderboard.tabId
let [source, bangId] = tabId.split('__') let [source, bangId] = tabId.split('__')
let key = `${source}${tabId}${page}` let key = `${source}${tabId}${page}`
if (state.list.length && state.key == key) return Promise.resolve() if (listInfo.list.length && listInfo.key == key) return Promise.resolve(listInfo)
commit('clearList') // commit('clearList')
// return ( // return (
// cache.has(key) // cache.has(key)
// ? Promise.resolve(cache.get(key)) // ? Promise.resolve(cache.get(key))
// : music[source].leaderboard.getList(bangId, page) // : music[source].leaderboard.getList(bangId, page)
// ).then(result => commit('setList', { result, key })) // ).then(result => commit('setList', { result, key }))
return music[source].leaderboard.getList(bangId, page).then(result => commit('setList', { result, key })) return music[source].leaderboard.getList(bangId, page).then(result => {
cache.set(key, result)
listInfo.list = result.list
listInfo.total = result.total
listInfo.limit = result.limit
listInfo.page = result.page
listInfo.key = key
return listInfo
})
}, },
getListAll({ state, rootState }, id) { getListAll({ state, rootState }, id) {
// console.log(source, id) // console.log(source, id)
@ -96,18 +96,6 @@ const mutations = {
setBoardsList(state, { boards, source }) { setBoardsList(state, { boards, source }) {
state.boards[source] = boards.list state.boards[source] = boards.list
}, },
setList(state, { result, key }) {
state.list = result.list
state.total = result.total
state.limit = result.limit
state.page = result.page
state.key = key
cache.set(key, result)
},
clearList(state) {
state.list = []
state.total = 0
},
} }
export default { export default {

View File

@ -1,6 +1,7 @@
import musicSdk from '../../utils/music' import musicSdk from '../../utils/music'
import { clearLyric, clearMusicUrl } from '../../utils' import { clearLyric, clearMusicUrl } from '../../utils'
import { sync as eventSyncName } from '@renderer/event/names' import { sync as eventSyncName } from '@renderer/event/names'
import { removeListPosition, setListPrevSelectId } from '@renderer/utils/data'
let allList = {} let allList = {}
window.allList = allList window.allList = allList
@ -27,19 +28,16 @@ const state = {
id: 'default', id: 'default',
name: '试听列表', name: '试听列表',
list: [], list: [],
location: 0,
}, },
loveList: { loveList: {
id: 'love', id: 'love',
name: '我的收藏', name: '我的收藏',
list: [], list: [],
location: 0,
}, },
tempList: { tempList: {
id: 'temp', id: 'temp',
name: '临时列表', name: '临时列表',
list: [], list: [],
location: 0,
}, },
userList: [], userList: [],
} }
@ -53,21 +51,29 @@ const getters = {
allList: () => allList, allList: () => allList,
} }
const getOtherSourcePromises = new Map()
// actions // actions
const actions = { const actions = {
getOtherSource({ state, commit }, musicInfo) { getOtherSource({ state, commit }, musicInfo) {
return (musicInfo.otherSource && musicInfo.otherSource.length ? Promise.resolve(musicInfo.otherSource) : musicSdk.findMusic(musicInfo)).then(otherSource => { if (musicInfo.otherSource?.length) return Promise.resolve(musicInfo.otherSource)
let key = `${musicInfo.source}_${musicInfo.songmid}`
if (getOtherSourcePromises.has(key)) return getOtherSourcePromises.get(key)
const promise = musicSdk.findMusic(musicInfo).then(otherSource => {
commit('setOtherSource', { musicInfo, otherSource }) commit('setOtherSource', { musicInfo, otherSource })
if (getOtherSourcePromises.has(key)) getOtherSourcePromises.delete(key)
return otherSource return otherSource
}) })
getOtherSourcePromises.set(key, promise)
return promise
}, },
} }
// mitations // mitations
const mutations = { const mutations = {
initList(state, { defaultList, loveList, userList }) { initList(state, { defaultList, loveList, userList }) {
if (defaultList != null) Object.assign(state.defaultList, { list: defaultList.list, location: defaultList.location }) if (defaultList != null) Object.assign(state.defaultList, { list: defaultList.list })
if (loveList != null) Object.assign(state.loveList, { list: loveList.list, location: loveList.location }) if (loveList != null) Object.assign(state.loveList, { list: loveList.list })
if (userList != null) state.userList = userList if (userList != null) state.userList = userList
allListInit(state.defaultList, state.loveList, state.userList) allListInit(state.defaultList, state.loveList, state.userList)
state.isInitedList = true state.isInitedList = true
@ -95,18 +101,17 @@ const mutations = {
state.userList = userList state.userList = userList
allListInit(state.defaultList, state.loveList, state.userList) allListInit(state.defaultList, state.loveList, state.userList)
}, },
setList(state, { id, list, name, location, source, sourceListId, isSync }) { setList(state, { id, list, name, source, sourceListId, isSync }) {
const targetList = allList[id] const targetList = allList[id]
if (targetList) { if (targetList) {
if (name && targetList.name === name) { if (name && targetList.name === name) {
if (!isSync) { if (!isSync) {
window.eventHub.$emit(eventSyncName.send_action_list, { window.eventHub.$emit(eventSyncName.send_action_list, {
action: 'set_list', action: 'set_list',
data: { id, list, name, location, source, sourceListId }, data: { id, list, name, source, sourceListId },
}) })
} }
targetList.list.splice(0, targetList.list.length, ...list) targetList.list.splice(0, targetList.list.length, ...list)
targetList.location = location
return return
} }
@ -115,14 +120,13 @@ const mutations = {
if (!isSync) { if (!isSync) {
window.eventHub.$emit(eventSyncName.send_action_list, { window.eventHub.$emit(eventSyncName.send_action_list, {
action: 'set_list', action: 'set_list',
data: { id, list, name, location, source, sourceListId }, data: { id, list, name, source, sourceListId },
}) })
} }
let newList = { let newList = {
name, name,
id, id,
list, list,
location,
source, source,
sourceListId, sourceListId,
} }
@ -293,11 +297,11 @@ const mutations = {
const targetMusicInfo = targetList.list.find(item => item.songmid == id) const targetMusicInfo = targetList.list.find(item => item.songmid == id)
if (targetMusicInfo) Object.assign(targetMusicInfo, data) if (targetMusicInfo) Object.assign(targetMusicInfo, data)
}, },
createUserList(state, { name, id = `userlist_${Date.now()}`, list = [], source, sourceListId, isSync }) { createUserList(state, { name, id = `userlist_${Date.now()}`, list = [], source, sourceListId, position, isSync }) {
if (!isSync) { if (!isSync) {
window.eventHub.$emit(eventSyncName.send_action_list, { window.eventHub.$emit(eventSyncName.send_action_list, {
action: 'create_user_list', action: 'create_user_list',
data: { name, id, list, source, sourceListId }, data: { name, id, list, source, sourceListId, position },
}) })
} }
@ -307,11 +311,14 @@ const mutations = {
name, name,
id, id,
list: [], list: [],
location: 0,
source, source,
sourceListId, sourceListId,
} }
state.userList.push(newList) if (position == null) {
state.userList.push(newList)
} else {
state.userList.splice(position + 1, 0, newList)
}
allListUpdate(newList) allListUpdate(newList)
} }
this.commit('list/listAddMultiple', { id, list, isSync: true }) this.commit('list/listAddMultiple', { id, list, isSync: true })
@ -328,6 +335,7 @@ const mutations = {
if (index < 0) return if (index < 0) return
let list = state.userList.splice(index, 1)[0] let list = state.userList.splice(index, 1)[0]
allListRemove(list) allListRemove(list)
removeListPosition(id)
}, },
setUserListName(state, { id, name, isSync }) { setUserListName(state, { id, name, isSync }) {
if (!isSync) { if (!isSync) {
@ -368,9 +376,6 @@ const mutations = {
state.userList.splice(index, 1) state.userList.splice(index, 1)
state.userList.splice(index + 1, 0, targetList) state.userList.splice(index + 1, 0, targetList)
}, },
setListScroll(state, { id, location }) {
if (allList[id]) allList[id].location = location
},
setMusicPosition(state, { id, position, list, isSync }) { setMusicPosition(state, { id, position, list, isSync }) {
if (!isSync) { if (!isSync) {
window.eventHub.$emit(eventSyncName.send_action_list, { window.eventHub.$emit(eventSyncName.send_action_list, {
@ -406,6 +411,9 @@ const mutations = {
setOtherSource(state, { musicInfo, otherSource }) { setOtherSource(state, { musicInfo, otherSource }) {
musicInfo.otherSource = otherSource musicInfo.otherSource = otherSource
}, },
setPrevSelectListId(state, val) {
setListPrevSelectId(val)
},
} }
export default { export default {

View File

@ -18,7 +18,6 @@ const state = {
}, },
playIndex: -1, playIndex: -1,
changePlay: false, changePlay: false,
isShowPlayerDetail: false,
playedList: [], playedList: [],
playMusicInfo: null, playMusicInfo: null,
@ -40,7 +39,7 @@ const filterList = async({ playedList, listInfo, savePath, commit }) => {
canPlayList.push(item) canPlayList.push(item)
// 排除已播放音乐 // 排除已播放音乐
let index = filteredPlayedList.findIndex(m => (m.songmid || m.musicInfo.songmid) == item.musicInfo.songmid) let index = filteredPlayedList.findIndex(m => m.songmid == item.songmid)
if (index > -1) { if (index > -1) {
filteredPlayedList.splice(index, 1) filteredPlayedList.splice(index, 1)
continue continue
@ -52,7 +51,7 @@ const filterList = async({ playedList, listInfo, savePath, commit }) => {
// if (!assertApiSupport(s.source)) return false // if (!assertApiSupport(s.source)) return false
canPlayList.push(s) canPlayList.push(s)
let index = filteredPlayedList.findIndex(m => (m.songmid || m.musicInfo.songmid) == s.songmid) let index = filteredPlayedList.findIndex(m => m.songmid == s.songmid)
if (index > -1) { if (index > -1) {
filteredPlayedList.splice(index, 1) filteredPlayedList.splice(index, 1)
return false return false
@ -156,15 +155,23 @@ const getters = {
let listPlayIndex = Math.min(state.playIndex, state.listInfo.list.length - 1) let listPlayIndex = Math.min(state.playIndex, state.listInfo.list.length - 1)
if (listId != '__temp__') { if (listId != '__temp__') {
const currentSongmid = state.playMusicInfo.musicInfo.songmid || state.playMusicInfo.musicInfo.musicInfo.songmid if (state.playMusicInfo.musicInfo.key) {
if (isPlayList) { const currentKey = state.playMusicInfo.musicInfo.key
playIndex = state.listInfo.list.findIndex(m => (m.songmid || m.musicInfo.songmid) == currentSongmid) if (isPlayList) {
if (!isTempPlay) listPlayIndex = playIndex playIndex = state.listInfo.list.findIndex(m => m.key == currentKey)
} else if (listId == 'download') { if (!isTempPlay) listPlayIndex = playIndex
playIndex = window.downloadList.findIndex(m => m.musicInfo.songmid == currentSongmid) } else if (listId == 'download') {
playIndex = window.downloadList.findIndex(m => m.key == currentKey)
}
} else { } else {
let list = window.allList[listId] const currentSongmid = state.playMusicInfo.musicInfo.songmid
if (list) playIndex = list.list.findIndex(m => m.songmid == currentSongmid) if (isPlayList) {
playIndex = state.listInfo.list.findIndex(m => m.songmid == currentSongmid)
if (!isTempPlay) listPlayIndex = playIndex
} else {
let list = window.allList[listId]
if (list) playIndex = list.list.findIndex(m => m.songmid == currentSongmid)
}
} }
} }
if (listPlayIndex >= 0) prevListPlayIndex = listPlayIndex if (listPlayIndex >= 0) prevListPlayIndex = listPlayIndex
@ -187,6 +194,8 @@ const getters = {
// isTempPlay, // isTempPlay,
// // musicInfo: state.playMusicInfo.musicInfo, // // musicInfo: state.playMusicInfo.musicInfo,
// }) // })
// console.log(state.playMusicInfo)
return { return {
listId, listId,
playIndex, playIndex,
@ -197,7 +206,6 @@ const getters = {
musicInfo: state.playMusicInfo.musicInfo, musicInfo: state.playMusicInfo.musicInfo,
} }
}, },
isShowPlayerDetail: state => state.isShowPlayerDetail,
playMusicInfo: state => state.playMusicInfo, playMusicInfo: state => state.playMusicInfo,
playedList: state => state.playedList, playedList: state => state.playedList,
tempPlayList: state => state.tempPlayList, tempPlayList: state => state.tempPlayList,
@ -267,16 +275,16 @@ const actions = {
let currentSongmid let currentSongmid
if (state.playMusicInfo.isTempPlay) { if (state.playMusicInfo.isTempPlay) {
const musicInfo = currentList[playInfo.listPlayIndex] const musicInfo = currentList[playInfo.listPlayIndex]
if (musicInfo) currentSongmid = musicInfo.songmid || musicInfo.musicInfo.songmid if (musicInfo) currentSongmid = musicInfo.songmid
} else { } else {
currentSongmid = state.playMusicInfo.musicInfo.songmid || state.playMusicInfo.musicInfo.musicInfo.songmid currentSongmid = state.playMusicInfo.musicInfo.songmid
} }
// 从已播放列表移除播放列表已删除的歌曲 // 从已播放列表移除播放列表已删除的歌曲
let index let index
for (index = state.playedList.findIndex(m => (m.musicInfo.songmid || m.musicInfo.musicInfo.songmid) === currentSongmid) - 1; index > -1; index--) { for (index = state.playedList.findIndex(m => m.musicInfo.songmid === currentSongmid) - 1; index > -1; index--) {
const playMusicInfo = state.playedList[index] const playMusicInfo = state.playedList[index]
const currentSongmid = playMusicInfo.musicInfo.songmid || playMusicInfo.musicInfo.musicInfo.songmid const currentSongmid = playMusicInfo.musicInfo.songmid
if (playMusicInfo.listId == currentListId && !currentList.some(m => (m.songmid || m.musicInfo.songmid) === currentSongmid)) { if (playMusicInfo.listId == currentListId && !currentList.some(m => m.songmid === currentSongmid)) {
commit('removePlayedList', index) commit('removePlayedList', index)
continue continue
} }
@ -343,16 +351,16 @@ const actions = {
let currentSongmid let currentSongmid
if (state.playMusicInfo.isTempPlay) { if (state.playMusicInfo.isTempPlay) {
const musicInfo = currentList[playInfo.listPlayIndex] const musicInfo = currentList[playInfo.listPlayIndex]
if (musicInfo) currentSongmid = musicInfo.songmid || musicInfo.musicInfo.songmid if (musicInfo) currentSongmid = musicInfo.songmid
} else { } else {
currentSongmid = state.playMusicInfo.musicInfo.songmid || state.playMusicInfo.musicInfo.musicInfo.songmid currentSongmid = state.playMusicInfo.musicInfo.songmid
} }
// 从已播放列表移除播放列表已删除的歌曲 // 从已播放列表移除播放列表已删除的歌曲
let index let index
for (index = state.playedList.findIndex(m => (m.musicInfo.songmid || m.musicInfo.musicInfo.songmid) === currentSongmid) + 1; index < state.playedList.length; index++) { for (index = state.playedList.findIndex(m => m.musicInfo.songmid === currentSongmid) + 1; index < state.playedList.length; index++) {
const playMusicInfo = state.playedList[index] const playMusicInfo = state.playedList[index]
const currentSongmid = playMusicInfo.musicInfo.songmid || playMusicInfo.musicInfo.musicInfo.songmid const currentSongmid = playMusicInfo.musicInfo.songmid
if (playMusicInfo.listId == currentListId && !currentList.some(m => (m.songmid || m.musicInfo.songmid) === currentSongmid)) { if (playMusicInfo.listId == currentListId && !currentList.some(m => m.songmid === currentSongmid)) {
commit('removePlayedList', index) commit('removePlayedList', index)
continue continue
} }
@ -457,9 +465,6 @@ const mutations = {
clearPlayedList(state) { clearPlayedList(state) {
state.playedList.splice(0, state.playedList.length) state.playedList.splice(0, state.playedList.length)
}, },
visiblePlayerDetail(state, visible) {
state.isShowPlayerDetail = visible
},
setTempPlayList(state, list) { setTempPlayList(state, list) {
state.tempPlayList.push(...list.map(({ musicInfo, listId }) => ({ musicInfo, listId, isTempPlay: true }))) state.tempPlayList.push(...list.map(({ musicInfo, listId }) => ({ musicInfo, listId, isTempPlay: true })))
if (!state.playMusicInfo) this.commit('player/playNext') if (!state.playMusicInfo) this.commit('player/playNext')
@ -478,8 +483,8 @@ const mutations = {
} else { } else {
let listId = playMusicInfo.listId let listId = playMusicInfo.listId
if (listId != '__temp__' && !playMusicInfo.isTempPlay && listId === state.listInfo.id) { if (listId != '__temp__' && !playMusicInfo.isTempPlay && listId === state.listInfo.id) {
const currentSongmid = playMusicInfo.musicInfo.songmid || playMusicInfo.musicInfo.musicInfo.songmid const currentSongmid = playMusicInfo.musicInfo.songmid
playIndex = state.listInfo.list.findIndex(m => (m.songmid || m.musicInfo.songmid) == currentSongmid) playIndex = state.listInfo.list.findIndex(m => m.songmid == currentSongmid)
} }
} }

View File

@ -61,9 +61,6 @@ export default {
setMediaDeviceId(state, val) { setMediaDeviceId(state, val) {
state.setting.player.mediaDeviceId = val state.setting.player.mediaDeviceId = val
}, },
setPrevSelectListId(state, val) {
state.setting.list.prevSelectListId = val
},
setDesktopLyricConfig(state, config) { setDesktopLyricConfig(state, config) {
state.setting.desktopLyric = Object.assign(state.setting.desktopLyric, config) state.setting.desktopLyric = Object.assign(state.setting.desktopLyric, config)
}, },

View File

@ -0,0 +1,45 @@
import { rendererSend, rendererInvoke, NAMES } from '../../common/ipc'
import { throttle } from './index'
let listPosition = {}
let listPrevSelectId
const saveListPosition = throttle(() => {
rendererSend(NAMES.mainWindow.save_data, {
path: 'listPosition',
data: listPosition,
})
}, 1000)
export const initListPosition = () => {
return rendererInvoke(NAMES.mainWindow.get_data, 'listPosition').then(data => {
if (!data) data = {}
listPosition = data
})
}
export const getListPosition = id => listPosition[id] || 0
export const setListPosition = (id, position) => {
listPosition[id] = position || 0
saveListPosition()
}
export const removeListPosition = id => {
delete listPosition[id]
saveListPosition()
}
const saveListPrevSelectId = throttle(() => {
rendererSend(NAMES.mainWindow.save_data, {
path: 'listPrevSelectId',
data: listPrevSelectId,
})
}, 200)
export const initListPrevSelectId = () => {
return rendererInvoke(NAMES.mainWindow.get_data, 'listPrevSelectId').then(id => {
listPrevSelectId = id
})
}
export const getListPrevSelectId = () => listPrevSelectId
export const setListPrevSelectId = id => {
listPrevSelectId = id
saveListPrevSelectId()
}

View File

@ -4,6 +4,7 @@ import { shell, clipboard } from 'electron'
import crypto from 'crypto' import crypto from 'crypto'
import { rendererSend, rendererInvoke, NAMES } from '../../common/ipc' import { rendererSend, rendererInvoke, NAMES } from '../../common/ipc'
import iconv from 'iconv-lite' import iconv from 'iconv-lite'
import { gzip, gunzip } from 'zlib'
/** /**
* 获取两个数之间的随机整数大于等于min小于max * 获取两个数之间的随机整数大于等于min小于max
@ -433,3 +434,50 @@ export const setMusicUrl = (musicInfo, type, url) => rendererSend(NAMES.mainWind
url, url,
}) })
export const clearMusicUrl = () => rendererSend(NAMES.mainWindow.clear_music_url) export const clearMusicUrl = () => rendererSend(NAMES.mainWindow.clear_music_url)
export const gzipData = str => {
return new Promise((resolve, reject) => {
gzip(str, (err, result) => {
if (err) return reject(err)
resolve(result)
})
})
}
export const gunzipData = buf => {
return new Promise((resolve, reject) => {
gunzip(buf, (err, result) => {
if (err) return reject(err)
resolve(result.toString())
})
})
}
export const saveLxConfigFile = async(path, data) => {
if (!path.endsWith('.lxmc')) path += '.lxmc'
fs.writeFile(path, await gzipData(JSON.stringify(data)), 'binary', err => {
console.log(err)
})
}
export const readLxConfigFile = async path => {
let isJSON = path.endsWith('.json')
let data = await fs.promises.readFile(path, isJSON ? 'utf8' : 'binary')
if (!data || isJSON) return data
data = await gunzipData(Buffer.from(data, 'binary'))
data = JSON.parse(data.toString('utf8'))
// 修复v1.14.0出现的导出数据被序列化两次的问题
if (typeof data != 'object') {
try {
data = JSON.parse(data)
} catch (err) {
return data
}
}
return data
}
const fileNameRxp = /[\\/:*?#"<>|]/g
export const filterFileName = name => name.replace(fileNameRxp, '')

View File

@ -120,6 +120,7 @@ module.exports = class Lyric {
return { return {
text: line.text, text: line.text,
time: line.time, time: line.time,
translation: line.translation,
dom_line: fontPlayer.lineContent, dom_line: fontPlayer.lineContent,
} }
}) })

View File

@ -48,7 +48,7 @@ module.exports = class LinePlayer {
_initLines() { _initLines() {
this.lines = [] this.lines = []
this.translationLines = [] this.translationLines = []
const lines = this.lyric.split('\n') const lines = this.lyric.split(/\r\n|\r|\n/)
const linesMap = {} const linesMap = {}
// const translationLines = this.translationLyric.split('\n') // const translationLines = this.translationLyric.split('\n')
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {

View File

@ -29,7 +29,8 @@ export default {
const { body, statusCode } = await _requestObj2.promise const { body, statusCode } = await _requestObj2.promise
// console.log(body) // console.log(body)
if (statusCode != 200 || body.err_code !== 0) throw new Error('获取热门评论失败') if (statusCode != 200 || body.err_code !== 0) throw new Error('获取热门评论失败')
return { source: 'kg', comments: this.filterComment(body.weightList || []) } const total = body.weightList?.length ?? 0
return { source: 'kg', comments: this.filterComment(body.weightList || []), total, page, limit, maxPage: 1 }
}, },
async getReplyComment({ songmid, audioId }, replyId, page = 1, limit = 100) { async getReplyComment({ songmid, audioId }, replyId, page = 1, limit = 100) {
if (this._requestObj2) this._requestObj2.cancelHttp() if (this._requestObj2) this._requestObj2.cancelHttp()

View File

@ -1,6 +1,17 @@
import { httpFetch } from '../../request' import { httpFetch } from '../../request'
import { decodeName, formatPlayTime, sizeFormate } from '../../index' import { decodeName, formatPlayTime, sizeFormate } from '../../index'
import { toMD5 } from '../utils' import { toMD5 } from '../utils'
import infSign from './vendors/infSign.min'
const handleSignature = (id, page, limit) => new Promise((resolve, reject) => {
infSign({ appid: 1058, type: 0, module: 'playlist', page, pagesize: limit, specialid: id }, null, {
useH5: !0,
isCDN: !0,
callback(i) {
resolve(i.signature)
},
})
})
export default { export default {
_requestObj_tags: null, _requestObj_tags: null,
@ -35,6 +46,7 @@ export default {
id: '8', id: '8',
}, },
], ],
cache: new Map(),
regExps: { regExps: {
listData: /global\.data = (\[.+\]);/, listData: /global\.data = (\[.+\]);/,
listInfo: /global = {[\s\S]+?name: "(.+)"[\s\S]+?pic: "(.+)"[\s\S]+?};/, listInfo: /global = {[\s\S]+?name: "(.+)"[\s\S]+?pic: "(.+)"[\s\S]+?};/,
@ -249,7 +261,7 @@ export default {
}) })
if (!songInfo.list) { if (!songInfo.list) {
if (songInfo.global_collection_id) return this.getUserListDetail2(songInfo.global_collection_id) if (songInfo.global_collection_id) return this.getUserListDetail2(songInfo.global_collection_id)
else throw new Error('fail') else return this.getUserListDetail4(songInfo, chain, page).catch(() => this.getUserListDetail5(chain))
} }
let result = await Promise.all(this.createTask(songInfo.list.map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat()) let result = await Promise.all(this.createTask(songInfo.list.map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
// console.log(info, songInfo) // console.log(info, songInfo)
@ -362,6 +374,94 @@ export default {
} }
}, },
async getListInfoByChain(chain) {
if (this.cache.has(chain)) return this.cache.get(chain)
const { body } = await httpFetch(`https://m.kugou.com/share/?chain=${chain}&id=${chain}`, {
headers: {
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',
},
}).promise
let result = body.match(/var\sphpParam\s=\s({.+?});/)
if (result) result = JSON.parse(result[1])
this.cache.set(chain, result)
return result
},
async getUserListDetailByPcChain(chain) {
let key = `${chain}_pc_list`
if (this.cache.has(key)) return this.cache.get(key)
const { body } = await httpFetch(`http://www.kugou.com/share/${chain}.html`, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
},
}).promise
let result = body.match(/var\sdataFromSmarty\s=\s(\[.+?\])/)
if (result) result = JSON.parse(result[1])
this.cache.set(chain, result)
result = await Promise.all(this.createTask(result.map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
// console.log(info, songInfo)
return this.filterData2(result)
},
async getUserListDetail4(songInfo, chain, page) {
const limit = 100
const [listInfo, list] = await Promise.all([
this.getListInfoByChain(chain),
this.getUserListDetailById(songInfo.id, page, limit),
])
return {
list: list || [],
page,
limit,
total: listInfo.songcount,
source: 'kg',
info: {
name: listInfo.specialname,
img: listInfo.imgurl && listInfo.imgurl.replace('{size}', 240),
// desc: body.result.info.list_desc,
author: listInfo.nickname,
// play_count: this.formatPlayCount(info.count),
},
}
},
async getUserListDetail5(chain) {
const [listInfo, list] = await Promise.all([
this.getListInfoByChain(chain),
this.getUserListDetailByPcChain(chain),
])
return {
list: list || [],
page: 1,
limit: this.listDetailLimit,
total: listInfo.songcount,
source: 'kg',
info: {
name: listInfo.specialname,
img: listInfo.imgurl && listInfo.imgurl.replace('{size}', 240),
// desc: body.result.info.list_desc,
author: listInfo.nickname,
// play_count: this.formatPlayCount(info.count),
},
}
},
async getUserListDetailById(id, page, limit) {
const signature = await handleSignature(id, page, limit)
let info = await this.createHttp(`https://pubsongscdn.kugou.com/v2/get_other_list_file?srcappid=2919&clientver=20000&appid=1058&type=0&module=playlist&page=${page}&pagesize=${limit}&specialid=${id}&signature=${signature}`, {
headers: {
Referer: 'https://m3ws.kugou.com/share/index.php',
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
dfid: '-',
},
})
// console.log(info)
let result = await Promise.all(this.createTask(info.info.map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
// console.log(info, songInfo)
return this.filterData2(result)
},
async getUserListDetail(link, page, retryNum = 0) { async getUserListDetail(link, page, retryNum = 0) {
if (retryNum > 3) return Promise.reject(new Error('link try max num')) if (retryNum > 3) return Promise.reject(new Error('link try max num'))
if (link.includes('#')) link = link.replace(/#.*$/, '') if (link.includes('#')) link = link.replace(/#.*$/, '')

File diff suppressed because one or more lines are too long

View File

@ -28,7 +28,7 @@ export default {
const { body, statusCode } = await _requestObj2.promise const { body, statusCode } = await _requestObj2.promise
if (statusCode != 200 || body.result !== 'ok') throw new Error('获取热门评论失败') if (statusCode != 200 || body.result !== 'ok') throw new Error('获取热门评论失败')
// console.log(body) // console.log(body)
return { source: 'kw', comments: this.filterComment(body.rows) } return { source: 'kw', comments: this.filterComment(body.rows), total: body.total, page, limit, maxPage: Math.ceil(body.total / limit) || 1 }
}, },
filterComment(rawList) { filterComment(rawList) {
if (!rawList) return [] if (!rawList) return []

View File

@ -53,7 +53,7 @@ export default {
const { body, statusCode } = await _requestObj2.promise const { body, statusCode } = await _requestObj2.promise
// console.log(body) // console.log(body)
if (statusCode != 200 || body.returnCode !== '000000') throw new Error('获取热门评论失败') if (statusCode != 200 || body.returnCode !== '000000') throw new Error('获取热门评论失败')
return { source: 'mg', comments: this.filterComment(body.data.items) } return { source: 'mg', comments: this.filterComment(body.data.items), total: body.data.itemTotal, page, limit, maxPage: Math.ceil(body.data.itemTotal / limit) || 1 }
}, },
async getReplyComment(musicInfo, replyId, page = 1, limit = 10) { async getReplyComment(musicInfo, replyId, page = 1, limit = 10) {
if (this._requestObj2) this._requestObj2.cancelHttp() if (this._requestObj2) this._requestObj2.cancelHttp()

View File

@ -1,5 +1,5 @@
import { apis } from '../api-source' import { apis } from '../api-source'
import leaderboard from './leaderboard2' import leaderboard from './leaderboard'
import songList from './songList' import songList from './songList'
import musicSearch from './musicSearch' import musicSearch from './musicSearch'
import pic from './pic' import pic from './pic'

View File

@ -2,7 +2,9 @@ import { httpFetch } from '../../request'
import { sizeFormate } from '../../index' 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' }]
export default { export default {
limit: 200, limit: 200,
@ -59,7 +61,7 @@ export default {
}, },
], ],
getUrl(id, page) { getUrl(id, page) {
return `https://app.c.nf.migu.cn/MIGUM3.0/v1.0/template/rank-detail/release?columnId=${id}` return `https://app.c.nf.migu.cn/MIGUM2.0/v1.0/content/querycontentbyId.do?columnId=${id}&needAll=0`
// return `http://m.music.migu.cn/migu/remoting/cms_list_tag?nid=${id}&pageSize=${this.limit}&pageNo=${page - 1}` // return `http://m.music.migu.cn/migu/remoting/cms_list_tag?nid=${id}&pageSize=${this.limit}&pageNo=${page - 1}`
}, },
successCode: '000000', successCode: '000000',
@ -67,16 +69,12 @@ export default {
requestObj: null, requestObj: null,
getBoardsData() { getBoardsData() {
if (this.requestBoardsObj) this._requestBoardsObj.cancelHttp() if (this.requestBoardsObj) this._requestBoardsObj.cancelHttp()
this.requestBoardsObj = httpFetch('https://app.c.nf.migu.cn/MIGUM2.0/v2.0/content/indexrank.do?templateVersion=8', { this.requestBoardsObj = httpFetch('https://app.c.nf.migu.cn/MIGUM3.0/v1.0/template/rank-list/release', {
// this.requestBoardsObj = httpFetch('https://app.c.nf.migu.cn/MIGUM2.0/v2.0/content/indexrank.do?templateVersion=8', {
headers: { headers: {
sign: 'c3b7ae985e2206e97f1b2de8f88691e2', Referer: 'https://app.c.nf.migu.cn/',
timestamp: 1578225871982, 'User-Agent': 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36',
appId: 'yyapp2', channel: '0146921',
mode: 'android',
ua: 'Android_migu',
version: '6.9.4',
osVersion: 'android 7.0',
'User-Agent': 'okhttp/3.9.1',
}, },
}) })
return this.requestBoardsObj.promise return this.requestBoardsObj.promise
@ -98,13 +96,13 @@ export default {
// console.log(rawData) // console.log(rawData)
let ids = new Set() let ids = new Set()
const list = [] const list = []
rawData.forEach(item => { rawData.forEach(({ objectInfo: item }) => {
if (ids.has(item.copyrightId)) return if (ids.has(item.copyrightId)) return
ids.add(item.copyrightId) ids.add(item.copyrightId)
const types = [] const types = []
const _types = {} const _types = {}
item.rateFormats && item.rateFormats.forEach(type => { item.newRateFormats && item.newRateFormats.forEach(type => {
let size let size
switch (type.formatType) { switch (type.formatType) {
case 'PQ': case 'PQ':
@ -131,6 +129,8 @@ export default {
} }
}) })
const intervalTest = /(\d\d:\d\d)$/.test(item.length)
list.push({ list.push({
singer: this.getSinger(item.artists), singer: this.getSinger(item.artists),
name: item.songName, name: item.songName,
@ -140,7 +140,7 @@ export default {
songId: item.songId, songId: item.songId,
copyrightId: item.copyrightId, copyrightId: item.copyrightId,
source: 'mg', source: 'mg',
interval: null, interval: intervalTest ? RegExp.$1 : null,
img: item.albumImgs && item.albumImgs.length ? item.albumImgs[0].img : null, img: item.albumImgs && item.albumImgs.length ? item.albumImgs[0].img : null,
lrc: null, lrc: null,
lrcUrl: item.lrcUrl, lrcUrl: item.lrcUrl,
@ -197,9 +197,9 @@ export default {
getList(bangid, page, retryNum = 0) { getList(bangid, page, retryNum = 0) {
if (++retryNum > 3) return Promise.reject(new Error('try max num')) if (++retryNum > 3) return Promise.reject(new Error('try max num'))
return this.getData(this.getUrl(bangid, page)).then(({ statusCode, body }) => { return this.getData(this.getUrl(bangid, page)).then(({ statusCode, body }) => {
console.log(body) // console.log(body)
if (statusCode !== 200 || body.code !== this.successCode) return this.getList(bangid, page, retryNum) if (statusCode !== 200 || body.code !== this.successCode) return this.getList(bangid, page, retryNum)
const list = this.filterData(body.data.columnInfo.dataList) const list = this.filterData(body.columnInfo.contents)
return { return {
total: list.length, total: list.length,
list, list,

View File

@ -45,7 +45,7 @@ export default {
ids.add(item.id) ids.add(item.id)
const types = [] const types = []
const _types = {} const _types = {}
item.rateFormats && item.rateFormats.forEach(type => { item.newRateFormats && item.newRateFormats.forEach(type => {
let size let size
switch (type.formatType) { switch (type.formatType) {
case 'PQ': case 'PQ':

File diff suppressed because one or more lines are too long

View File

@ -77,6 +77,7 @@ export default {
if (this._requestObj) this._requestObj.cancelHttp() if (this._requestObj) this._requestObj.cancelHttp()
const _requestObj = httpFetch('http://c.y.qq.com/base/fcgi-bin/fcg_global_comment_h5.fcg', { const _requestObj = httpFetch('http://c.y.qq.com/base/fcgi-bin/fcg_global_comment_h5.fcg', {
method: 'POST',
headers: { headers: {
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)', 'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)',
}, },
@ -110,6 +111,7 @@ export default {
if (this._requestObj2) this._requestObj2.cancelHttp() if (this._requestObj2) this._requestObj2.cancelHttp()
const _requestObj2 = httpFetch('http://c.y.qq.com/base/fcgi-bin/fcg_global_comment_h5.fcg', { const _requestObj2 = httpFetch('http://c.y.qq.com/base/fcgi-bin/fcg_global_comment_h5.fcg', {
method: 'POST',
headers: { headers: {
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)', 'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)',
}, },
@ -134,6 +136,9 @@ export default {
source: 'tx', source: 'tx',
comments: this.filterComment(comment.commentlist), comments: this.filterComment(comment.commentlist),
total: comment.commenttotal, total: comment.commenttotal,
page,
limit,
maxPage: Math.ceil(comment.commenttotal / limit) || 1,
} }
}, },
replaceEmoji(msg) { replaceEmoji(msg) {
@ -149,7 +154,8 @@ export default {
}, },
filterComment(rawList) { filterComment(rawList) {
return rawList.map(item => { return rawList.map(item => {
let time = item.rootcommentid ? parseInt(item.rootcommentid.substring(item.rootcommentid.lastIndexOf('_') + 1) + '000') : null let time = parseInt(item.time + '000')
let timeStr = dateFormat2(time)
if (item.middlecommentcontent) { if (item.middlecommentcontent) {
let firstItem = item.middlecommentcontent[0] let firstItem = item.middlecommentcontent[0]
firstItem.avatarurl = item.avatarurl firstItem.avatarurl = item.avatarurl
@ -159,23 +165,23 @@ export default {
item.middlecommentcontent.reverse() item.middlecommentcontent.reverse()
} }
return { return {
id: item.subcommentid, id: `${item.rootcommentid}_${item.commentid}`,
rootId: item.rootcommentid, rootId: item.rootcommentid,
text: item.rootcommentcontent ? this.replaceEmoji(item.rootcommentcontent).replace(/\\n/g, '\n').split('\n') : [], text: item.rootcommentcontent ? this.replaceEmoji(item.rootcommentcontent).replace(/\\n/g, '\n').split('\n') : [],
time, time: item.rootcommentid == item.commentid ? time : null,
timeStr: time ? dateFormat2(time) : null, timeStr: item.rootcommentid == item.commentid ? timeStr : null,
userName: item.rootcommentnick ? item.rootcommentnick.substring(1) : '', userName: item.rootcommentnick ? item.rootcommentnick.substring(1) : '',
avatar: item.avatarurl, avatar: item.avatarurl,
userId: item.encrypt_rootcommentuin, userId: item.encrypt_rootcommentuin,
likedCount: item.praisenum, likedCount: item.praisenum,
reply: item.middlecommentcontent reply: item.middlecommentcontent
? item.middlecommentcontent.map(c => { ? item.middlecommentcontent.map(c => {
let index = c.subcommentid.lastIndexOf('_') // let index = c.subcommentid.lastIndexOf('_')
return { return {
id: c.subcommentid, id: `sub_${item.rootcommentid}_${c.subcommentid}`,
text: this.replaceEmoji(c.subcommentcontent).replace(/\\n/g, '\n').split('\n'), text: this.replaceEmoji(c.subcommentcontent).replace(/\\n/g, '\n').split('\n'),
time: parseInt(c.subcommentid.substring(index + 1) + '000'), time: c.subcommentid == item.commentid ? time : null,
timeStr: dateFormat2(parseInt(c.subcommentid.substring(index + 1) + '000')), timeStr: c.subcommentid == item.commentid ? timeStr : null,
userName: c.replynick.substring(1), userName: c.replynick.substring(1),
avatar: c.avatarurl, avatar: c.avatarurl,
userId: c.encrypt_replyuin, userId: c.encrypt_replyuin,

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