Compare commits

...

69 Commits

Author SHA1 Message Date
lyswhut
e93052f0cb 发布0.13.1版本 2019-12-16 21:19:27 +08:00
lyswhut
9539083f7e 降级electron到7.1.2 2019-12-16 21:04:22 +08:00
lyswhut
8f26eb1c69 修复全局更新弹窗无法遮盖搜索框的问题 2019-12-16 00:24:32 +08:00
lyswhut
a126e5b4c8 发布0.13.0版本 2019-12-15 21:33:46 +08:00
lyswhut
033c3cd8e9 多修复选框不定状态到选中的Bug 2019-12-15 21:23:52 +08:00
lyswhut
50d029c671 移除Q音源的试听与下载 2019-12-15 21:03:19 +08:00
lyswhut
6a3f7e6150 修复搜索建议列表被弹出层覆盖的问题 2019-12-15 19:18:31 +08:00
lyswhut
aadfbefbe9 更新electron及vue到最新 2019-12-14 14:01:53 +08:00
lyswhut
edbcaf0f90 优化“信口雌黄”皮肤配色 2019-12-14 13:54:22 +08:00
lyswhut
207427e993 新增“离开搜索界面时清空搜索列表”设置 2019-12-13 13:34:35 +08:00
lyswhut
e109dd876a 修正缓存描述 2019-12-13 13:09:44 +08:00
lyswhut
64ac69c48b 聚合搜索新增音源显示 2019-12-11 16:05:07 +08:00
lyswhut
652b06c116 更新依赖到最新 2019-12-06 20:55:12 +08:00
lyswhut
654035a469 update issue template 2019-12-04 10:45:31 +08:00
lyswhut
cd50e0086b update issue template 2019-12-03 13:03:09 +08:00
lyswhut
66a119e9dc 新增搜索框搜索建议键盘上下方向键选择功能 2019-12-02 20:46:27 +08:00
lyswhut
14ec7d48f6 更新接口文字描述 2019-12-01 14:29:26 +08:00
lyswhut
5ad5b211e9 发布0.12.1版本 2019-12-01 13:51:13 +08:00
lyswhut
aac4b6ccfe 将所有外部链接从默认浏览器打开 2019-12-01 13:45:48 +08:00
lyswhut
cfc6967fdc 优化链接点击效果 2019-12-01 13:42:16 +08:00
lyswhut
8a0b960985 更新依赖到最新 2019-12-01 12:38:54 +08:00
lyswhut
bd260c3dc5 移除多余的字 2019-12-01 12:34:48 +08:00
lyswhut
203dff8d98 修复酷我源排行榜、歌单详情列表里的歌曲音质匹配问题 2019-11-30 18:36:44 +08:00
lyswhut
ca56089a3e 取消对导出数据的格式化 2019-11-29 21:47:13 +08:00
lyswhut
a012d1b927 更新依赖到最新 2019-11-29 21:35:42 +08:00
lyswhut
0af322a3a8 优化tab布局 2019-11-29 21:33:11 +08:00
lyswhut
8d743803c8 优化滚动方法 2019-11-29 00:01:04 +08:00
lyswhut
63fed77c5b 更新安装失败、缺少dll帮助 2019-11-25 16:58:16 +08:00
lyswhut
f2077dca04 新增数据传输结束时文件下载完成情况的判断 2019-11-25 11:44:31 +08:00
lyswhut
cccfb6a08a 歌曲无封面时下载报错的问题 2019-11-25 11:41:51 +08:00
lyswhut
fa6dead83d 更新依赖到最新 2019-11-24 19:38:45 +08:00
lyswhut
605418d750 更新electron到7.1.2 2019-11-21 20:20:31 +08:00
lyswhut
8aa869b2e4 更新appImage包的分类 2019-11-20 13:19:26 +08:00
lyswhut
558b6e0ccf 发布0.12.0版本 2019-11-17 12:29:23 +08:00
lyswhut
6a695368da 修复Linux图标显示问题 2019-11-17 12:28:01 +08:00
lyswhut
ce89f29aeb 更新Linux图标配置 2019-11-17 10:50:10 +08:00
lyswhut
6d935d8589 更换下载库 2019-11-16 20:16:09 +08:00
lyswhut
967093328f 添加数据保存节流 2019-11-16 20:15:27 +08:00
lyswhut
48e1da128d 修复封面下载出错问题 2019-11-16 20:13:48 +08:00
lyswhut
369de684a6 add electron version tag 2019-11-16 20:12:42 +08:00
lyswhut
d5f65eea06 更新常见问题链接 2019-11-13 21:11:14 +08:00
lyswhut
8e5deb5bf4 添加软件启动后,界面无法显示的问题 2019-11-11 23:36:22 +08:00
lyswhut
df653258bb 更换成commonjs写法 2019-11-10 15:39:40 +08:00
lyswhut
ed28a59fcb 发布0.11.0版本 2019-11-10 13:29:54 +08:00
lyswhut
adc46411b2 新增杀毒软件提示有病毒或恶意行为说明 2019-11-10 13:27:53 +08:00
lyswhut
fcc4d666ec 移除残留remote模块的使用 2019-11-10 01:50:56 +08:00
lyswhut
9d89f5d48d 更新electron到7.1.1 2019-11-09 02:14:49 +08:00
lyswhut
88c62e01ad 新增下载管理的任务状态分类 2019-11-08 21:07:37 +08:00
lyswhut
1823598a8b 新增歌曲缓冲定时器 2019-11-07 21:56:00 +08:00
lyswhut
3fd22988c1 修正获取新版信息失败提示内容 2019-11-06 23:18:27 +08:00
lyswhut
a3ade4cde2 默认使用临时接口 2019-11-06 21:53:12 +08:00
lyswhut
55c2286eca 更新electron到7.1.0 2019-11-06 01:04:08 +08:00
lyswhut
1afa050698 移除log 2019-11-03 17:29:00 +08:00
lyswhut
dfa15c6952 优化更新弹窗机制及其内容描述 2019-11-03 17:18:44 +08:00
lyswhut
a2af321c5e 分离主进程与渲染进程的功能,禁用remote模块 2019-11-03 09:53:19 +08:00
lyswhut
fddfaf23ce 更新配置 2019-11-02 11:51:49 +08:00
lyswhut
ae0ee2048a 新增nodejs版本输出 2019-11-02 11:42:31 +08:00
lyswhut
6659d1fe43 更新nodejs版本配置 2019-11-02 11:22:27 +08:00
lyswhut
f601e61f8d 发布0.10.0版本 2019-11-02 11:00:21 +08:00
lyswhut
f8ed7a112a 更新依赖到最新,更新electron到7.0.1 2019-11-02 10:59:12 +08:00
lyswhut
b0d6181bd7 完善token获取机制 2019-10-31 20:26:53 +08:00
lyswhut
82843b14b4 更新依赖 2019-10-31 00:42:48 +08:00
lyswhut
0df3ca3c5f 新增音乐sdk初始化方法 2019-10-30 22:29:14 +08:00
lyswhut
c881f8e886 更新描述 2019-10-30 01:43:27 +08:00
lyswhut
f1bf274de1 修复咪咕源无法播放的问题 2019-10-29 13:17:06 +08:00
lyswhut
91e4e9c2d5 简化写法 2019-10-28 22:48:30 +08:00
lyswhut
63a5f94a67 修复酷我源搜索提示、排行榜接口挂掉的问题 2019-10-28 22:42:01 +08:00
lyswhut
1bd8694262 减少播放时对CPU与GPU的使用 2019-10-27 17:35:29 +08:00
lyswhut
501231adff 更新依赖到最新,更新electron到7.0.0 2019-10-27 17:34:11 +08:00
70 changed files with 4230 additions and 1773 deletions

View File

@@ -1,5 +1,5 @@
---
name: 功能请求
name: 功能请求(请先查看常见问题及搜索issue列表中有无你要提的问题)
about: 为这个项目提出一个想法
title: 例如添加xxx功能、优化xxx功能
labels: ''

View File

@@ -1,5 +1,5 @@
---
name: 报告Bug
name: 报告Bug(请先查看常见问题及搜索issue列表中有无你要提的问题)
about: 创建报告以帮助我们改进
title: 例如:音乐无法播放
labels: ''

View File

@@ -1,19 +1,17 @@
sudo: true
language: node_js
node_js: 12
matrix:
include:
- os: osx
osx_image: xcode10.2
language: node_js
node_js: "12"
env:
- ELECTRON_CACHE=$HOME/.cache/electron
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
- os: linux
language: node_js
node_js: "12"
dist: trusty
services: docker
language: generic
cache:
directories:
@@ -26,6 +24,8 @@ notifications:
email: false
script:
- node --version
- npm --version
- |
if [ "$TRAVIS_OS_NAME" == "linux" ]; then
npm install && npm run publish:gh:linux

View File

@@ -6,6 +6,102 @@ Project versioning adheres to [Semantic Versioning](http://semver.org/).
Commit convention is based on [Conventional Commits](http://conventionalcommits.org).
Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
## [0.13.1](https://github.com/lyswhut/lx-music-desktop/compare/v0.13.0...v0.13.1) - 2019-12-16
### 修复
- 修复全局更新弹窗无法遮盖搜索框的问题
### 其他
- 由于electron 7.1.3 - 7.1.5 的自动更新功能存在Bug现降级到7.1.2
## [0.13.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.12.1...v0.13.0) - 2019-12-15
### 新增
- 新增搜索框搜索建议键盘上下方向键选择功能
- 聚合搜索新增音源显示
- 新增“离开搜索界面时清空搜索列表”设置选项,默认关闭,可到设置-强迫症设置开启
### 优化
- 优化“信口雌黄”皮肤配色
### 修复
- 修复存在弹出层时,搜索建议列表被弹出层覆盖的问题
- 修复搜索、排行榜、歌单列表多选框从不定状态到选中的Bug
### 移除
- 因Q音接口失效移除Q音源的试听与下载
### 其他
- 更新electron到7.1.5
- 更新vue到2.6.11
## [0.12.1](https://github.com/lyswhut/lx-music-desktop/compare/v0.12.0...v0.12.1) - 2019-12-01
### 优化
- 优化定位歌曲时的列表滚动机制
- 优化链接点击效果
### 修复
- 修复使用酷我源下载歌曲时,当歌曲无封面时下载报错的问题
- 修复酷我源排行榜、歌单详情列表里的歌曲音质匹配问题(原来无论歌曲有无高品、无损都会显示有)
- 禁止外部链接在软件内打开,将所有外部链接从默认浏览器打开
### 其他
- 更新electron到7.1.2
## [0.12.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.11.0...v0.12.0) - 2019-11-17
由于新下载库仍然没有完成但下载功能已经可用so 移除之前使用的第三方下载库,暂时把新下载库的下载模块直接加入本程序,若出现下载问题欢迎反馈!
### 新增
- 新增下载功能对代理设置的支持,现在若在软件设置了代理服务器,下载功能也将会走代理网络了
### 优化
- 新下载模块将对恢复下载的任务进行字节校验用于解决下载进度超过100%后仍然下载的问题
- 注意:目前仍然无法暂停处于**链接获取**状态中的任务
### 修复
- 修复Linux deb版本`.desktop`桌面文件缺少图标的问题,新增中文名称显示、软件分类,感谢@lowy的反馈
- 修复下载列表歌曲状态分类列表操作Bug
- 修复歌曲封面下载失败时仍然执行嵌入封面操作导致报错的问题
- 跳过重复添加**相同歌曲名与扩展名的歌曲**例如你之前下载了A歌曲的128k音质现在想要下载它的320k音质但由于两者都是MP3格式会因为重名导致之前的128k音质被覆盖但列表中仍然显示两种音质的问题但实际上都是指向后面的320k音质
## [0.11.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.10.0...v0.11.0) - 2019-11-10
### 新增
- 新增歌曲缓冲定时器,尝试用于解决网络正常但是歌曲缓冲过久的问题
- 新增下载管理的任务状态分类
- 添加**杀毒软件提示有病毒或恶意行为**的说明,可到**常见问题**拉到最后查看(常见问题可在开源地址找到)
### 优化
- 优化更新弹窗机制及其内容描述,对于可以自动更新的版本,现在可以看到软件的下载进度了
## [0.10.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.9.1...v0.10.0) - 2019-11-02
#### 优化
- 大幅减少程序**播放时**对CPU与GPU的使用经测试CPU使用减少60%以上GPU使用减少90%以上这应该能解决MAC系统上的温度上涨的问题
#### 修复
- 修复酷我源**搜索提示**、**排行榜**无法获取的问题
- 修复咪咕源无法播放的问题
## [0.9.1](https://github.com/lyswhut/lx-music-desktop/compare/v0.9.0...v0.9.1) - 2019-10-27
#### 修复

23
FAQ.md
View File

@@ -4,7 +4,7 @@
## 软件为什么没有桌面歌词与自定义列表功能
洛雪音乐的最初定位不是作为播放器开发的,它主要用于**查找歌曲**,软件的播放功能仅用于试听,不建议用作为常用播放器使用,因此无桌面、界面歌词,不可自定义列表。
洛雪音乐的最初定位不是作为播放器开发的,它主要用于**查找歌曲**,软件的播放功能仅用于试听,不建议用作为常用播放器使用,因此无桌面、界面歌词,不可自定义列表等功能
## 歌曲无法试听与下载
@@ -49,9 +49,13 @@
## Windows 7 下界面异常
当 win7 没有开启**透明效果**时界面将会显示异常,开启方法请自行百度。<br>
对于一些开启透明效果后仍然无法正常显示界面的系统,我也不知道是什么原因导致的,只能说正常的系统是没有这个问题的,如果你知道原因导致的欢迎反馈!
对于一些完全无法正常显示界面的情况,请阅读下面的 **软件启动后,界面无法显示**
## 安装版安装失败,提示应用未安装
## 软件启动后,界面无法显示
软件启动后可以在任务栏看到软件但软件界面在桌面上无任何显示这一般是显卡驱动异常导致的由于软件默认使用GUP渲染界面当驱动异常时可能出现该情况可以用驱动管理软件检测下驱动尝试更新显卡驱动试试。
## 安装版安装失败,提示安装程序并未成功地运行完成
对于部分电脑出现安装失败的问题,可以做出以下尝试:
@@ -62,4 +66,15 @@
## 缺少`xxx.dll`
这个是电脑缺少某些dll导致的正常的系统是没有这个问题的解决办法需自行百度弹出的错误信息看下别人是怎么解决的。
这个是电脑缺少某些dll导致的正常的系统是没有这个问题的可以尝试如下几个解决办法:
- 以管理员权限打开`cmd`,输入`sfc /scannow`回车等待检查完成重启电脑
- 若上面的方法**修复、重启**电脑后仍然不行,就自行百度弹出的**错误信息**看下别人是怎么解决的
## 杀毒软件提示有病毒或恶意行为
本人只能保证我写的代码不包含任何**恶意代码**、**搜集用户信息**的行为并且软件代码已开源请自行查阅软件安装包也是由CI拉取源代码构建构建日志[windows包](https://ci.appveyor.com/project/lyswhut/lx-music-desktop)、[Mac/Linux包](https://travis-ci.org/lyswhut/lx-music-desktop)<br>
尽管如此但这不意味着软件是100%安全的,由于软件使用了第三方依赖,当这些依赖存在恶意行为时,软件也将会受到牵连,所以我只能尽量选择使用较多人用、信任度较高的依赖。<br>
当然,以上说明建立的前提是在你所用的安装包是从**官方渠道**下载的,或者有相关能力者还可以下载源代码自己构建安装包。
最后,若出现杀毒软件报毒,请自行判断选择是否继续使用本软件!

View File

@@ -4,6 +4,7 @@
<a href="https://github.com/lyswhut/lx-music-desktop/releases"><img src="https://img.shields.io/github/release/lyswhut/lx-music-desktop" alt="Release version"></a>
<a href="https://ci.appveyor.com/project/lyswhut/lx-music-desktop"><img src="https://ci.appveyor.com/api/projects/status/flrsqd5ymp8fnte5?svg=true" alt="Build status"></a>
<a href="https://travis-ci.org/lyswhut/lx-music-desktop"><img src="https://travis-ci.org/lyswhut/lx-music-desktop.svg?branch=master" alt="Build status"></a>
<a href="https://electronjs.org/releases/stable"><img src="https://img.shields.io/github/package-json/dependency-version/lyswhut/lx-music-desktop/dev/electron/master" alt="Electron version"></a>
<!-- <a href="https://github.com/lyswhut/lx-music-desktop/releases"><img src="https://img.shields.io/github/downloads/lyswhut/lx-music-desktop/latest/total" alt="Downloads"></a> -->
<a href="https://github.com/lyswhut/lx-music-desktop/tree/dev"><img src="https://img.shields.io/github/package-json/v/lyswhut/lx-music-desktop/dev" alt="Dev branch version"></a>
<!-- <a href="https://github.com/lyswhut/lx-music-desktop/blob/master/LICENSE"><img src="https://img.shields.io/github/license/lyswhut/lx-music-desktop" alt="License"></a> -->
@@ -35,8 +36,8 @@
所用技术栈:
- Electron 6.x
- Vue 2.x
- Electron 7
- Vue 2
已支持的平台:
@@ -47,7 +48,7 @@
软件变化请查看:[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)<br>
软件下载请转到:[发布页面](https://github.com/lyswhut/lx-music-desktop/releases)<br>
或者到网盘下载网盘内有MAC、windows版`https://www.lanzous.com/b906260/` 密码:`glqw`<br>
使用常见问题请转至:[常见问题](https://github.com/lyswhut/lx-music-desktop#常见问题)
使用常见问题请转至:[常见问题](https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md)
### 源码使用方法
@@ -80,7 +81,7 @@ npm run pack
### 免责声明
本项目**不开发或者破解直接获取音频数据**的功能,所有音频数据均来自**第三方接口**<br>
本软件仅用于**测试 `electron 6.x` 在各种系统上的兼容性**及用于**对比各大音乐平台歌单、排行榜等数据列表的差异性**,使用本软件产生的**任何涉及版权相关的数据**请于**24小时内删除**。<br>
本软件仅用于**测试 `electron 7` 在各种系统上的兼容性**及用于**对比各大音乐平台歌单、排行榜等数据列表的差异性**,使用本软件产生的**任何涉及版权相关的数据**请于**24小时内删除**。<br>
本软件仅用于学习交流使用,禁止用于商业用途,使用本软件所造成的的后果由使用者承担!<br>
若对此有疑问请 mail to: lyswhut@qq.com

4141
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "lx-music-desktop",
"version": "0.9.1",
"version": "0.13.1",
"description": "一个免费的音乐下载助手",
"main": "./dist/electron/main.js",
"productName": "lx-music-desktop",
@@ -49,7 +49,7 @@
"lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-formatter-friendly --fix src"
},
"browserslist": [
"Chrome >= 76"
"Electron >= 7.1.2"
],
"engines": {
"node": ">= 12"
@@ -71,12 +71,17 @@
"artifactName": "${productName} v${version} ${env.ARCH} ${env.TARGET}.${ext}"
},
"mac": {
"icon": "./resources/icons/512x512.png",
"icon": "./resources/icons/512x512.icns",
"category": "public.app-category.music"
},
"linux": {
"maintainer": "lyswhut <lyswuhut@qq.com>",
"artifactName": "${productName} v${version} ${env.ARCH}.${ext}"
"artifactName": "${productName} v${version} ${env.ARCH}.${ext}",
"icon": "./resources/icons",
"category": "Utility;AudioVideo;Audio;Player;Music;",
"desktop": {
"Name[zh_CN]": "洛雪音乐助手"
}
},
"nsis": {
"oneClick": false,
@@ -105,7 +110,7 @@
},
"appImage": {
"license": "./licenses/license_zh.txt",
"category": "Audio"
"category": "Utility;AudioVideo;Audio;Player;Music;"
},
"publish": [
{
@@ -134,38 +139,38 @@
},
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
"devDependencies": {
"@babel/core": "^7.6.4",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/polyfill": "^7.6.0",
"@babel/preset-env": "^7.6.3",
"autoprefixer": "^9.6.5",
"@babel/core": "^7.7.5",
"@babel/plugin-syntax-dynamic-import": "^7.7.4",
"@babel/polyfill": "^7.7.0",
"@babel/preset-env": "^7.7.6",
"autoprefixer": "^9.7.3",
"babel-eslint": "^10.0.3",
"babel-loader": "^8.0.6",
"babel-minify-webpack-plugin": "^0.3.1",
"babel-preset-minify": "^0.5.1",
"cfonts": "^2.4.5",
"chalk": "^2.4.2",
"chalk": "^3.0.0",
"changelog-parser": "^2.8.0",
"copy-webpack-plugin": "^5.0.4",
"core-js": "^3.3.2",
"cos-nodejs-sdk-v5": "^2.5.12",
"copy-webpack-plugin": "^5.1.1",
"core-js": "^3.5.0",
"cos-nodejs-sdk-v5": "^2.5.14",
"cross-env": "^6.0.3",
"css-loader": "^3.2.0",
"css-loader": "^3.3.2",
"del": "^5.1.0",
"electron": "^6.0.12",
"electron": "^7.1.2",
"electron-builder": "^21.2.0",
"electron-debug": "^3.0.1",
"electron-devtools-installer": "^2.2.4",
"eslint": "^6.5.1",
"eslint": "^6.7.2",
"eslint-config-standard": "^14.1.0",
"eslint-formatter-friendly": "^7.0.0",
"eslint-loader": "^3.0.2",
"eslint-loader": "^3.0.3",
"eslint-plugin-html": "^6.0.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-import": "^2.19.1",
"eslint-plugin-node": "^10.0.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"file-loader": "^4.2.0",
"file-loader": "^5.0.2",
"friendly-errors-webpack-plugin": "^1.7.0",
"html-webpack-plugin": "^3.2.0",
"less": "^3.10.3",
@@ -178,18 +183,18 @@
"pug": "^2.0.4",
"pug-loader": "^2.4.0",
"pug-plain-loader": "^1.0.0",
"raw-loader": "^3.1.0",
"raw-loader": "^4.0.0",
"rimraf": "^3.0.0",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"terser-webpack-plugin": "^2.1.3",
"url-loader": "^2.2.0",
"vue-loader": "^15.7.1",
"terser-webpack-plugin": "^2.3.0",
"url-loader": "^3.0.0",
"vue-loader": "^15.7.2",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.10",
"vue-template-compiler": "^2.6.11",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.8.2",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.9.0",
"webpack-hot-middleware": "^2.25.0",
"webpack-merge": "^4.2.2"
},
@@ -197,19 +202,18 @@
"axios": "^0.19.0",
"crypto-js": "^3.1.9-1",
"dnscache": "^1.0.2",
"electron-log": "^3.0.8",
"electron-store": "^5.0.0",
"electron-updater": "^4.1.2",
"electron-log": "^4.0.0",
"electron-store": "^5.1.0",
"electron-updater": "^4.2.0",
"flac-metadata": "^0.1.1",
"js-htmlencode": "^0.3.0",
"lrc-file-parser": "^0.1.14",
"node-downloader-helper": "^1.0.10",
"node-id3": "^0.1.11",
"lrc-file-parser": "^0.1.15",
"node-id3": "^0.1.13",
"request": "^2.88.0",
"vue": "^2.6.10",
"vue": "^2.6.11",
"vue-electron": "^1.0.6",
"vue-router": "^3.1.3",
"vuex": "^3.1.1",
"vuex": "^3.1.2",
"vuex-electron": "^1.0.3",
"vuex-router-sync": "^5.0.0"
}

View File

@@ -1,3 +1,7 @@
#### 修复
### 修复
- 修复没有配置文件时程序启动出错的问题
- 修复全局更新弹窗无法遮盖搜索框的问题
### 其他
- 由于electron 7.1.3 - 7.1.5 的自动更新功能存在Bug现降级到7.1.2

View File

@@ -1,7 +1,31 @@
{
"version": "0.9.1",
"desc": "<h4>修复</h4>\n<ul>\n<li>修复没有配置文件时程序启动出错的问题</li>\n</ul>\n",
"version": "0.13.1",
"desc": "<h3>修复</h3>\n<ul>\n<li>修复全局更新弹窗无法遮盖搜索框的问题</li>\n</ul>\n<h3>其他</h3>\n<ul>\n<li>由于electron 7.1.3 - 7.1.5 的自动更新功能存在Bug现降级到7.1.2</li>\n</ul>\n",
"history": [
{
"version": "0.13.0",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增搜索框搜索建议键盘上下方向键选择功能</li>\n<li>聚合搜索新增音源显示</li>\n<li>新增“离开搜索界面时清空搜索列表”设置选项,默认关闭,可到设置-强迫症设置开启</li>\n</ul>\n<h3>优化</h3>\n<ul>\n<li>优化“信口雌黄”皮肤配色</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复存在弹出层时,搜索建议列表被弹出层覆盖的问题</li>\n<li>修复搜索、排行榜、歌单列表多选框从不定状态到选中的Bug</li>\n</ul>\n<h3>移除</h3>\n<ul>\n<li>因Q音接口失效移除Q音源的试听与下载</li>\n</ul>\n<h3>其他</h3>\n<ul>\n<li>更新electron到7.1.5</li>\n<li>更新vue到2.6.11</li>\n</ul>\n"
},
{
"version": "0.12.1",
"desc": "<h3>优化</h3>\n<ul>\n<li>优化定位歌曲时的列表滚动机制</li>\n<li>优化链接点击效果</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复使用酷我源下载歌曲时,当歌曲无封面时下载报错的问题</li>\n<li>修复酷我源排行榜、歌单详情列表里的歌曲音质匹配问题(原来无论歌曲有无高品、无损都会显示有)</li>\n<li>禁止外部链接在软件内打开,将所有外部链接从默认浏览器打开</li>\n</ul>\n<h3>其他</h3>\n<ul>\n<li>更新electron到7.1.2</li>\n</ul>\n"
},
{
"version": "0.12.0",
"desc": "<p>由于新下载库仍然没有完成但下载功能已经可用so 移除之前使用的第三方下载库,暂时把新下载库的下载模块直接加入本程序,若出现下载问题欢迎反馈!</p>\n<h3>新增</h3>\n<ul>\n<li>新增下载功能对代理设置的支持,现在若在软件设置了代理服务器,下载功能也将会走代理网络了</li>\n</ul>\n<h3>优化</h3>\n<ul>\n<li>新下载模块将对恢复下载的任务进行字节校验用于解决下载进度超过100%后仍然下载的问题</li>\n<li>注意:目前仍然无法暂停处于<strong>链接获取</strong>状态中的任务</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复Linux deb版本<code>.desktop</code>桌面文件缺少图标的问题,新增中文名称显示、软件分类,感谢@lowy的反馈</li>\n<li>修复下载列表歌曲状态分类列表操作Bug</li>\n<li>修复歌曲封面下载失败时仍然执行嵌入封面操作导致报错的问题</li>\n<li>跳过重复添加<strong>相同歌曲名与扩展名的歌曲</strong>例如你之前下载了A歌曲的128k音质现在想要下载它的320k音质但由于两者都是MP3格式会因为重名导致之前的128k音质被覆盖但列表中仍然显示两种音质的问题但实际上都是指向后面的320k音质</li>\n</ul>\n"
},
{
"version": "0.11.0",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增歌曲缓冲定时器,尝试用于解决网络正常但是歌曲缓冲过久的问题</li>\n<li>新增下载管理的任务状态分类</li>\n<li>添加<strong>杀毒软件提示有病毒或恶意行为</strong>的说明,可到<strong>常见问题</strong>拉到最后查看(常见问题可在开源地址找到)</li>\n</ul>\n<h3>优化</h3>\n<ul>\n<li>优化更新弹窗机制及其内容描述,对于可以自动更新的版本,现在可以看到软件的下载进度了</li>\n</ul>\n"
},
{
"version": "0.10.0",
"desc": "<h4>优化</h4>\n<ul>\n<li>大幅减少程序<strong>播放时</strong>对CPU与GPU的使用经测试CPU使用减少60%以上GPU使用减少90%以上这应该能解决MAC系统上的温度上涨的问题</li>\n</ul>\n<h4>修复</h4>\n<ul>\n<li>修复酷我源<strong>搜索提示</strong>、<strong>排行榜</strong>无法获取的问题</li>\n<li>修复咪咕源无法播放的问题</li>\n</ul>\n"
},
{
"version": "0.9.1",
"desc": "<h4>修复</h4>\n<ul>\n<li>修复没有配置文件时程序启动出错的问题</li>\n</ul>\n"
},
{
"version": "0.9.0",
"desc": "<h4>新增</h4>\n<ul>\n<li>新增窗口大小设置,若觉得软件窗口小可以到设置页调大点</li>\n<li>新增定位当前播放歌曲,点击播放栏左侧的<strong>歌曲图片</strong>可在播放列表定位当前播放的歌曲(该功能对播放下载列表的歌曲无效)</li>\n</ul>\n<h4>修复</h4>\n<ul>\n<li>修复搜索提示失效的问题</li>\n<li>修复从歌单或列表点击搜索按钮搜索目标歌曲时,搜索框未聚焦仍然弹出候选搜索列表的问题</li>\n</ul>\n"

View File

@@ -1,19 +0,0 @@
const { ipcMain, ipcRenderer } = require('electron')
export const mainSend = (name, params) => {
ipcMain.send(name, params)
}
export const mainOn = (name, callback) => {
ipcMain.on(name, callback)
}
export const rendererSend = (name, params) => {
ipcRenderer.send(name, params)
}
export const rendererOn = (name, callback) => {
ipcRenderer.on(name, callback)
}

31
src/common/ipc.js Normal file
View File

@@ -0,0 +1,31 @@
const { ipcMain, ipcRenderer } = require('electron')
exports.mainOn = (event, callback) => {
ipcMain.on(event, callback)
}
exports.mainOnce = (event, callback) => {
ipcMain.once(event, callback)
}
exports.mainHandle = (name, callback) => {
ipcMain.handle(name, callback)
}
exports.mainHandleOnce = (name, callback) => {
ipcMain.handleOnce(name, callback)
}
exports.rendererSend = (name, params) => {
ipcRenderer.send(name, params)
}
exports.rendererSendSync = (name, params) => ipcRenderer.sendSync(name, params)
exports.rendererInvoke = (name, params) => ipcRenderer.invoke(name, params)
exports.rendererOn = (name, callback) => {
ipcRenderer.on(name, callback)
}
exports.rendererOnce = (name, callback) => {
ipcRenderer.once(name, callback)
}

View File

@@ -1,4 +1,4 @@
const { mainOn } = require('../../common/icp')
const { mainOn } = require('../../common/ipc')
const { app } = require('electron')
const { name: defaultName } = require('../../../package.json')

View File

@@ -0,0 +1,8 @@
const { mainHandle } = require('../../common/ipc')
mainHandle('clearCache', async(event, options) => {
if (!global.mainWindow) throw new Error('mainwindow is undefined')
return global.mainWindow.webContents.session.clearCache()
})

View File

@@ -0,0 +1,7 @@
const { mainHandle } = require('../../common/ipc')
mainHandle('getCacheSize', async(event, options) => {
if (!global.mainWindow) throw new Error('mainwindow is undefined')
return global.mainWindow.webContents.session.getCacheSize()
})

View File

@@ -4,3 +4,9 @@ require('./request')
require('./progressBar')
require('./trafficLight')
require('./musicMeta')
require('./selectDir')
require('./setWindowSize')
require('./showSaveDialog')
require('./clearCache')
require('./getCacheSize')
require('./setIgnoreMouseEvent')

View File

@@ -1,4 +1,4 @@
const { mainOn } = require('../../common/icp')
const { mainOn } = require('../../common/ipc')
const { setMeta } = require('../utils/musicMeta')
mainOn('setMusicMeta', (event, { filePath, meta }) => {

View File

@@ -1,4 +1,4 @@
const { mainOn } = require('../../common/icp')
const { mainOn } = require('../../common/ipc')
mainOn('progress', (event, params) => {

View File

@@ -1,6 +1,6 @@
const request = require('request')
const { mainOn } = require('../../common/icp')
const { mainOn } = require('../../common/ipc')
const tasks = []

View File

@@ -1,4 +1,4 @@
const { mainOn } = require('../../common/icp')
const { mainOn } = require('../../common/ipc')
mainOn('restartWindow', (event, name) => {

View File

@@ -0,0 +1,8 @@
const { mainHandle } = require('../../common/ipc')
const { dialog } = require('electron')
mainHandle('selectDir', async(event, options) => {
if (!global.mainWindow) throw new Error('mainwindow is undefined')
return dialog.showOpenDialog(global.mainWindow, options)
})

View File

@@ -1,11 +0,0 @@
const { mainOn } = require('../../common/icp')
const { dialog } = require('electron')
module.exports = win => {
mainOn('selectPath', (event, params) => {
let path = dialog.showOpenDialog(win, params.options)
if (path === undefined) return
event.sender.send(params.eventName, path)
})
}

View File

@@ -0,0 +1,8 @@
const { mainOn } = require('../../common/ipc')
mainOn('setIgnoreMouseEvents', (event, isIgnored) => {
if (!global.mainWindow) return
isIgnored
? global.mainWindow.setIgnoreMouseEvents(true, { forward: true })
: global.mainWindow.setIgnoreMouseEvents(false)
})

View File

@@ -0,0 +1,7 @@
const { mainOn } = require('../../common/ipc')
mainOn('setWindowSize', (event, options) => {
if (!global.mainWindow) return
global.mainWindow.setBounds(options)
})

View File

@@ -0,0 +1,8 @@
const { mainHandle } = require('../../common/ipc')
const { dialog } = require('electron')
mainHandle('showSaveDialog', async(event, options) => {
if (!global.mainWindow) throw new Error('mainwindow is undefined')
return dialog.showSaveDialog(global.mainWindow, options)
})

View File

@@ -1,5 +1,5 @@
// const { app } = require('electron')
const { mainOn } = require('../../common/icp')
const { mainOn } = require('../../common/ipc')
mainOn('min', event => {
@@ -7,11 +7,11 @@ mainOn('min', event => {
global.mainWindow.minimize()
}
})
// mainOn('max', event => {
// if (global.mainWindow) {
// global.mainWindow.maximize()
// }
// })
mainOn('max', event => {
if (global.mainWindow) {
global.mainWindow.maximize()
}
})
mainOn('close', event => {
if (global.mainWindow) {
// global.mainWindowdow.destroy()

View File

@@ -50,6 +50,7 @@ function createWindow() {
width: windowSizeInfo.width,
frame: false,
transparent: !isLinux,
enableRemoteModule: false,
// icon: path.join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'),
resizable: false,
maximizable: false,

View File

@@ -1,6 +1,6 @@
const { log } = require('../../common/utils')
const { autoUpdater } = require('electron-updater')
const { mainOn } = require('../../common/icp')
const { mainOn } = require('../../common/ipc')
autoUpdater.logger = log
// autoUpdater.autoDownload = false
@@ -21,7 +21,7 @@ log.info('App starting...')
function sendStatusToWindow(text) {
log.info(text)
// global.mainWindow.webContents.send('message', text)
// ipcMain.send('message', text)
}
@@ -90,16 +90,20 @@ module.exports = isFirstCheckedUpdate => {
sendStatusToWindow('Update not available.')
handleSendEvent({ type: 'update-not-available' })
})
autoUpdater.on('error', () => {
autoUpdater.on('error', err => {
sendStatusToWindow('Error in auto-updater.')
handleSendEvent({ type: 'update-error' })
handleSendEvent({ type: 'update-error', info: err.message })
})
autoUpdater.on('download-progress', progressObj => {
sendStatusToWindow('Download progress...')
let log_message = 'Download speed: ' + progressObj.bytesPerSecond
log_message = log_message + ' - Downloaded ' + progressObj.percent + '%'
log_message = log_message + ' (' + progressObj.transferred + '/' + progressObj.total + ')'
sendStatusToWindow(log_message)
handleSendEvent({ type: 'update-progress', info: progressObj })
})
autoUpdater.on('update-downloaded', info => {
sendStatusToWindow('Update downloaded.')
handleSendEvent({ type: 'update-downloaded' })
handleSendEvent({ type: 'update-downloaded', info })
})
mainOn('quit-update', () => {
setTimeout(() => {

View File

@@ -6,12 +6,39 @@ const extReg = /^(\.(?:jpe?g|png)).*$/
module.exports = (filePath, meta) => {
if (!meta.APIC) return NodeID3.write(meta, filePath)
if (!/^http/.test(meta.APIC)) {
delete meta.APIC
return NodeID3.write(meta, filePath)
}
let picPath = filePath.replace(/\.mp3$/, '') + path.extname(meta.APIC).replace(extReg, '$1')
request(meta.APIC).pipe(fs.createWriteStream(picPath)).on('finish', () => {
meta.APIC = picPath
NodeID3.write(meta, filePath)
fs.unlink(picPath, err => {
if (err) console.log(err.message)
request(meta.APIC)
.on('response', respones => {
if (respones.statusCode !== 200 && respones.statusCode != 206) {
delete meta.APIC
NodeID3.write(meta, filePath)
return
}
respones
.pipe(fs.createWriteStream(picPath))
.on('finish', () => {
if (respones.complete) {
meta.APIC = picPath
NodeID3.write(meta, filePath)
} else {
delete meta.APIC
}
fs.unlink(picPath, err => {
if (err) console.log(err.message)
})
}).on('error', err => {
if (err) console.log(err.message)
delete meta.APIC
NodeID3.write(meta, filePath)
})
})
.on('error', err => {
if (err) console.log(err.message)
delete meta.APIC
NodeID3.write(meta, filePath)
})
})
}

View File

@@ -20,20 +20,16 @@
<script>
import dnscache from 'dnscache'
import { mapMutations, mapGetters, mapActions } from 'vuex'
import { rendererOn } from '../common/icp'
import { rendererOn, rendererSend } from '../common/ipc'
import { isLinux } from '../common/utils'
import music from './utils/music'
import { throttle, openUrl } from './utils'
window.ELECTRON_DISABLE_SECURITY_WARNINGS = process.env.ELECTRON_DISABLE_SECURITY_WARNINGS
dnscache({
enable: true,
ttl: 21600,
cachesize: 1000,
})
let win
let body
if (!isLinux) {
win = require('electron').remote.getCurrentWindow()
body = document.body
}
export default {
data() {
@@ -55,6 +51,20 @@ export default {
downloadStatus: 'downloadStatus',
}),
},
created() {
this.saveSetting = throttle(n => {
this.electronStore.set('setting', n)
})
this.saveDefaultList = throttle(n => {
this.electronStore.set('list.defaultList', n)
}, 500)
this.saveLoveList = throttle(n => {
this.electronStore.set('list.loveList', n)
}, 500)
this.saveDownloadList = throttle(n => {
this.electronStore.set('download.list', n)
}, 1000)
},
mounted() {
document.body.classList.add(this.isLinux ? 'noTransparent' : 'transparent')
this.init()
@@ -62,27 +72,25 @@ export default {
watch: {
setting: {
handler(n) {
this.electronStore.set('setting', n)
this.saveSetting(n)
},
deep: true,
},
defaultList: {
handler(n) {
// console.log(n)
this.electronStore.set('list.defaultList', n)
this.saveDefaultList(n)
},
deep: true,
},
loveList: {
handler(n) {
// console.log(n)
this.electronStore.set('list.loveList', n)
this.saveLoveList(n)
},
deep: true,
},
downloadList: {
handler(n) {
this.electronStore.set('download.list', n)
this.saveDownloadList(n)
},
deep: true,
},
@@ -96,37 +104,52 @@ export default {
},
methods: {
...mapActions(['getVersionInfo']),
...mapMutations(['setNewVersion', 'setVersionModalVisible']),
...mapMutations(['setNewVersion', 'setVersionModalVisible', 'setDownloadProgress']),
...mapMutations('list', ['initList']),
...mapMutations('download', ['updateDownloadList']),
...mapMutations(['setSetting']),
init() {
document.body.addEventListener('click', this.handleBodyClick, true)
if (this.isProd && !isLinux) {
body.addEventListener('mouseenter', this.dieableIgnoreMouseEvents)
body.addEventListener('mouseleave', this.enableIgnoreMouseEvents)
document.body.addEventListener('mouseenter', this.dieableIgnoreMouseEvents)
document.body.addEventListener('mouseleave', this.enableIgnoreMouseEvents)
}
rendererOn('update-available', (e, info) => {
// this.showUpdateModal(true)
this.setNewVersion({
// console.log(info)
this.setVersionModalVisible({ isDownloading: true })
this.getVersionInfo().catch(() => ({
version: info.version,
desc: info.releaseNotes,
})).then(body => {
// console.log(body)
this.setNewVersion(body)
this.$nextTick(() => {
this.setVersionModalVisible({ isShow: true })
})
})
})
rendererOn('update-error', () => {
if (!this.updateTimeout) return
this.setVersionModalVisible({ isError: true })
rendererOn('update-error', (event, err) => {
// console.log(err)
this.clearUpdateTimeout()
this.setVersionModalVisible({ isError: true })
this.$nextTick(() => {
this.showUpdateModal()
})
})
rendererOn('update-downloaded', () => {
rendererOn('update-progress', (event, progress) => {
// console.log(progress)
this.setDownloadProgress(progress)
})
rendererOn('update-downloaded', info => {
// console.log(info)
this.clearUpdateTimeout()
this.setVersionModalVisible({ isError: false })
this.showUpdateModal()
this.setVersionModalVisible({ isDownloaded: true })
this.$nextTick(() => {
this.showUpdateModal()
})
})
rendererOn('update-not-available', () => {
if (!this.updateTimeout) return
if (this.setting.ignoreVersion) this.setSetting(Object.assign({}, this.setting, { ignoreVersion: null }))
this.clearUpdateTimeout()
this.setNewVersion({
version: this.version.version,
@@ -135,26 +158,29 @@ export default {
// 更新超时定时器
this.updateTimeout = setTimeout(() => {
this.updateTimeout = null
this.setVersionModalVisible({ isError: true })
this.setVersionModalVisible({ isTimeOut: true })
this.$nextTick(() => {
this.showUpdateModal()
})
}, 180000)
}, 60 * 30 * 1000)
this.initData()
this.globalObj.apiSource = this.setting.apiSource
this.globalObj.proxy = Object.assign({}, this.setting.network.proxy)
window.globalObj = this.globalObj
// 初始化音乐sdk
music.init()
},
enableIgnoreMouseEvents() {
if (isLinux) return
win.setIgnoreMouseEvents(false)
rendererSend('setIgnoreMouseEvents', false)
// console.log('content enable')
},
dieableIgnoreMouseEvents() {
if (isLinux) return
// console.log('content disable')
win.setIgnoreMouseEvents(true, { forward: true })
rendererSend('setIgnoreMouseEvents', true)
},
initData() { // 初始化数据
@@ -181,12 +207,19 @@ export default {
showUpdateModal() {
(this.version.newVersion && this.version.newVersion.history ? Promise.resolve(this.version.newVersion) : this.getVersionInfo().then(body => {
this.setNewVersion(body)
if (body.version !== this.setting.ignoreVersion) this.setSetting(Object.assign({}, this.setting, { ignoreVersion: null }))
return body
})).then(body => {
if (body.version === this.version.version) return
if (this.version.isError && body.version === this.setting.ignoreVersion) return
})).catch(() => {
if (this.version.newVersion) return this.version.newVersion
this.setVersionModalVisible({ isUnknow: true })
let result = {
version: '0.0.0',
desc: null,
}
this.setNewVersion(result)
return result
}).then(result => {
if (result.version === this.version.version) return
// console.log(this.version)
this.$nextTick(() => {
this.setVersionModalVisible({ isShow: true })
})
@@ -197,13 +230,20 @@ export default {
clearTimeout(this.updateTimeout)
this.updateTimeout = null
},
handleBodyClick(event) {
if (event.target.tagName != 'A') return
if (event.target.host == window.location.host) return
event.preventDefault()
if (/^https?:\/\//.test(event.target.href)) openUrl(event.target.href)
},
},
beforeDestroy() {
this.clearUpdateTimeout()
if (this.isProd) {
body.removeEventListener('mouseenter', this.dieableIgnoreMouseEvents)
body.removeEventListener('mouseleave', this.enableIgnoreMouseEvents)
document.body.removeEventListener('mouseenter', this.dieableIgnoreMouseEvents)
document.body.removeEventListener('mouseleave', this.enableIgnoreMouseEvents)
}
document.body.removeEventListener('click', this.handleBodyClick)
},
}
</script>

View File

@@ -57,6 +57,10 @@ table {
}
}
a {
color: #000;
}
.badge {
display: inline-block;
padding: 0.25em 0.4em;
@@ -112,7 +116,7 @@ svg {
transition-property: fill;
}
.hover {
.hover, a {
cursor: pointer;
transition: color .2s ease;
&:hover {
@@ -150,7 +154,7 @@ svg {
each(@themes, {
#container.@{value} {
.hover {
.hover, a {
&:hover {
color: ~'@{color-@{value}-theme}';
}

View File

@@ -7,7 +7,7 @@
// @color-theme: #03a678;
@color-theme: #4daf7c;
@color-theme-bgimg: none;
@color-theme-bgposition: center center;
@color-theme-bgposition: center;
@color-theme-bgsize: auto auto;
@color-theme-hover: fadeout(lighten(@color-theme, 10%), 30%);
@color-theme-active: fadeout(darken(@color-theme, 20%), 60%);
@@ -59,7 +59,7 @@
@color-green-theme: #4daf7c;
@color-green-theme-bgimg: none;
@color-green-theme-bgposition: center center;
@color-green-theme-bgposition: center;
@color-green-theme-bgsize: auto auto;
@color-green-theme-hover: fadeout(lighten(@color-green-theme, 10%), 30%);
@color-green-theme-active: fadeout(darken(@color-green-theme, 20%), 60%);
@@ -97,9 +97,9 @@
@color-green-tab-border-bottom: lighten(@color-green-theme, 5%);
@color-yellow-theme: #f2d35b;
@color-yellow-theme: #e9d460;
@color-yellow-theme-bgimg: none;
@color-yellow-theme-bgposition: center center;
@color-yellow-theme-bgposition: center;
@color-yellow-theme-bgsize: auto auto;
@color-yellow-theme-hover: fadeout(lighten(@color-yellow-theme, 10%), 30%);
@color-yellow-theme-active: fadeout(darken(@color-yellow-theme, 20%), 60%);
@@ -108,37 +108,37 @@
@color-yellow-theme_2: #fff;
@color-yellow-theme_2-background_1: #fff;
@color-yellow-theme_2-background_2: fadeout(@color-yellow-theme_2-background_1, 2%);
@color-yellow-theme_2-hover: fadeout(lighten(@color-yellow-theme, 10%), 70%);
@color-yellow-theme_2-active: fadeout(darken(@color-yellow-theme, 5%), 70%);
@color-yellow-theme_2-hover: fadeout(lighten(@color-yellow-theme, 10%), 60%);
@color-yellow-theme_2-active: fadeout(darken(@color-yellow-theme, 5%), 60%);
@color-yellow-theme_2-font: darken(@color-yellow-theme_2, 70%);
@color-yellow-theme_2-font-label: fadeout(@color-yellow-theme_2-font, 50%);
@color-yellow-theme_2-line: lighten(@color-yellow-theme, 35%);
@color-yellow-theme_2-font-label: fadeout(@color-yellow-theme_2-font, 40%);
@color-yellow-theme_2-line: lighten(@color-yellow-theme, 25%);
@color-yellow-theme-sidebar: @color-yellow-theme;
@color-yellow-btn: fadeout(darken(@color-yellow-theme, 5%), 15%);
@color-yellow-btn-background: fadeout(lighten(@color-yellow-theme, 25%), 70%);
@color-yellow-btn: fadeout(darken(@color-yellow-theme, 5%), 5%);
@color-yellow-btn-background: fadeout(lighten(@color-yellow-theme, 25%), 60%);
@color-yellow-pagination-background: fadeout(lighten(@color-yellow-theme, 30%), 30%);
@color-yellow-pagination-hover: fadeout(lighten(@color-yellow-theme, 5%), 70%);
@color-yellow-pagination-active: fadeout(darken(@color-yellow-theme, 5%), 70%);
@color-yellow-pagination-select: fadeout(lighten(@color-yellow-theme, 5%), 50%);
@color-yellow-search-form-background: fadeout(lighten(@color-yellow-theme, 35%), 10%);
@color-yellow-search-list-hover: fadeout(darken(@color-yellow-theme, 10%), 70%);
@color-yellow-scrollbar-track: fadeout(@color-yellow-theme, 80%);
@color-yellow-scrollbar-thumb: fadeout(@color-yellow-theme, 60%);
@color-yellow-scrollbar-thumb-hover: fadeout(@color-yellow-theme, 40%);
@color-yellow-search-form-background: fadeout(lighten(@color-yellow-theme, 25%), 10%);
@color-yellow-search-list-hover: fadeout(darken(@color-yellow-theme, 10%), 60%);
@color-yellow-scrollbar-track: fadeout(@color-yellow-theme, 60%);
@color-yellow-scrollbar-thumb: fadeout(@color-yellow-theme, 45%);
@color-yellow-scrollbar-thumb-hover: fadeout(@color-yellow-theme, 30%);
@color-yellow-player-pic-c1: fadeout(@color-yellow-theme_2, 50%);
@color-yellow-player-pic-c2: darken(@color-yellow-theme_2, 30%);
@color-yellow-player-progress: darken(@color-yellow-theme_2, 6%);
@color-yellow-player-progress-bar1: darken(@color-yellow-theme_2, 12%);
@color-yellow-player-progress-bar2: lighten(@color-yellow-theme, 12%);
@color-yellow-player-progress-bar2: lighten(@color-yellow-theme, 2%);
@color-yellow-player-status-text: lighten(@color-yellow-theme_2-font, 10%);
@color-yellow-tab-btn-background: fadeout(lighten(@color-yellow-theme, 10%), 80%);
@color-yellow-tab-btn-background: fadeout(lighten(@color-yellow-theme, 10%), 70%);
@color-yellow-tab-btn-background-hover: @color-yellow-theme_2-hover;
@color-yellow-tab-border-top: fadeout(lighten(@color-yellow-theme, 5%), 50%);
@color-yellow-tab-border-bottom: lighten(@color-yellow-theme, 5%);
@color-yellow-tab-border-top: fadeout(lighten(@color-yellow-theme, 5%), 40%);
@color-yellow-tab-border-bottom: @color-yellow-theme;
@color-orange-theme: #f5ab35;
@color-orange-theme-bgimg: none;
@color-orange-theme-bgposition: center center;
@color-orange-theme-bgposition: center;
@color-orange-theme-bgsize: auto auto;
@color-orange-theme-hover: fadeout(lighten(@color-orange-theme, 10%), 30%);
@color-orange-theme-active: fadeout(darken(@color-orange-theme, 20%), 60%);
@@ -177,7 +177,7 @@
@color-blue-theme: #3498db;
@color-blue-theme-bgimg: none;
@color-blue-theme-bgposition: center center;
@color-blue-theme-bgposition: center;
@color-blue-theme-bgsize: auto auto;
@color-blue-theme-hover: fadeout(lighten(@color-blue-theme, 10%), 30%);
@color-blue-theme-active: fadeout(darken(@color-blue-theme, 20%), 60%);
@@ -216,7 +216,7 @@
@color-red-theme: #d64541;
@color-red-theme-bgimg: none;
@color-red-theme-bgposition: center center;
@color-red-theme-bgposition: center;
@color-red-theme-bgsize: auto auto;
@color-red-theme-hover: fadeout(lighten(@color-red-theme, 10%), 30%);
@color-red-theme-active: fadeout(darken(@color-red-theme, 20%), 60%);
@@ -257,7 +257,7 @@
@color-purple-theme: #9b59b6;
@color-purple-theme-bgimg: none;
@color-purple-theme-bgposition: center center;
@color-purple-theme-bgposition: center;
@color-purple-theme-bgsize: auto auto;
@color-purple-theme-hover: fadeout(lighten(@color-purple-theme, 10%), 30%);
@color-purple-theme-active: fadeout(darken(@color-purple-theme, 20%), 60%);
@@ -296,7 +296,7 @@
@color-grey-theme: #6c7a89;
@color-grey-theme-bgimg: none;
@color-grey-theme-bgposition: center center;
@color-grey-theme-bgposition: center;
@color-grey-theme-bgsize: auto auto;
@color-grey-theme-hover: fadeout(lighten(@color-grey-theme, 10%), 30%);
@color-grey-theme-active: fadeout(darken(@color-grey-theme, 20%), 60%);
@@ -335,7 +335,7 @@
@color-midAutumn-theme: rgba(74, 55, 82, 1);
@color-midAutumn-theme-bgimg: url(../images/jqbg.jpg);
@color-midAutumn-theme-bgposition: center center;
@color-midAutumn-theme-bgposition: center;
@color-midAutumn-theme-bgsize: auto 100%;
@color-midAutumn-theme-hover: fadeout(lighten(@color-midAutumn-theme, 10%), 30%);
@color-midAutumn-theme-active: fadeout(lighten(@color-midAutumn-theme, 15%), 60%);
@@ -374,7 +374,7 @@
@color-dhHyrz-theme: rgb(87, 144, 167);
@color-dhHyrz-theme-bgimg: url(../images/hzwbg.jpeg);
@color-dhHyrz-theme-bgposition: center center;
@color-dhHyrz-theme-bgposition: center;
@color-dhHyrz-theme-bgsize: auto 100%;
@color-dhHyrz-theme-hover: fadeout(lighten(@color-dhHyrz-theme, 10%), 45%);
@color-dhHyrz-theme-active: fadeout(lighten(@color-dhHyrz-theme, 15%), 60%);

View File

@@ -115,6 +115,9 @@ export default {
&:hover:not(.active) {
background-color: @color-theme-hover;
}
&:hover:not(.active) {
background-color: @color-theme-active;
}
}
}
}
@@ -141,6 +144,9 @@ each(@themes, {
&:hover:not(.active) {
background-color: ~'@{color-@{value}-theme-hover}';
}
&:active:not(.active) {
background-color: ~'@{color-@{value}-theme-active}';
}
}
}
}

View File

@@ -27,7 +27,7 @@ div(:class="$style.player")
div(:class="$style.column2")
div(:class="$style.progress")
//- div(:class="[$style.progressBar, $style.progressBar1]" :style="{ transform: `scaleX(${progress || 0})` }")
div(:class="[$style.progressBar, $style.progressBar2]" :style="{ transform: `scaleX(${progress || 0})` }")
div(:class="[$style.progressBar, $style.progressBar2, isActiveTransition ? $style.barTransition : '']" @transitionend="handleTransitionEnd" :style="{ transform: `scaleX(${progress || 0})` }")
div(:class="$style.progressMask" @click='setProgess' ref="dom_progress")
div(:class="$style.column3")
span {{nowPlayTimeStr}}
@@ -48,7 +48,7 @@ div(:class="$style.player")
<script>
import Lyric from 'lrc-file-parser'
import { rendererSend } from '../../../common/icp'
import { rendererSend } from '../../../common/ipc'
import { formatPlayTime2, getRandom, checkPath, setTitle, clipboardWriteText, debounce } from '../../utils'
import { mapGetters, mapActions, mapMutations } from 'vuex'
import { requestMsg } from '../../utils/message'
@@ -88,6 +88,11 @@ export default {
msDownX: 0,
msDownVolume: 0,
},
isActiveTransition: false,
mediaBuffer: {
timeout: null,
playTime: 0,
},
}
},
computed: {
@@ -213,7 +218,7 @@ export default {
this.stopPlay()
if (this.listId != 'download' && this.audio.error.code !== 1 && this.retryNum < 2) { // 若音频URL无效则尝试刷新2次URL
// console.log(this.retryNum)
this.audioErrorTime = this.audio.currentTime // 记录出错的播放时间
if (!this.audioErrorTime) this.audioErrorTime = this.audio.currentTime // 记录出错的播放时间
this.retryNum++
this.setUrl(this.list[this.playIndex], true)
this.status = 'URL过期正在刷新URL...'
@@ -238,6 +243,14 @@ export default {
})
this.audio.addEventListener('canplay', () => {
console.log('加载完成开始播放')
if (this.mediaBuffer.playTime) {
let playTime = this.mediaBuffer.playTime
this.mediaBuffer.playTime = 0
this.audio.currentTime = playTime
}
if (this.mediaBuffer.timeout) {
this.clearBufferTimeout()
}
// if (this.musicInfo.lrc) this.lyric.lrc.play(this.audio.currentTime * 1000)
this.status = '音乐加载中...'
})
@@ -246,10 +259,13 @@ export default {
// // if (this.musicInfo.lyric.orgLrc) this.musicInfo.lyric.lrc.play(this.audio.currentTime * 1000)
// this.status = '播放中...'
// })
// this.audio.addEventListener('emptied', () => {
// console.log('媒介资源元素突然为空,网络错误 or 切换歌曲?')
// this.status = '媒介资源元素突然为空,网络错误?'
// })
this.audio.addEventListener('emptied', () => {
this.mediaBuffer.playTime = 0
this.clearBufferTimeout()
// console.log('媒介资源元素突然为空,网络错误 or 切换歌曲?')
// this.status = '媒介资源元素突然为空,网络错误?'
})
this.audio.addEventListener('timeupdate', () => {
this.nowPlayTime = this.audio.currentTime
@@ -257,7 +273,9 @@ export default {
this.audio.addEventListener('waiting', () => {
// this.musicInfo.lyric.lrc.pause()
// console.log('缓冲中...')
this.stopPlay()
this.startBuffering()
this.status = '缓冲中...'
})
@@ -321,7 +339,7 @@ export default {
} else if (this.isAPITemp) {
list = this.list.filter(s => s.source == 'kw')
} else {
list = this.list
list = this.list.filter(s => s.source != 'tx')
}
if (!list.length) return this.setPlayIndex(-1)
let playIndex = this.list === list ? this.playIndex : list.indexOf(this.list[this.playIndex])
@@ -367,18 +385,33 @@ export default {
},
setProgess(e) {
if (!this.audio.src) return
this.audio.currentTime =
(e.offsetX / this.pregessWidth) * this.maxPlayTime
if (!this.isPlay) this.audio.play()
this.isActiveTransition = true
this.$nextTick(() => {
const time = (e.offsetX / this.pregessWidth) * this.maxPlayTime
if (this.audioErrorTime) this.audioErrorTime = time
if (this.mediaBuffer.playTime) {
this.clearBufferTimeout()
this.mediaBuffer.playTime = time
this.startBuffering()
}
this.audio.currentTime = time
if (!this.isPlay) this.audio.play()
})
},
setProgessWidth() {
this.pregessWidth = parseInt(
window.getComputedStyle(this.$refs.dom_progress, null).width
window.getComputedStyle(this.$refs.dom_progress, null).width,
)
},
togglePlay() {
if (!this.audio.src) return
this.isPlay ? this.audio.pause() : this.audio.play()
if (this.isPlay) {
this.audio.pause()
this.clearBufferTimeout()
} else {
this.audio.play()
}
},
imgError(e) {
// e.target.src = 'https://y.gtimg.cn/music/photo_new/T002R500x500M000002BMEC42fM8S3.jpg'
@@ -503,6 +536,36 @@ export default {
},
})
},
handleTransitionEnd(e) {
// console.log(e)
this.isActiveTransition = false
},
startBuffering() {
console.log('start t')
if (this.mediaBuffer.timeout) return
this.mediaBuffer.timeout = setTimeout(() => {
this.mediaBuffer.timeout = null
if (!this.mediaBuffer.playTime) this.mediaBuffer.playTime = this.audio.currentTime
let skipTime = this.audio.currentTime + getRandom(3, 6)
if (skipTime > this.maxPlayTime) skipTime = (this.maxPlayTime - this.audio.currentTime) / 2
if (skipTime - this.mediaBuffer.playTime < 1 || this.maxPlayTime - skipTime < 1) {
this.mediaBuffer.playTime = 0
this.handleNext()
return
}
this.startBuffering()
this.audio.currentTime = skipTime
console.log(this.mediaBuffer.playTime)
console.log(this.audio.currentTime)
}, 3000)
},
clearBufferTimeout() {
console.log('clear t')
if (!this.mediaBuffer.timeout) return
clearTimeout(this.mediaBuffer.timeout)
this.mediaBuffer.timeout = null
this.mediaBuffer.playTime = 0
},
},
}
</script>
@@ -693,21 +756,23 @@ export default {
width: 100%;
height: 100%;
transform-origin: 0;
transition-property: transform;
transition-timing-function: ease-out;
border-radius: @radius-progress-border;
}
.progress-bar1 {
transition-duration: 0.6s;
background-color: @color-player-progress-bar1;
}
.progress-bar2 {
transition-duration: 0.2s;
background-color: @color-player-progress-bar2;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
}
.bar-transition {
transition-property: transform;
transition-timing-function: ease-out;
transition-duration: 0.2s;
}
.column3 {
transition: @transition-theme;
transition-property: color;

View File

@@ -12,8 +12,8 @@
</template>
<script>
import { rendererSend } from 'common/icp'
import { mapGetters } from 'vuex'
import { rendererSend } from 'common/ipc'
import { mapGetters, mapMutations } from 'vuex'
import music from '../../utils/music'
import { debounce } from '../../utils'
export default {
@@ -34,15 +34,21 @@ export default {
source() {
return this.setting.search.tempSearchSource
},
isAutoClearInput() {
isAutoClearSearchInput() {
return this.setting.odc.isAutoClearSearchInput
},
isAutoClearSearchList() {
return this.setting.odc.isAutoClearSearchList
},
},
watch: {
route(n) {
if (this.isAutoClearInput && n.name != 'search' && this.searchText) this.searchText = ''
if (n.name != 'search') {
if (this.isAutoClearSearchInput && this.searchText) this.searchText = ''
if (this.isAutoClearSearchList) this.clearSearchList()
}
},
'storeSearchText'(n) {
storeSearchText(n) {
if (n !== this.searchText) this.searchText = n
},
searchText(n) {
@@ -62,6 +68,9 @@ export default {
}, 50)
},
methods: {
...mapMutations('search', {
clearSearchList: 'clearList',
}),
handleEvent({ action, data }) {
switch (action) {
case 'focus':
@@ -127,7 +136,7 @@ export default {
align-items: center;
padding-left: 15px;
-webkit-app-region: drag;
z-index: 1;
z-index: 2;
position: relative;
}
.input {

View File

@@ -13,6 +13,7 @@ div(:class="$style.view")
.view {
position: relative;
z-index: 1;
> * {
position: absolute;
width: 100%;

View File

@@ -2,16 +2,12 @@ import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
const requireComponent = require.context(
'./', true, /\.vue$/
)
const requireComponent = require.context('./', true, /\.vue$/)
requireComponent.keys().forEach(fileName => {
const componentConfig = requireComponent(fileName)
const componentName = upperFirst(
camelCase(fileName.replace(/^\.\//, '').replace(/\.\w+$/, ''))
)
const componentName = upperFirst(camelCase(fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')))
Vue.component(componentName, componentConfig.default || componentConfig)
})

View File

@@ -71,9 +71,9 @@ export default {
let bool = this.bool
if (this.indeterminate) {
bool = true
// this.$nextTick(() => {
this.bool = true
// })
this.$nextTick(() => {
this.bool = bool
})
}
checked = bool
} else {

View File

@@ -4,7 +4,9 @@ div(:class="[$style.search, focus ? $style.active : '', big ? $style.big : '', s
input(:placeholder="placeholder" v-model.trim="text"
@focus="handleFocus" @blur="handleBlur" @input="$emit('input', text)"
@change="sendEvent('change')"
@keyup.enter="handleSearch")
@keyup.enter="handleSearch"
@keyup.40.prevent="handleKeyDown"
@keyup.38.prevent="handleKeyUp")
button(type="button" @click="handleSearch")
slot
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 30.239 30.239' space='preserve')
@@ -14,7 +16,7 @@ div(:class="[$style.search, focus ? $style.active : '', big ? $style.big : '', s
//- leave-active-class="animated flipOutX")
div(v-if="list" :class="$style.list" :style="listStyle")
ul(ref="dom_list")
li(v-for="(item, index) in list" :key="item" @click="handleTemplistClick(index)")
li(v-for="(item, index) in list" :key="item" :class="selectIndex === index ? $style.select : null" @mouseenter="selectIndex = index" @click="handleTemplistClick(index)")
span {{item}}
</template>
@@ -49,7 +51,7 @@ export default {
return {
isShow: false,
text: '',
index: null,
selectIndex: -1,
focus: false,
listStyle: {
height: 0,
@@ -59,6 +61,7 @@ export default {
watch: {
list(n) {
if (!this.visibleList) return
if (this.selectIndex > -1) this.selectIndex = -1
this.$nextTick(() => {
this.listStyle.height = this.$refs.dom_list.scrollHeight + 'px'
})
@@ -84,7 +87,8 @@ export default {
},
handleSearch() {
this.hideList()
this.sendEvent('submit')
if (this.selectIndex < 0) return this.sendEvent('submit')
this.sendEvent('listClick', this.selectIndex)
},
showList() {
this.isShow = true
@@ -93,6 +97,9 @@ export default {
hideList() {
this.isShow = false
this.listStyle.height = 0
this.$nextTick(() => {
this.selectIndex = -1
})
},
sendEvent(action, data) {
this.$emit('event', {
@@ -100,6 +107,12 @@ export default {
data,
})
},
handleKeyDown() {
this.selectIndex = this.selectIndex + 1 < this.list.length ? this.selectIndex + 1 : 0
},
handleKeyUp() {
this.selectIndex = this.selectIndex - 1 < -1 ? this.list.length - 1 : this.selectIndex - 1
},
},
}
</script>
@@ -187,7 +200,7 @@ export default {
.mixin-ellipsis-2;
}
&:hover {
&.select {
background-color: @color-search-list-hover;
}
&:last-child {
@@ -239,7 +252,7 @@ each(@themes, {
}
.list {
li {
&:hover {
&.select {
background-color: ~'@{color-@{value}-search-list-hover}';
}
}

View File

@@ -29,9 +29,9 @@ div(:class="$style.songList")
td(style="width: 20%; padding-left: 0; padding-right: 0;")
material-list-buttons(:index="index" :search-btn="true"
:remove-btn="false" @btn-click="handleListBtnClick"
:listAdd-btn="item.source == 'kw' || (!isAPITemp)"
:play-btn="item.source == 'kw' || (!isAPITemp)"
:download-btn="item.source == 'kw' || (!isAPITemp)")
:listAdd-btn="item.source == 'kw' || !isAPITemp"
:play-btn="item.source != 'tx' && (item.source == 'kw' || !isAPITemp)"
:download-btn="item.source != 'tx' && (item.source == 'kw' || !isAPITemp)")
//- button.btn-info(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k'] || item._types.flac" @click.stop='openDownloadModal(index)') 下载
//- button.btn-secondary(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k']" @click.stop='testPlay(index)') 试听
//- button.btn-success(type='button' v-if="(item._types['128k'] || item._types['192k'] || item._types['320k']) && userInfo" @click.stop='showListModal(index)')
@@ -40,7 +40,7 @@ div(:class="$style.songList")
material-pagination(:count="total" :limit="limit" :page="page" @btn-click="handleTogglePage")
div(v-else :class="$style.noitem")
p(v-html="noItem")
material-flow-btn(:show="isShowEditBtn && (source == 'kw' || !isAPITemp)" :remove-btn="false" @btn-click="handleFlowBtnClick")
material-flow-btn(:show="isShowEditBtn && source != 'tx' && (source == 'kw' || !isAPITemp)" :remove-btn="false" @btn-click="handleFlowBtnClick")
</template>
<script>
@@ -140,7 +140,7 @@ export default {
this.clickIndex = index
return
}
this.emitEvent((this.source == 'kw' || !this.isAPITemp) ? 'testPlay' : 'search', index)
this.emitEvent((this.source != 'tx' && (this.source == 'kw' || !this.isAPITemp)) ? 'testPlay' : 'search', index)
this.clickTime = 0
this.clickIndex = -1
},

View File

@@ -110,13 +110,13 @@ export default {
display: inline-block;
border: none;
cursor: pointer;
padding: 5px 10px 7px;
padding: 0 10px;
font-size: 12px;
// color: @color-btn;
outline: none;
transition: background-color @transition-theme;
background-color: @color-tab-btn-background;
line-height: 28px;
}
&:hover {
// border-left-color: @color-theme_2-hover;

View File

@@ -1,7 +1,31 @@
<template lang="pug">
material-modal(:show="version.showModal" @close="handleClose")
main(:class="$style.main" v-if="version.newVersion")
h2 {{ version.isError ? (isUnknow ? '❓ 版本信息获取失败 ❓' : '🌟发现新版本🌟') : '🚀程序更新🚀'}}
material-modal(:show="version.showModal" @close="handleClose" v-if="version.newVersion")
main(:class="$style.main" v-if="version.isDownloaded")
h2 🚀程序更新🚀
div.scroll(:class="$style.info")
div(:class="$style.current")
h3 最新版本{{version.newVersion.version}}
h3 当前版本{{version.version}}
h3 版本变化
p(:class="$style.desc" v-html="version.newVersion.desc")
div(:class="[$style.history, $style.desc]" v-if="history.length")
h3 历史版本
div(:class="$style.item" v-for="ver in history")
h4 v{{ver.version}}
p(v-html="ver.desc")
div(:class="$style.footer")
div(:class="$style.desc")
p 新版本已下载完毕
p
| 你可以选择
strong 立即重启更新
| 或稍后
strong 关闭程序时
| 自动更新~
material-btn(:class="$style.btn" @click.onec="handleRestartClick") 立即重启更新
main(:class="$style.main" v-else-if="version.isError && !version.isUnknow && version.newVersion.version != version.version")
h2 版本更新出错
div.scroll(:class="$style.info")
div(:class="$style.current")
@@ -15,44 +39,97 @@ material-modal(:show="version.showModal" @close="handleClose")
h4 v{{ver.version}}
p(v-html="ver.desc")
div(:class="$style.footer" v-if="version.isError")
div(:class="$style.desc" v-if="!isUnknow")
div(:class="$style.footer")
div(:class="$style.desc")
p 发现有新版本啦但是自动更新功能出问题了
p
| 现在可以选择继续使用当前版本或
strong 去发布页下载新版本
|
| 可以去&nbsp;
strong.hover.underline(@click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" title="点击打开") 软件发布页
| &nbsp;&nbsp;
strong.hover.underline(@click="handleOpenUrl('https://www.lanzous.com/b906260/')" title="点击打开") 网盘
| (密码
strong.hover(@click="handleCopy('glqw')" title="点击复制") glqw
| )&nbsp;下载新版本
p
| 国内Windows/MAC用户推荐到
strong.hover.underline(@click="handleOpenUrl('https://www.lanzous.com/b906260/')") 网盘(点击打开)
| 下载密码
strong.hover(@click="handleCopy('glqw')" title="点击复制") glqw
div(:class="$style.btns")
material-btn(:class="$style.btn" @click.onec="handleIgnoreClick") 忽略该版本
material-btn(:class="$style.btn" @click.onec="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop#readme')") 去软件发布页
div(:class="$style.footer" v-else)
div(:class="$style.desc")
p 新版本已下载完毕
p
| 你可以选择
strong 立即重启更新
| 或稍后
strong 关闭程序时
| 自动更新~
material-btn(:class="$style.btn" @click.onec="handleRestartClick") 立即重启更新
strong 网盘
| 下载
main(:class="$style.main" v-else-if="version.isDownloading && version.isTimeOut && !version.isUnknow")
h2 新版本下载超时
div(:class="$style.desc")
p 你当前所在网络访问GitHub较慢导致新版本下载超时已经下了半个钟了😳建议手动更新版本
p
| 你可以去
material-btn(min @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" title="点击打开") 软件发布页
|
material-btn(min @click="handleOpenUrl('https://www.lanzous.com/b906260/')" title="点击打开") 网盘
| (密码
strong.hover(@click="handleCopy('glqw')" title="点击复制") glqw
| )下载新版本
p
| 国内Windows/MAC用户推荐到
strong 网盘
| 下载
p 当前下载进度{{progress}}
main(:class="$style.main" v-else-if="version.isUnknow")
h2 获取最新版本信息失败
div.scroll(:class="$style.info")
div(:class="$style.current")
h3 当前版本{{version.version}}
div(:class="$style.desc")
p 更新信息获取失败可能是无法访问Github导致的请手动检查更新
p
| 检查方法打开
material-btn(min @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" title="点击打开") 软件发布页
|
material-btn(min @click="handleOpenUrl('https://www.lanzous.com/b906260/')" title="点击打开") 网盘
| (密码
strong.hover(@click="handleCopy('glqw')" title="点击复制") glqw
| )查看它们的
strong 版本号
| 与当前版本({{version.version}})对比是否一样
p 若一样则不必理会该弹窗直接关闭即可否则请手动下载新版本更新
main(:class="$style.main" v-else)
h2 🌟发现新版本🌟
div.scroll(:class="$style.info")
div(:class="$style.current")
h3 最新版本{{version.newVersion.version}}
h3 当前版本{{version.version}}
h3 版本变化
p(:class="$style.desc" v-html="version.newVersion.desc")
div(:class="[$style.history, $style.desc]" v-if="history.length")
h3 历史版本
div(:class="$style.item" v-for="ver in history")
h4 v{{ver.version}}
p(v-html="ver.desc")
div(:class="$style.footer")
div(:class="$style.desc")
p 发现有新版本啦正在努力更新中若下载太慢可以手动更新哦~
p
| 手动更新可以去&nbsp;
strong.hover.underline(@click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" title="点击打开") 软件发布页
| &nbsp;&nbsp;
strong.hover.underline(@click="handleOpenUrl('https://www.lanzous.com/b906260/')" title="点击打开") 网盘
| (密码
strong.hover(@click="handleCopy('glqw')" title="点击复制") glqw
| )&nbsp;下载
p 国内Windows/MAC用户推荐到网盘下载
p 当前下载进度{{progress}}
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import { rendererSend } from '../../../common/icp'
import { checkVersion, openUrl, clipboardWriteText } from '../../utils'
import { rendererSend } from '../../../common/ipc'
import { checkVersion, openUrl, clipboardWriteText, sizeFormate } from '../../utils'
export default {
computed: {
...mapGetters(['version', 'setting']),
history() {
if (!this.version.newVersion) return []
if (!this.version.newVersion || !this.version.newVersion.history) return []
let arr = []
let currentVer = this.version.version
this.version.newVersion.history.forEach(ver => {
@@ -61,8 +138,10 @@ export default {
return arr
},
isUnknow() {
return this.version.newVersion.version == '0.0.0'
progress() {
return this.version.downloadProgress
? `${this.version.downloadProgress.percent.toFixed(2)}% - ${sizeFormate(this.version.downloadProgress.transferred)}/${sizeFormate(this.version.downloadProgress.total)} - ${sizeFormate(this.version.downloadProgress.bytesPerSecond)}/s`
: '初始化中...'
},
},
methods: {
@@ -72,11 +151,6 @@ export default {
isShow: false,
})
},
handleIgnoreClick(event) {
this.handleClose()
// event.target.disabled = true
this.setSetting(Object.assign({}, this.setting, { ignoreVersion: this.version.newVersion.version }))
},
handleOpenUrl(url) {
openUrl(url)
},
@@ -148,6 +222,10 @@ export default {
list-style: initial;
padding-inline-start: 30px;
}
p {
font-size: 14px;
line-height: 1.5;
}
}
.history {
@@ -175,22 +253,23 @@ export default {
.footer {
flex: 0 0 none;
.desc {
font-size: 12px;
padding-top: 10px;
font-size: 12px;
color: @color-theme;
line-height: 1.2;
line-height: 1.25;
p {
font-size: 12px;
color: @color-theme;
line-height: 1.2;
}
}
}
.btn {
margin-top: 10px;
display: block;
width: 100%;
}
.btns {
display: grid;
padding-top: 10px;
grid-template-columns: 1fr 1fr;
grid-gap: 0 10px;
}
each(@themes, {
:global(#container.@{value}) {

View File

@@ -10,9 +10,7 @@ function route(path, view, name, meta, props) {
path,
meta,
props,
component: (resovle) => import(
`../views/${view}.vue`
).then(resovle),
component: (resovle) => import(`../views/${view}.vue`).then(resovle),
}
}

View File

@@ -9,11 +9,7 @@ export default {
timeout: 20000,
}, (err, resp, body) => {
if (err) {
return ++retryNum > 3 ? resolve({
version: '0.0.0',
desc: '<h3>版本信息获取失败</h3><ul><li>更新信息获取失败可能是无法访问Github导致的请手动检查更新</li><li>检查方法:去设置-关于洛雪音乐打开<strong>开源地址</strong>或<strong>网盘地址</strong>查看<strong>版本号</strong>与当前版本对比是否最新</li></ul>',
history: [],
}) : this.dispatch('getVersionInfo', retryNum).then(ver => resolve(ver))
return ++retryNum > 3 ? reject() : this.dispatch('getVersionInfo', retryNum).then(ver => resolve(ver)).catch(err => reject(err))
}
resolve(body)
})

View File

@@ -53,7 +53,7 @@ const getExt = type => {
}
}
const checkList = (list, musicInfo, type) => list.some(s => s.musicInfo.songmid === musicInfo.songmid && s.type === type)
const checkList = (list, musicInfo, type, ext) => list.some(s => s.musicInfo.songmid === musicInfo.songmid && (s.type === type || s.ext === ext))
const getStartTask = (list, downloadStatus, maxDownloadNum) => {
let downloadCount = 0
@@ -88,7 +88,7 @@ const getUrl = (downloadInfo, isRefresh) => {
* 设置歌曲meta信息
* @param {*} downloadInfo
* @param {*} filePath
* @param {*} isEmbedPic
* @param {*} isEmbedPic // 是否嵌入图片
*/
const saveMeta = (downloadInfo, filePath, isEmbedPic) => {
if (downloadInfo.type === 'ape' || downloadInfo.type === 'flac') return
@@ -121,11 +121,24 @@ const downloadLyric = (downloadInfo, filePath) => {
})
}
const refreshUrl = function(commit, downloadInfo) {
commit('setStatusText', { downloadInfo, text: '链接失效,正在刷新链接' })
getUrl(downloadInfo, true).then(result => {
commit('updateUrl', { downloadInfo, url: result.url })
commit('setStatusText', { downloadInfo, text: '链接刷新成功' })
dls[downloadInfo.key].refreshUrl(result.url)
dls[downloadInfo.key].start()
}).catch(err => {
console.log(err)
this.dispatch('download/startTask')
})
}
// actions
const actions = {
createDownload({ state, rootState, commit }, { musicInfo, type }) {
if (checkList(state.list, musicInfo, type)) return
let ext = getExt(type)
if (checkList(state.list, musicInfo, type, ext)) return
const downloadInfo = {
isComplate: false,
status: state.downloadStatus.WAITING,
@@ -146,6 +159,11 @@ const actions = {
}
downloadInfo.filePath = path.join(rootState.setting.download.savePath, downloadInfo.fileName)
commit('addTask', downloadInfo)
try { // 删除同路径下的同名文件
fs.unlinkSync(downloadInfo.filePath)
} catch (err) {
if (err.code !== 'ENOENT') return commit('setStatusText', { downloadInfo, text: '文件删除失败' })
}
if (dls[downloadInfo.key]) {
dls[downloadInfo.key].stop().finally(() => {
delete dls[downloadInfo.key]
@@ -167,7 +185,7 @@ const actions = {
if (!downloadInfo) downloadInfo = result
// 开始任务
commit('onDownload', downloadInfo)
commit('onStart', downloadInfo)
commit('setStatusText', { downloadInfo, text: '任务初始化中' })
let msg = checkPath(rootState.setting.download.savePath)
if (msg) return commit('setStatusText', '检查下载目录出错: ' + msg)
@@ -178,12 +196,12 @@ const actions = {
fileName: downloadInfo.fileName,
method: 'get',
override: true,
onEnd() {
if (downloadInfo.progress.progress != '100.00') {
delete dls[downloadInfo.key]
return this.dispatch('download/startTask', downloadInfo)
}
commit('onEnd', downloadInfo)
onCompleted() {
// if (downloadInfo.progress.progress != '100.00') {
// delete dls[downloadInfo.key]
// return this.dispatch('download/startTask', downloadInfo)
// }
commit('onCompleted', downloadInfo)
_this.dispatch('download/startTask')
const filePath = path.join(options.path, options.fileName)
@@ -199,54 +217,40 @@ const actions = {
_this.dispatch('download/startTask')
return
}
let code
if (err.message.includes('Response status was')) {
code = err.message.replace(/Response status was (\d+)$/, '$1')
} else if (err.code === 'ETIMEDOUT' || err.code == 'ENOTFOUND') {
code = err.code
if (err.code == 'ENOTFOUND') {
refreshUrl.call(_this, commit, downloadInfo)
} else {
console.log('Download failed, Attempting Retry')
dls[downloadInfo.key].resume()
dls[downloadInfo.key].start()
commit('setStatusText', { downloadInfo, text: '正在重试' })
return
}
switch (code) {
case '401':
case '403':
case '410':
case 'ETIMEDOUT':
case 'ENOTFOUND':
commit('setStatusText', { downloadInfo, text: '链接失效,正在刷新链接' })
getUrl(downloadInfo, true).then(result => {
commit('updateUrl', { downloadInfo, url: result.url })
commit('setStatusText', { downloadInfo, text: '链接刷新成功' })
dls[downloadInfo.key].url = dls[downloadInfo.key].requestURL = result.url
dls[downloadInfo.key].__initProtocol(result.url)
dls[downloadInfo.key].resume()
}).catch(err => {
console.log(err)
_this.dispatch('download/startTask')
})
}
},
// onStateChanged(state) {
// console.log(state)
// },
onDownload() {
commit('onDownload', downloadInfo)
console.log('on download')
onFail(response) {
commit('onError', downloadInfo)
if (++tryNum[downloadInfo.key] > 2) {
_this.dispatch('download/startTask')
return
}
switch (response.statusCode) {
case 401:
case 403:
case 410:
refreshUrl.call(_this, commit, downloadInfo)
}
},
onStart() {
commit('onStart', downloadInfo)
console.log('on start')
},
onProgress(status) {
commit('onProgress', { downloadInfo, status })
console.log(status)
},
onPause() {
onStop() {
commit('pauseTask', downloadInfo)
_this.dispatch('download/startTask')
},
onResume() {
commit('resumeTask', downloadInfo)
},
}
commit('setStatusText', { downloadInfo, text: '获取URL中...' })
let p = options.url ? Promise.resolve() : getUrl(downloadInfo).then(result => {
@@ -315,9 +319,6 @@ const mutations = {
downloadInfo.status = state.downloadStatus.PAUSE
downloadInfo.statusText = '暂停下载'
},
resumeTask(state, downloadInfo) {
downloadInfo.statusText = '开始下载'
},
setStatusText(state, { downloadInfo, index, text }) { // 设置状态文本
if (downloadInfo) {
downloadInfo.statusText = text
@@ -352,7 +353,7 @@ const mutations = {
state.list[index].status = status
}
},
onEnd(state, downloadInfo) {
onCompleted(state, downloadInfo) {
downloadInfo.isComplate = true
downloadInfo.status = state.downloadStatus.COMPLETED
downloadInfo.statusText = '下载完成'
@@ -361,7 +362,7 @@ const mutations = {
downloadInfo.status = state.downloadStatus.ERROR
downloadInfo.statusText = '任务出错'
},
onDownload(state, downloadInfo) {
onStart(state, downloadInfo) {
downloadInfo.status = state.downloadStatus.RUN
downloadInfo.statusText = '正在下载'
},

View File

@@ -22,15 +22,18 @@ export default {
state.setting.list.scroll.locations[id] = location
},
setNewVersion(state, val) {
// val.history.forEach(ver => {
// ver.desc = ver.desc.replace(/\n/g, '<br>')
// })
// val.desc = val.desc.replace(/\n/g, '<br>')
state.version.newVersion = val
},
setVersionModalVisible(state, { isShow, isError }) {
setDownloadProgress(state, info) {
state.version.downloadProgress = info
},
setVersionModalVisible(state, { isShow, isError, isDownloaded, isTimeOut, isDownloading, isUnknow }) {
if (isShow !== undefined) state.version.showModal = isShow
if (isError !== undefined) state.version.isError = isError
if (isTimeOut !== undefined) state.version.isTimeOut = isTimeOut
if (isDownloading !== undefined) state.version.isDownloading = isDownloading
if (isDownloaded !== undefined) state.version.isDownloaded = isDownloaded
if (isUnknow !== undefined) state.version.isUnknow = isUnknow
},
setVolume(state, val) {
state.setting.player.volume = val

View File

@@ -62,6 +62,11 @@ export default {
newVersion: null,
showModal: false,
isError: false,
isTimeOut: false,
isUnknow: false,
isDownloaded: false,
isDownloading: false,
downloadProgress: null,
},
userInfo: null,
setting,

View File

@@ -0,0 +1,312 @@
import fs from 'fs'
import path from 'path'
import request from 'request'
import { EventEmitter } from 'events'
import { performance } from 'perf_hooks'
import { STATUS } from './util'
const defaultChunkInfo = {
path: null,
startByte: 0,
endByte: '',
}
const defaultRequestOptions = {
method: 'GET',
headers: {},
}
const defaultOptions = {
}
class Task extends EventEmitter {
/**
*
* @param {String} url download url
* @param {Object} chunkInfo
* @param {Object} options
*/
constructor(url, savePath, filename, options = {}) {
super()
this.resumeLastChunk = null
this.downloadUrl = url
this.chunkInfo = Object.assign({}, defaultChunkInfo, {
path: path.join(savePath, filename),
startByte: 0,
})
if (!this.chunkInfo.endByte) this.chunkInfo.endByte = ''
this.options = Object.assign({}, defaultOptions, options)
this.requestOptions = Object.assign({}, defaultRequestOptions, this.options.requestOptions || {})
if (!this.requestOptions.headers) this.requestOptions.headers = {}
this.progress = {
total: 0,
downloaded: 0,
speed: 0,
}
this.statsEstimate = {
time: 0,
bytes: 0,
prevBytes: 0,
}
this.status = STATUS.idle
}
__init() {
this.status = STATUS.init
const { path, startByte, endByte } = this.chunkInfo
if (startByte) this.requestOptions.headers.range = `bytes=${startByte}-${endByte}`
return new Promise((resolve, reject) => {
if (!path) return resolve()
fs.stat(path, (errStat, stats) => {
if (errStat) {
if (errStat.code !== 'ENOENT') {
this.__handleError(errStat)
reject(errStat)
return
}
} else if (stats.size >= 10) {
fs.open(path, 'r', (errOpen, fd) => {
if (errOpen) {
this.__handleError(errOpen)
reject(errOpen)
return
}
fs.read(fd, Buffer.alloc(10), 0, 10, stats.size - 10, (errRead, bytesRead, buffer) => {
if (errRead) {
this.__handleError(errRead)
reject(errRead)
return
}
fs.close(fd, errClose => {
if (errClose) {
this.__handleError(errClose)
reject(errClose)
return
}
// resume download
// console.log(buffer)
this.resumeLastChunk = buffer
this.progress.downloaded = stats.size
this.requestOptions.headers.range = `bytes=${stats.size - 10}-${endByte || ''}`
resolve()
})
})
})
return
}
resolve()
})
})
}
__httpFetch(url, options) {
// console.log(options)
this.request = request(url, options)
.on('response', response => {
if (response.statusCode !== 200 && response.statusCode !== 206) {
this.status = STATUS.failed
this.emit('fail', response)
this.__closeRequest()
this.__closeWriteStream()
return
}
this.emit('response', response)
try {
this.__initDownload(response)
} catch (error) {
return this.__handleError(error)
}
this.status = STATUS.running
response
.on('data', this.__handleWriteData.bind(this))
.on('error', err => this.__handleError(err))
.on('end', () => {
if (response.complete) {
this.__handleComplete()
} else {
this.__handleError(new Error('The connection was terminated while the message was still being sent'))
}
})
})
.on('error', err => this.__handleError(err))
.on('close', () => this.__closeWriteStream())
}
__initDownload(response) {
this.progress.total = parseInt(response.headers['content-length'] || 0)
let options = {}
let isResumable = this.options.forceResume || response.headers['accept-ranges'] !== 'none'
if (isResumable) {
options.flags = 'a'
if (this.progress.downloaded) this.progress.total -= 10
} else {
if (this.chunkInfo.startByte > 0) return this.__handleError(new Error('The resource cannot be resumed download.'))
}
this.progress.total += this.progress.downloaded
this.statsEstimate.prevBytes = this.progress.downloaded
if (!this.chunkInfo.path) return this.__handleError(new Error('Chunk save Path is not set.'))
this.ws = fs.createWriteStream(this.chunkInfo.path, options)
this.ws.on('finish', () => this.__closeWriteStream())
this.ws.on('error', async err => {
await this.__handleError(err)
fs.unlink(this.chunkInfo.path, () => this.__handleError(err))
})
}
__handleComplete() {
if (this.status == STATUS.error) return
this.__closeWriteStream().then(() => {
if (this.progress.downloaded == this.progress.total) {
this.status = STATUS.completed
this.emit('completed')
} else {
this.status = STATUS.stopped
this.emit('stop')
}
})
console.log('end')
}
__handleError(error) {
if (this.status == STATUS.error) return
this.status = STATUS.error
this.__closeRequest()
this.__closeWriteStream()
this.emit('error', error)
}
__closeWriteStream() {
return new Promise((resolve, reject) => {
if (!this.ws) return resolve()
console.log('close write stream')
this.ws.close(err => {
if (err) {
this.status = STATUS.error
this.emit('error', err)
reject(err)
return
}
this.ws = null
resolve()
})
})
}
__closeRequest() {
if (!this.request) return
console.log('close request')
this.request.abort()
this.request = null
}
__handleWriteData(chunk) {
if (this.resumeLastChunk) {
chunk = this.__handleDiffChunk(chunk)
if (!chunk) {
this.__handleError(new Error('Resume failed, response chunk does not match.'))
this.stop()
return
}
}
// console.log('data', chunk)
if (this.status == STATUS.stopped || this.ws == null) return console.log('cancel write')
this.__calculateProgress(chunk.length)
this.ws.write(chunk, err => {
if (!err) return
console.log(err)
this.__handleError(err)
this.stop()
})
}
__handleDiffChunk(chunk) {
// console.log('diff', chunk)
let resumeLastChunkLen = this.resumeLastChunk.length
let chunkLen = chunk.length
let isOk
if (chunkLen >= resumeLastChunkLen) {
isOk = chunk.slice(0, resumeLastChunkLen).toString('hex') === this.resumeLastChunk.toString('hex')
if (!isOk) return null
this.resumeLastChunk = null
return chunk.slice(resumeLastChunkLen)
} else {
isOk = chunk.slice(0, chunkLen).toString('hex') === this.resumeLastChunk.slice(0, chunkLen).toString('hex')
if (!isOk) return null
this.resumeLastChunk = this.resumeLastChunk.slice(chunkLen)
return chunk.slice(chunkLen)
}
}
__handleStop() {
return new Promise((resolve, reject) => {
if (this.request) {
this.request.abort()
this.request = null
}
if (this.ws) {
this.ws.close(err => {
if (err) {
reject(err)
this.emit('error', err)
return
}
this.ws = null
resolve()
})
} else {
resolve()
}
})
}
__calculateProgress(receivedBytes) {
const currentTime = performance.now()
const elaspsedTime = currentTime - this.statsEstimate.time
const progress = this.progress
progress.downloaded += receivedBytes
progress.progress = progress.total ? (progress.downloaded / progress.total) * 100 : -1
// emit the progress every second or if finished
if (progress.downloaded === progress.total || elaspsedTime > 1000) {
this.statsEstimate.time = currentTime
this.statsEstimate.bytes = progress.downloaded - this.statsEstimate.prevBytes
this.statsEstimate.prevBytes = progress.downloaded
this.emit('progress', {
total: progress.total,
downloaded: progress.downloaded,
progress: progress.progress,
speed: this.statsEstimate.bytes,
})
}
}
async start() {
this.status = STATUS.running
await this.__init()
this.__httpFetch(this.downloadUrl, this.requestOptions)
this.emit('start')
}
async stop() {
if (this.status === STATUS.stopped) return
this.status = STATUS.stopped
await this.__handleStop()
this.emit('stop')
}
refreshUrl(url) {
this.downloadUrl = url
}
}
export default Task

View File

@@ -1,6 +1,6 @@
import { DownloaderHelper } from 'node-downloader-helper'
import Downloader from './Downloader'
// import { pauseResumeTimer } from './util'
import { sizeFormate } from '../index'
import { sizeFormate, getProxyInfo } from '../index'
import { debugDownload } from '../env'
// these are the default options
@@ -21,38 +21,34 @@ export default ({
fileName,
method = 'get',
headers,
override,
forceResume,
// resumeTime = 5000,
onEnd = () => {},
onCompleted = () => {},
onError = () => {},
onStateChanged = () => {},
onDownload = () => {},
onPause = () => {},
onResume = () => {},
onFail = () => {},
onStart = () => {},
onStop = () => {},
onProgress = () => {},
resumeInfo,
} = {}) => {
const dl = new DownloaderHelper(url, path, {
fileName,
method,
headers,
override,
const dl = new Downloader(url, path, fileName, {
requestOptions: {
method,
headers,
proxy: getProxyInfo(),
},
forceResume,
})
dl.on('end', () => {
onEnd()
dl.on('completed', () => {
onCompleted()
debugDownload && console.log('Download Completed')
}).on('error', err => {
if (err.message === 'socket hang up') return
onError(err)
debugDownload && console.error('Something happend', err)
}).on('stateChanged', state => {
onStateChanged(state)
debugDownload && console.log('State: ', state)
}).on('download', () => {
onDownload()
}).on('start', () => {
onStart()
// pauseResumeTimer(dl, resumeTime)
}).on('progress', stats => {
const progress = stats.progress.toFixed(2)
@@ -68,24 +64,17 @@ export default ({
const total = sizeFormate(stats.total)
console.log(`${speed}/s - ${progress}% [${downloaded}/${total}]`)
}
}).on('pause', () => {
onPause()
}).on('stop', () => {
onStop()
debugDownload && console.log('paused')
}).on('resume', () => {
onResume()
debugDownload && console.log('resume')
}).on('fail', resp => {
onFail(resp)
debugDownload && console.log('fail')
})
debugDownload && console.log('Downloading: ', url)
if (resumeInfo) {
dl.__total = resumeInfo.totalFileSize // <--- Workaround
// dl.__filePath = resumeInfo.filePath // <--- Workaround
dl.__isResumable = true // <--- Workaround
dl.resume()
} else {
dl.start()
}
dl.start()
return dl
}

View File

@@ -1,24 +1,11 @@
import { DH_STATES } from 'node-downloader-helper'
export const pauseResumeTimer = (_dl, wait) => {
setTimeout(() => {
if (_dl.state === DH_STATES.FINISHED || _dl.state === DH_STATES.FAILED) {
return
}
_dl
.pause()
.then(() => console.log(`Paused for ${wait / 1000} seconds`))
.then(() =>
setTimeout(() => {
if (!_dl.isResumable()) {
console.warn(
"This URL doesn't support resume, it will start from the beginning"
)
}
return _dl.resume()
}, wait)
)
}, wait)
exports.STATUS = {
idle: 'IDLE',
init: 'INIT',
running: 'RUNNING',
paused: 'PAUSED',
stopped: 'STOPPED',
completed: 'COMPLETED',
error: 'ERROR',
failed: 'FAILED',
}

View File

@@ -1,9 +1,9 @@
import fs from 'fs'
import { shell, remote, clipboard } from 'electron'
import { shell, clipboard } from 'electron'
import path from 'path'
import os from 'os'
import crypto from 'crypto'
import { rendererSend } from '../../common/icp'
import { rendererSend, rendererInvoke } from '../../common/ipc'
/**
* 获取两个数之间的随机整数大于等于min小于max
@@ -42,22 +42,35 @@ export const b64DecodeUnicode = str => {
export const decodeName = str => str.replace(/&apos;/g, '\'')
export const scrollTo = (element, to, duration = 300, fn = function() {}) => {
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
}
/**
* 设置滚动条位置
* @param {*} element 要设置滚动的容器 dom
* @param {*} to 滚动的目标位置
* @param {*} duration 滚动完成时间 ms
* @param {*} fn 滚动完成后的回调
*/
export const scrollTo = (element, to, duration = 300, fn = () => {}) => {
if (!element) return
const start = element.scrollTop || element.scrollY || 0
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 fn()
const change = to - start
const increment = 10
if (!change) {
fn()
return
}
let currentTime = 0; let val
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
}
if (!change) return fn()
let currentTime = 0
let val
const animateScroll = () => {
currentTime += increment
val = parseInt(easeInOutQuad(currentTime, start, change, duration))
@@ -83,16 +96,16 @@ export const checkPath = path => fs.existsSync(path)
/**
* 在资源管理器中打开目录
* 选择路径
* @param {*} 选项
*/
export const openSelectDir = options => remote.dialog.showOpenDialog(remote.getCurrentWindow(), options)
export const selectDir = options => rendererInvoke('selectDir', options)
/**
* 在资源管理器中打开目录
* 打开保存对话框
* @param {*} 选项
*/
export const openSaveDir = options => remote.dialog.showSaveDialog(remote.getCurrentWindow(), options)
export const openSaveDir = options => rendererInvoke('showSaveDialog', options)
/**
* 在资源管理器中打开目录
@@ -164,7 +177,7 @@ export const isChildren = (parent, children) => {
* @param {*} setting
*/
export const updateSetting = setting => {
const defaultVersion = '1.0.13'
const defaultVersion = '1.0.14'
const defaultSetting = {
version: defaultVersion,
player: {
@@ -202,6 +215,7 @@ export const updateSetting = setting => {
},
odc: {
isAutoClearSearchInput: false,
isAutoClearSearchList: false,
},
search: {
searchSource: 'kw',
@@ -219,9 +233,8 @@ export const updateSetting = setting => {
windowSizeId: 1,
themeId: 0,
sourceId: 'kw',
apiSource: 'test',
apiSource: 'temp',
randomAnimate: true,
ignoreVersion: null,
}
const overwriteSetting = {
version: defaultVersion,
@@ -251,8 +264,8 @@ export const openUrl = url => {
* 设置标题
*/
let dom_title = document.getElementsByTagName('title')[0]
export const setTitle = title => {
dom_title.innerText = title || '洛雪音乐助手'
export const setTitle = (title = '洛雪音乐助手') => {
dom_title.innerText = title
}
@@ -361,19 +374,22 @@ export const asyncSetArray = (from, to, num = 100) => new Promise(resolve => {
/**
* 获取缓存大小
* @param {*} win
*/
export const getCacheSize = () => remote.getCurrentWindow().webContents.session.getCacheSize()
export const getCacheSize = () => rendererInvoke('getCacheSize')
/**
* 清除缓存
* @param {*} win
*/
export const clearCache = () => remote.getCurrentWindow().webContents.session.clearCache()
export const clearCache = () => rendererInvoke('clearCache')
/**
* 设置窗口大小
* @param {*} width
* @param {*} height
*/
export const setWindowSize = (width, height) => remote.getCurrentWindow().setBounds({ width, height })
export const setWindowSize = (width, height) => rendererSend('setWindowSize', { width, height })
export const getProxyInfo = () => window.globalObj.proxy.enable
? `http://${window.globalObj.proxy.username}:${window.globalObj.proxy.password}@${window.globalObj.proxy.host}:${window.globalObj.proxy.port};`
: undefined

View File

@@ -145,9 +145,7 @@ export default {
getList(sortId, tagId, page, tryNum = 0) {
if (this._requestObj_list) this._requestObj_list.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_list = httpFetch(
this.getListUrl(sortId, tagId, page)
)
this._requestObj_list = httpFetch(this.getListUrl(sortId, tagId, page))
return this._requestObj_list.promise.then(({ body }) => {
if (body.error_code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
return {

View File

@@ -4,7 +4,7 @@ import tx from './tx'
import wy from './wy'
import mg from './mg'
import bd from './bd'
export default {
const sources = {
sources: [
{
name: '酷我音乐',
@@ -38,3 +38,12 @@ export default {
mg,
bd,
}
export default {
...sources,
init() {
for (let source of sources.sources) {
let sm = sources[source.id]
sm && sm.init && sm.init()
}
},
}

View File

@@ -32,7 +32,7 @@ export default {
userid: 2626431536,
vip: 1,
},
}
},
)
requestObj.promise = requestObj.promise.then(({ body }) => {
if (body.error_code !== 0) return Promise.reject('图片获取失败')

View File

@@ -92,7 +92,7 @@ export default {
if (this._requestObj_list) this._requestObj_list.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_list = httpFetch(
this.getSongListUrl(sortId, tagId, page)
this.getSongListUrl(sortId, tagId, page),
)
return this._requestObj_list.promise.then(({ body }) => {
if (body.status !== 1) return this.getSongList(sortId, tagId, page, ++tryNum)
@@ -120,7 +120,7 @@ export default {
return_min: 6,
return_max: 15,
},
}
},
)
return this._requestObj_listRecommend.promise.then(({ body }) => {
if (body.status !== 1) return this.getSongListRecommend(++tryNum)
@@ -253,7 +253,7 @@ export default {
this.currentTagInfo.id = tagId
this.currentTagInfo.info = Object.assign({}, info)
return info
})
}),
)
if (!tagId && page === 1 && sortId === this.sortList[0].id) tasks.push(this.getSongListRecommend()) // 如果是所有类别,则顺便获取推荐列表
return Promise.all(tasks).then(([list, info, recommendList]) => {

View File

@@ -1,7 +1,7 @@
import { httpGet, cancelHttp } from '../../request'
import tempSearch from './tempSearch'
import musicSearch from './musicSearch'
import { formatSinger } from './util'
import { formatSinger, getToken } from './util'
import leaderboard from './leaderboard'
import lyric from './lyric'
import pic from './pic'
@@ -96,6 +96,10 @@ const kw = {
getPic(songInfo) {
return pic.getPic(songInfo)
},
init() {
getToken()
},
}
export default kw

View File

@@ -1,7 +1,6 @@
import { httpGet, cancelHttp } from '../../request'
import { formatPlayTime, decodeName } from '../../index'
import { formatSinger } from './util'
import { formatSinger, getToken, matchToken } from './util'
export default {
list: [
@@ -89,55 +88,65 @@ export default {
})
})
},
getData2(url) {
async getData2(url) {
if (this._cancelRequestObj2 != null) {
cancelHttp(this._cancelRequestObj2)
this._cancelPromiseCancelFn2(new Error('取消http请求'))
}
let token = window.kw_token.token
if (!token) token = await getToken()
return new Promise((resolve, reject) => {
this._cancelPromiseCancelFn2 = reject
this._cancelRequestObj2 = httpGet(url, (err, resp, body) => {
this._cancelRequestObj2 = httpGet(url, {
headers: {
Referer: 'http://www.kuwo.cn/',
csrf: token,
cookie: 'kw_token=' + token,
},
}, (err, resp, body) => {
this._cancelRequestObj2 = null
this._cancelPromiseCancelFn2 = null
if (err) {
console.log(err)
reject(err)
return reject(err)
}
window.kw_token.token = matchToken(resp.headers)
resolve(body)
})
})
},
filterData(rawList, rawList2) {
// console.log(rawList)
// console.log(rawList.length, rawList2.length)
return rawList.map((item, inedx) => {
let formats = item.formats.split('|')
let types = []
let _types = {}
if (formats.indexOf('MP3128')) {
if (formats.includes('MP3128')) {
types.push({ type: '128k', size: null })
_types['128k'] = {
size: null,
}
}
// if (formats.indexOf('MP3192')) {
// if (formats.includes('MP3192')) {
// types.push({ type: '192k', size: null })
// _types['192k'] = {
// size: null,
// }
// }
if (formats.indexOf('MP3H')) {
if (formats.includes('MP3H')) {
types.push({ type: '320k', size: null })
_types['320k'] = {
size: null,
}
}
// if (formats.indexOf('AL')) {
// if (formats.includes('AL')) {
// types.push({ type: 'ape', size: null })
// _types.ape = {
// size: null,
// }
// }
if (formats.indexOf('ALFLAC')) {
if (formats.includes('ALFLAC')) {
types.push({ type: 'flac', size: null })
_types.flac = {
size: null,

View File

@@ -3,7 +3,7 @@ import { httpFetch } from '../../request'
export default {
getPic({ songmid }) {
const requestObj = httpFetch(`http://artistpicserver.kuwo.cn/pic.web?corp=kuwo&type=rid_pic&pictype=500&size=500&rid=${songmid}`)
requestObj.promise = requestObj.promise.then(({ body }) => body)
requestObj.promise = requestObj.promise.then(({ body }) => /^http/.test(body) ? body : null)
return requestObj
},
}

View File

@@ -186,19 +186,19 @@ export default {
let formats = item.formats.split('|')
let types = []
let _types = {}
if (formats.indexOf('MP3128')) {
if (formats.includes('MP3128')) {
types.push({ type: '128k', size: null })
_types['128k'] = {
size: null,
}
}
if (formats.indexOf('MP3H')) {
if (formats.includes('MP3H')) {
types.push({ type: '320k', size: null })
_types['320k'] = {
size: null,
}
}
if (formats.indexOf('ALFLAC')) {
if (formats.includes('ALFLAC')) {
types.push({ type: 'flac', size: null })
_types.flac = {
size: null,

View File

@@ -1,20 +1,26 @@
import { httpFetch } from '../../request'
import { decodeName } from '../../index'
import { getToken, matchToken } from './util'
export default {
regExps: {
relWord: /RELWORD=(.+)/,
},
requestObj: null,
tempSearch(str) {
tempSearch(str, token) {
this.cancelTempSearch()
this.requestObj = httpFetch(`http://www.kuwo.cn/api/www/search/searchKey?key=${encodeURIComponent(str)}`, {
headers: {
Referer: 'http://www.kuwo.cn/',
csrf: token,
cookie: 'kw_token=' + token,
},
})
return this.requestObj.promise.then(({ statusCode, body }) => {
if (statusCode != 200 || body.code !== 200) return Promise.reject(new Error('请求失败'))
return this.requestObj.promise.then(({ statusCode, body, headers }) => {
if (statusCode != 200) return Promise.reject(new Error('请求失败'))
window.kw_token.token = matchToken(headers)
if (body.code !== 200) return Promise.reject(new Error('请求失败'))
return body
})
},
@@ -27,7 +33,9 @@ export default {
cancelTempSearch() {
if (this.requestObj && this.requestObj.cancelHttp) this.requestObj.cancelHttp()
},
search(str) {
return this.tempSearch(str).then(result => this.handleResult(result.data))
async search(str) {
let token = window.kw_token.token
if (!token) token = await getToken()
return this.tempSearch(str, token).then(result => this.handleResult(result.data))
},
}

View File

@@ -1,2 +1,34 @@
import { httpGet } from '../../request'
if (!window.kw_token) {
window.kw_token = {
token: null,
isGetingToken: false,
}
}
export const formatSinger = rawData => rawData.replace(/&/g, '、')
export const matchToken = headers => {
try {
return headers['set-cookie'][0].match(/kw_token=(\w+)/)[1]
} catch (err) {
return null
}
}
const wait = time => new Promise(resolve => setTimeout(() => resolve(), time))
export const getToken = () => new Promise((resolve, reject) => {
if (window.kw_token.isGetingToken) return wait(1000).then(() => getToken().then(token => resolve(token)))
if (window.kw_token.token) return resolve(window.kw_token.token)
window.kw_token.isGetingToken = true
httpGet('http://www.kuwo.cn', (err, resp) => {
window.kw_token.isGetingToken = false
if (err) return reject(err)
if (resp.statusCode != 200) return reject(new Error('获取失败'))
const token = window.kw_token.token = matchToken(resp.headers)
resolve(token)
})
})

View File

@@ -11,7 +11,7 @@ const api_test = {
family: 4,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 0 ? Promise.resolve({ type, url: encodeURI(body.data) }) : Promise.reject(new Error(requestMsg.fail))
return body.code === 0 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},

View File

@@ -109,7 +109,7 @@ export default {
if (this._requestObj_list) this._requestObj_list.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_list = httpFetch(
this.getListUrl(sortId, tagId, page)
this.getListUrl(sortId, tagId, page),
)
return this._requestObj_list.promise.then(({ body }) => {
if (body.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)

View File

@@ -4,6 +4,7 @@ import { debugRequest } from './env'
import { requestMsg } from './message'
import { bHh } from './music/options'
import { deflateRawSync } from 'zlib'
import { getProxyInfo } from './index'
// import fs from 'fs'
const defaultHeaders = {
@@ -219,10 +220,6 @@ export const http_jsonp = (url, options, callback) => {
})
}
const getProxyInfo = () => window.globalObj.proxy.enable
? `http://${window.globalObj.proxy.username}:${window.globalObj.proxy.password}@${window.globalObj.proxy.host}:${window.globalObj.proxy.port};`
: undefined
const regx = /(?:\d\w)+/g
const fetchData = (url, method, {

View File

@@ -1,6 +1,8 @@
<template lang="pug">
div(:class="$style.download")
//- transition
div(:class="$style.header")
material-tab(:class="$style.tab" :list="tabs" align="left" item-key="id" item-name="name" v-model="tabId")
div(:class="$style.content" v-if="list.length")
div(:class="$style.thead")
table
@@ -17,7 +19,7 @@ div(:class="$style.download")
div.scroll(v-if="list.length" :class="$style.tbody")
table
tbody
tr(v-for='(item, index) in list' :key='item.key' @click="handleDoubleClick(index)" :class="isPlayList && playIndex === index ? $style.active : ''")
tr(v-for='(item, index) in showList' :key='item.key' @click="handleDoubleClick(index)" :class="playListIndex === index ? $style.active : ''")
td.nobreak.center(style="width: 37px;" @click.stop)
material-checkbox(:id="index.toString()" v-model="selectdData" :value="item")
td.break(style="width: 28%;") {{item.musicInfo.name}} - {{item.musicInfo.singer}}
@@ -46,6 +48,29 @@ export default {
isIndeterminate: false,
isShowEditBtn: false,
isShowDownloadMultiple: false,
tabs: [
{
name: '全部任务',
id: 'all',
},
{
name: '正在下载',
id: 'runing',
},
{
name: '已暂停',
id: 'paused',
},
{
name: '出错',
id: 'error',
},
{
name: '下载完成',
id: 'finished',
},
],
tabId: 'all',
}
},
computed: {
@@ -54,6 +79,27 @@ export default {
isPlayList() {
return this.listId == 'download'
},
playListIndex() {
if (this.listId != 'download' || !this.list.length) return
let info = this.list[this.playIndex]
if (!info) return -1
let key = info.key
return this.showList.findIndex(i => i.key == key)
},
showList() {
switch (this.tabId) {
case 'runing':
return this.list.filter(i => i.status == this.downloadStatus.RUN || i.status == this.downloadStatus.WAITING)
case 'paused':
return this.list.filter(i => i.status == this.downloadStatus.PAUSE)
case 'error':
return this.list.filter(i => i.status == this.downloadStatus.ERROR)
case 'finished':
return this.list.filter(i => i.status == this.downloadStatus.COMPLETED)
default:
return this.list
}
},
},
watch: {
selectdData(n) {
@@ -78,14 +124,14 @@ export default {
handlePauseTask(index) {
let info = this.list[index]
let dl = this.dls[info.key]
dl ? dl.pause() : this.pauseTask(info)
dl ? dl.stop() : this.pauseTask(info)
console.log('pause')
},
handleStartTask(index) {
console.log('start')
let info = this.list[index]
let dl = this.dls[info.key]
dl ? dl.resume() : this.startTask(info)
dl ? dl.start() : this.startTask(info)
},
handleDoubleClick(index) {
if (
@@ -101,6 +147,8 @@ export default {
this.clickIndex = -1
},
handleClick(index) {
const key = this.showList[index].key
index = this.list.findIndex(i => i.key === key)
let info = this.list[index]
if (info.isComplate) {
this.handlePlay(index)
@@ -112,29 +160,32 @@ export default {
},
handlePlay(index) {
if (!checkPath(this.list[index].filePath)) return
this.setList({ list: this.list, listId: 'download', index })
let path = this.list[index].filePath
this.setList({ list: this.list, listId: 'download', index: this.list.findIndex(i => i.filePath === path) })
},
handleListBtnClick(info) {
const key = this.showList[info.index].key
let index = this.list.findIndex(i => i.key === key)
switch (info.action) {
case 'play':
this.handlePlay(info.index)
this.handlePlay(index)
break
case 'start':
this.handleStartTask(info.index)
this.handleStartTask(index)
break
case 'pause':
this.handlePauseTask(info.index)
this.handlePauseTask(index)
break
case 'remove':
this.removeTask(info.index)
this.removeTask(index)
break
case 'file':
this.handleOpenFolder(info.index)
this.handleOpenFolder(index)
break
}
},
handleSelectAllData(isSelect) {
this.selectdData = isSelect ? [...this.list] : []
this.selectdData = isSelect ? [...this.showList] : []
},
resetSelect() {
this.isSelectAll = false
@@ -188,16 +239,23 @@ export default {
.download {
overflow: hidden;
height: 100%;
display: flex;
flex-flow: column nowrap;
// .noItem {
// }
}
.header {
flex: none;
}
.content {
height: 100%;
font-size: 14px;
display: flex;
flex-flow: column nowrap;
flex: auto;
overflow: hidden;
// table {
// position: relative;
// thead {

View File

@@ -95,7 +95,7 @@ export default {
this.listAdd({ id: 'default', musicInfo: targetSong })
}
let targetIndex = this.defaultList.list.findIndex(
s => s.songmid === targetSong.songmid
s => s.songmid === targetSong.songmid,
)
if (targetIndex > -1) {
this.setList({

View File

@@ -18,7 +18,7 @@
table
tbody
tr(v-for='(item, index) in list' :key='item.songmid' :id="'mid_' + item.songmid"
@click="handleDoubleClick(index)" :class="[isPlayList && playIndex === index ? $style.active : '', (isAPITemp && item.source != 'kw') ? $style.disabled : '']")
@click="handleDoubleClick(index)" :class="[isPlayList && playIndex === index ? $style.active : '', (isAPITemp && item.source != 'kw') || item.source == 'tx' ? $style.disabled : '']")
td.nobreak.center(style="width: 37px;" @click.stop)
material-checkbox(:id="index.toString()" v-model="selectdData" :value="item")
td.break(style="width: 25%;")
@@ -243,7 +243,7 @@ export default {
this.clickIndex = -1
},
testPlay(index) {
if (this.isAPITemp && this.list[index].source != 'kw') return
if (this.list[index].source == 'tx' || (this.isAPITemp && this.list[index].source != 'kw')) return
this.setPlayList({ list: this.list, listId: this.listId, index })
},
handleRemove(index) {
@@ -253,7 +253,7 @@ export default {
switch (info.action) {
case 'download': {
const minfo = this.list[info.index]
if (this.isAPITemp && minfo.source != 'kw') return
if ((this.isAPITemp && minfo.source != 'kw') || minfo.source == 'tx') return
this.musicInfo = minfo
this.$nextTick(() => {
this.isShowDownload = true
@@ -286,7 +286,7 @@ export default {
this.selectdData = []
},
handleAddDownloadMultiple(type) {
const list = this.setting.apiSource == 'temp' ? this.selectdData.filter(s => s.source == 'kw') : [...this.selectdData]
const list = this.setting.apiSource == 'temp' ? this.selectdData.filter(s => s.source == 'kw') : this.selectdData.filter(s => s.source != 'tx')
this.createDownloadMultiple({ list, type })
this.resetSelect()
this.isShowDownloadMultiple = false

View File

@@ -26,12 +26,13 @@
| {{item.name}}
span.badge.badge-info(v-if="item._types['320k']") 高品质
span.badge.badge-success(v-if="item._types.ape || item._types.flac") 无损
span(:class="$style.labelSource" v-if="searchSourceId == 'all'") {{item.source}}
td.break(style="width: 20%;") {{item.singer}}
td.break(style="width: 25%;") {{item.albumName}}
td(style="width: 15%; padding-left: 0; padding-right: 0;")
material-list-buttons(:index="index" :remove-btn="false" :class="$style.listBtn"
:play-btn="item.source == 'kw' || !isAPITemp"
:download-btn="item.source == 'kw' || !isAPITemp"
:play-btn="item.source != 'tx' && (item.source == 'kw' || !isAPITemp)"
:download-btn="item.source != 'tx' && (item.source == 'kw' || !isAPITemp)"
@btn-click="handleListBtnClick")
td(style="width: 10%;") {{item.interval || '--/--'}}
div(:class="$style.pagination")
@@ -40,7 +41,7 @@
p 搜我所想~~😉
material-download-modal(:show="isShowDownload" :musicInfo="musicInfo" @select="handleAddDownload" @close="isShowDownload = false")
material-download-multiple-modal(:show="isShowDownloadMultiple" :list="selectdData" @select="handleAddDownloadMultiple" @close="isShowDownloadMultiple = false")
material-flow-btn(:show="isShowEditBtn && (searchSourceId == 'kw' || searchSourceId == 'all' || !isAPITemp)" :remove-btn="false" @btn-click="handleFlowBtnClick")
material-flow-btn(:show="isShowEditBtn && searchSourceId != 'tx' && (searchSourceId == 'kw' || searchSourceId == 'all' || !isAPITemp)" :remove-btn="false" @btn-click="handleFlowBtnClick")
material-list-add-modal(:show="isShowListAdd" :musicInfo="musicInfo" @close="isShowListAdd = false")
material-list-add-multiple-modal(:show="isShowListAddMultiple" :musicList="selectdData" @close="handleListAddModalClose")
</template>
@@ -189,12 +190,12 @@ export default {
targetSong = this.selectdData[0]
this.listAddMultiple({ id: 'default', list: this.filterList(this.selectdData) })
} else {
if (this.isAPITemp && this.listInfo.list[index].source != 'kw') return
if (this.listInfo.list[index].source == 'tx' || (this.isAPITemp && this.listInfo.list[index].source != 'kw')) return
targetSong = this.listInfo.list[index]
this.listAdd({ id: 'default', musicInfo: targetSong })
}
let targetIndex = this.defaultList.list.findIndex(
s => s.songmid === targetSong.songmid
s => s.songmid === targetSong.songmid,
)
if (targetIndex > -1) {
this.setList({
@@ -238,7 +239,7 @@ export default {
}
},
filterList(list) {
return this.setting.apiSource == 'temp' ? list.filter(s => s.source == 'kw') : [...list]
return this.setting.apiSource == 'temp' ? list.filter(s => s.source == 'kw') : list.filter(s => s.source != 'tx')
},
handleListAddModalClose(isSelect) {
if (isSelect) this.resetSelect()
@@ -291,6 +292,13 @@ export default {
.listBtn {
min-height: 24px;
}
.labelSource {
color: @color-theme;
padding: 5px;
font-size: .8em;
line-height: 1;
opacity: .75;
}
.pagination {
text-align: center;
padding: 15px 0;

View File

@@ -78,7 +78,7 @@ div.scroll(:class="$style.setting")
material-checkbox(id="setting_download_isDownloadLrc" v-model="current_setting.download.isDownloadLrc" label="是否启用")
dt 网络设置
dd
h3 代理设置歌曲下载暂不支持代理
h3 代理设置
div
p
material-checkbox(id="setting_network_proxy_enable" v-model="current_setting.network.proxy.enable" @change="handleProxyChange('enable')" label="是否启用")
@@ -93,6 +93,10 @@ div.scroll(:class="$style.setting")
h3 离开搜索界面时清空搜索框
div
material-checkbox(id="setting_odc_isAutoClearSearchInput" v-model="current_setting.odc.isAutoClearSearchInput" label="是否启用")
dd
h3 离开搜索界面时清空搜索列表
div
material-checkbox(id="setting_odc_isAutoClearSearchList" v-model="current_setting.odc.isAutoClearSearchList" label="是否启用")
dt 备份与恢复
dd
h3 部分数据
@@ -108,7 +112,7 @@ div.scroll(:class="$style.setting")
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleExportAllData") 导出
dt 其他
dd
h3 缓存大小清理缓存后图片等资源将需要重新下载
h3 缓存大小清理缓存后图片等资源将需要重新下载不建议清理软件会根据磁盘空间动态管理缓存大小
div
p
| 软件已使用缓存大小
@@ -120,10 +124,13 @@ div.scroll(:class="$style.setting")
p.small
| 最新版本{{version.newVersion ? version.newVersion.version : '未知'}}
p.small 当前版本{{version.version}}
p.small(v-if="version.newVersion")
p.small(v-if="this.version.downloadProgress" style="line-height: 1.5;")
| 发现新版本并在努力下载中请稍后...
br
| 下载进度{{downloadProgress}}
p(v-if="version.newVersion")
span(v-if="isLatestVer") 软件已是最新尽情地体验吧~🥂
material-btn(v-else-if="setting.ignoreVersion || version.isError" :class="[$style.btn, $style.gapLeft]" min @click="showUpdateModal") 打开更新窗口 🚀
span(v-else) 发现新版本并在努力下载中请稍等...
material-btn(v-else :class="[$style.btn, $style.gapLeft]" min @click="showUpdateModal") 打开更新窗口 🚀
p.small(v-else) 检查更新中...
dt 关于洛雪音乐
dd
@@ -137,7 +144,7 @@ div.scroll(:class="$style.setting")
span.hover(title="点击复制" @click="clipboardWriteText('glqw')") glqw
p.small
| 软件的常见问题可转至
span.hover.underline(title="点击打开" @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop#常见问题')") 常见问题
span.hover.underline(title="点击打开" @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md')") 常见问题
//- p.small
| 怀念曾经的
strong @messoer
@@ -174,7 +181,7 @@ div.scroll(:class="$style.setting")
import { mapGetters, mapMutations } from 'vuex'
import {
openDirInExplorer,
openSelectDir,
selectDir,
openSaveDir,
updateSetting,
openUrl,
@@ -184,7 +191,7 @@ import {
sizeFormate,
setWindowSize,
} from '../utils'
import { rendererSend } from '../../common/icp'
import { rendererSend } from '../../common/ipc'
import fs from 'fs'
export default {
@@ -198,6 +205,11 @@ export default {
isShowRebootBtn() {
return this.current_setting.windowSizeId != window.currentWindowSizeId
},
downloadProgress() {
return this.version.downloadProgress
? `${this.version.downloadProgress.percent.toFixed(2)}% - ${sizeFormate(this.version.downloadProgress.transferred)}/${sizeFormate(this.version.downloadProgress.total)} - ${sizeFormate(this.version.downloadProgress.bytesPerSecond)}/s`
: '更新初始化中...'
},
},
data() {
return {
@@ -233,6 +245,7 @@ export default {
},
odc: {
isAutoClearSearchInput: false,
isAutoClearSearchList: false,
},
windowSizeId: 1,
themeId: 0,
@@ -259,25 +272,14 @@ export default {
},
],
apiSources: [
// {
// id: 'messoer',
// // label: '由 messoer 提供的接口(推荐,软件的所有功能都可用)',
// label: '由 messoer 提供的接口(该接口已关闭)',
// disabled: true,
// },
// {
// id: 'internal',
// label: '内置接口只能试听或下载128k音质该接口支持软件的所有功能',
// disabled: false,
// },
{
id: 'test',
label: '测试接口(几乎软件的所有功能都可用,该接口访问速度略慢',
label: '测试接口(几乎软件的所有功能都可用)',
disabled: false,
},
{
id: 'temp',
label: '临时接口(软件的某些功能不可用,该接口比测试接口快一些,建议测试接口不可用再使用本接口)',
label: '临时接口(软件的某些功能不可用,建议测试接口不可用再使用本接口)',
disabled: false,
},
],
@@ -328,7 +330,7 @@ export default {
this.getCacheSize()
},
handleChangeSavePath() {
openSelectDir({
selectDir({
title: '选择歌曲保存路径',
defaultPath: this.current_setting.download.savePath,
properties: ['openDirectory'],
@@ -357,7 +359,7 @@ export default {
type: 'setting',
data: this.setting,
}
fs.writeFile(path, JSON.stringify(data, null, 2), 'utf8', err => {
fs.writeFile(path, JSON.stringify(data), 'utf8', err => {
console.log(err)
})
},
@@ -387,7 +389,7 @@ export default {
this.loveList,
],
}
fs.writeFile(path, JSON.stringify(data, null, 2), 'utf8', err => {
fs.writeFile(path, JSON.stringify(data), 'utf8', err => {
console.log(err)
})
},
@@ -416,12 +418,12 @@ export default {
this.loveList,
],
}
fs.writeFile(path, JSON.stringify(allData, null, 2), 'utf8', err => {
fs.writeFile(path, JSON.stringify(allData), 'utf8', err => {
console.log(err)
})
},
handleImportAllData() {
openSelectDir({
selectDir({
title: '选择备份文件',
properties: ['openFile'],
filters: [
@@ -443,7 +445,7 @@ export default {
})
},
handleImportSetting() {
openSelectDir({
selectDir({
title: '选择配置文件',
properties: ['openFile'],
filters: [
@@ -465,7 +467,7 @@ export default {
})
},
handleImportPlayList() {
openSelectDir({
selectDir({
title: '选择列表文件',
properties: ['openFile'],
filters: [

View File

@@ -183,14 +183,15 @@ export default {
let targetSong
if (index == null) {
targetSong = this.selectdData[0]
this.listAddMultiple({ id: 'default', list: this.selectdData })
this.listAddMultiple({ id: 'default', list: this.filterList(this.selectdData) })
this.resetSelect()
} else {
targetSong = this.listDetail.list[index]
if (targetSong.source == 'tx' || (this.isAPITemp && targetSong.source != 'kw')) return
this.listAdd({ id: 'default', musicInfo: targetSong })
}
let targetIndex = this.defaultList.list.findIndex(
s => s.songmid === targetSong.songmid
s => s.songmid === targetSong.songmid,
)
if (targetIndex > -1) {
this.setList({
@@ -234,7 +235,7 @@ export default {
case 'wy':
type = '128k'
}
this.createDownloadMultiple({ list: [...this.selectdData], type })
this.createDownloadMultiple({ list: this.filterList(this.selectdData), type })
this.resetSelect()
this.isShowDownloadMultiple = false
},
@@ -345,6 +346,9 @@ export default {
}
this.importSongListText = text.replace(regx, '$1')
},
filterList(list) {
return this.setting.apiSource == 'temp' ? list.filter(s => s.source == 'kw') : list.filter(s => s.source != 'tx')
},
/* addSongListDetail() {
// this.detailLoading = true
// this.getListDetailAll(this.selectListInfo.id).then(() => {