Compare commits

...

174 Commits

Author SHA1 Message Date
lyswhut
74f8e9544b 发布0.8.2版本 2019-10-20 18:21:43 +08:00
lyswhut
0a5e7c44ff 容旧版酷我源搜索列表过滤128k音质的bug 2019-10-20 18:20:58 +08:00
lyswhut
5c32160a58 发布0.8.1版本 2019-10-20 10:11:36 +08:00
lyswhut
2fc81c03ec 修复酷我源搜索歌曲结果处理问题 2019-10-20 02:48:28 +08:00
lyswhut
cc485df42f 移除192k试听 2019-10-20 00:32:36 +08:00
lyswhut
d575dd24ca 发布0.8.0版本 2019-10-19 12:08:53 +08:00
lyswhut
e47586cea3 更新请求头 2019-10-19 12:04:15 +08:00
lyswhut
1ce63c7550 修复酷狗歌曲无封面时报错的问题 2019-10-19 11:48:19 +08:00
lyswhut
36d69da3b7 缓存歌单详情列表数据 2019-10-19 11:47:15 +08:00
lyswhut
529be08420 优化音量条交互视觉效果 2019-10-18 21:47:10 +08:00
lyswhut
ffe11c7015 更新与分离常见问题 2019-10-18 18:04:28 +08:00
lyswhut
76b796bde7 修复歌单地址无效时无限重复请求的bug 2019-10-18 17:46:33 +08:00
lyswhut
f81715f07f 更新歌单导入描述 2019-10-18 13:32:01 +08:00
lyswhut
ec3f667b75 修复音量调整Bug,新增拖动调整音量 2019-10-18 12:03:01 +08:00
lyswhut
87f0b4fd17 修复传参问题 2019-10-17 22:41:24 +08:00
lyswhut
c7e95c5166 新增各平台通过输入歌单链接或歌单ID打开歌单详情列表 2019-10-17 17:01:49 +08:00
lyswhut
6ea0837cce 新增腾讯、网易云歌单链接打开 2019-10-17 14:05:29 +08:00
lyswhut
59627e048f 分离搜索框业务逻辑,优化搜索框搜索体验 2019-10-17 10:58:09 +08:00
lyswhut
45fea5726b 修复歌曲音质不存在时仍然播放或下载的Bug 2019-10-16 21:26:59 +08:00
lyswhut
e539f29f6d 修复音质过滤导致的歌单无法显示问题 2019-10-16 21:00:46 +08:00
lyswhut
1d2810e98f 新增网易云源歌单 2019-10-16 19:54:13 +08:00
lyswhut
1cbafc9750 新增网易云源歌曲搜索 2019-10-16 10:38:42 +08:00
lyswhut
e675f7ac06 更新描述 2019-10-16 00:59:19 +08:00
lyswhut
9dc9d3b1b7 修复网易云128k直接试听 2019-10-16 00:40:36 +08:00
lyswhut
a104ce688e 修复默认列表没有创建时无法显示收藏列表的Bug 2019-10-13 09:20:44 +08:00
lyswhut
2dd1ceb0b9 更新依赖到最新 2019-10-11 22:52:27 +08:00
lyswhut
9379328d96 修复QQ源歌单无法翻页Bug 2019-10-10 18:43:04 +08:00
lyswhut
d8969c327c 改变请求头 2019-10-10 13:24:14 +08:00
lyswhut
c3624fb7d6 Create LICENSE 2019-10-08 10:20:46 +08:00
lyswhut
b2de230100 remove license file 2019-10-08 10:19:46 +08:00
lyswhut
9d0bf50931 发布0.7.0版本 2019-10-07 22:22:32 +08:00
lyswhut
843e453b21 新增我的音乐列表歌曲源显示 2019-10-07 22:17:13 +08:00
lyswhut
f7546b0090 新增咪咕源搜索、歌单、排行榜 2019-10-07 21:51:16 +08:00
lyswhut
ed12aac7d8 新增QQ音乐源搜索及其128k试听 2019-10-07 13:07:16 +08:00
lyswhut
15f4053241 修复试听音乐时试听列表id传递错误的Bug 2019-10-07 01:35:20 +08:00
lyswhut
7a334f0099 新增缓存清理功能 2019-10-06 02:14:05 +08:00
lyswhut
4a510393ab 更新依赖到最新 2019-10-05 19:35:44 +08:00
lyswhut
52b0638258 修复strMediaMid获取错误 2019-10-05 16:10:40 +08:00
lyswhut
b198b1945d 尝试优化选我的音乐列表内容很多时多选的卡顿问题 2019-10-04 18:04:01 +08:00
lyswhut
10784e95bb 优化选择框动画效果 2019-10-04 10:13:26 +08:00
lyswhut
f4c65516d0 修复对多列表滚动条位置恢复的支持 2019-10-03 18:42:26 +08:00
lyswhut
6498305ec8 把我喜欢列表改名成我的收藏 2019-10-03 18:14:59 +08:00
lyswhut
7a91e6efef 完成我喜欢播放列表 2019-10-03 18:11:21 +08:00
lyswhut
4204b62f8f 重构列表功能,使其支持多列表,新增我喜欢列表 2019-10-03 02:00:31 +08:00
lyswhut
70b718d372 发布0.6.2版本 2019-10-01 16:36:44 +08:00
lyswhut
2b5d6f3592 移除static全局路径 2019-10-01 13:04:02 +08:00
lyswhut
8e48d5ba68 解除酷狗封面、歌词对接口服务器的依赖 2019-10-01 12:06:18 +08:00
lyswhut
3cec62d1a9 Merge branch 'openKg' into dev 2019-10-01 10:40:19 +08:00
lyswhut
b1650bcbee Merge branch 'master' into dev 2019-09-30 20:21:57 +08:00
lyswhut
492e3d8965 恢复酷狗其他音质播放 2019-09-30 20:20:28 +08:00
lyswhut
6d7be78b70 开放腾讯源128k音源直接播放 2019-09-30 15:24:00 +08:00
lyswhut
bac9439f8e 新增QQ音乐源搜索 2019-09-30 14:49:27 +08:00
lyswhut
5b612bfa0f 修复百度源搜索结果为空时的接口处理Bug 2019-09-30 13:14:55 +08:00
lyswhut
1f77b3dd4b 新增QQ音乐源歌单 2019-09-30 13:04:35 +08:00
lyswhut
b0d7a53d15 Update issue templates 2019-09-29 17:09:36 +08:00
lyswhut
c029eccdd3 修复当试听列表为空时,无法切到其他界面的Bug 2019-09-29 15:48:47 +08:00
lyswhut
7236f8929c 修正火影皮肤名字 2019-09-28 17:17:35 +08:00
lyswhut
aa114f86e3 发布0.6.1版本 2019-09-28 10:45:26 +08:00
lyswhut
95db349973 移除酷我音源 ape 音质 2019-09-27 11:51:20 +08:00
lyswhut
2acef590b4 更新常见问题内容 2019-09-27 10:55:56 +08:00
lyswhut
29855b2b6e 修复请求超时的逻辑处理Bug 2019-09-27 10:53:24 +08:00
lyswhut
ea10dd68a3 更新常见问题 2019-09-26 13:34:07 +08:00
lyswhut
e1942c66ac 更新依赖到最新 2019-09-26 00:07:13 +08:00
lyswhut
6bd18e8078 优化更新弹窗 2019-09-26 00:06:02 +08:00
lyswhut
f3dd306f54 优化播放组件渲染性能 2019-09-25 13:05:30 +08:00
lyswhut
3cf39ea48b 优化列表展开动画 2019-09-25 09:02:12 +08:00
lyswhut
d57f212c62 分离背景位置控制 2019-09-25 00:57:12 +08:00
lyswhut
b273c52124 移除192k音质 2019-09-24 13:34:14 +08:00
lyswhut
ce0a9afc33 调整皮肤tablist颜色 2019-09-24 13:27:05 +08:00
lyswhut
8437fe1716 新增 《海贼王》皮肤 2019-09-23 22:12:14 +08:00
lyswhut
f63f645696 新增DNS缓存功能 2019-09-23 13:24:32 +08:00
lyswhut
f7267806df 新增列表滚动条位置恢复 2019-09-22 11:33:37 +08:00
lyswhut
bebf1906a2 修正fetch单词的拼写错误 2019-09-22 10:08:37 +08:00
lyswhut
58e61e0bda 发布0.6.0版本 2019-09-21 21:09:15 +08:00
lyswhut
42aa62cff0 调整失败更新提示 2019-09-21 21:07:36 +08:00
lyswhut
bcf7b3fccb 修改更新失败提示 2019-09-21 21:04:45 +08:00
lyswhut
ff09378c6d 去重id 2019-09-21 20:53:03 +08:00
lyswhut
99b0595e50 新增搜索页面接口限制 2019-09-21 20:34:40 +08:00
lyswhut
5321469c1d 完成聚合搜索功能 2019-09-21 20:05:56 +08:00
lyswhut
ef9eb83a75 完善聚合搜索逻辑 2019-09-21 13:41:19 +08:00
lyswhut
c12bbc7f0f 重构搜索页面逻辑 2019-09-20 22:09:18 +08:00
lyswhut
a16c4c3657 更新依赖到最新 2019-09-19 22:30:15 +08:00
lyswhut
c8240448c6 内置百度源歌曲图片、歌词获取 2019-09-17 21:36:46 +08:00
lyswhut
d19569131c 将网络代理密码框设置为password类型 2019-09-16 21:50:41 +08:00
lyswhut
37eec4c971 更换接口地址 2019-09-16 14:08:29 +08:00
lyswhut
65fe679ee2 修复切换歌单列表详情时的UI Bug 2019-09-16 13:31:06 +08:00
lyswhut
a16b825c3b 修复代理bug 2019-09-16 00:24:30 +08:00
lyswhut
b9aedc9165 新增代理功能 2019-09-16 00:00:07 +08:00
lyswhut
8bdbbc562c 修复试听列表没有歌曲时会显示列表加载中的Bug 2019-09-14 11:00:42 +08:00
lyswhut
696f6c096c 优化从《梦里嫦娥》皮肤切换到其他皮肤时侧栏动画的切换效果 2019-09-13 14:51:28 +08:00
lyswhut
d0ef6aef1c 发布0.5.5版本 2019-09-13 14:07:39 +08:00
lyswhut
7204b51439 新增:《月里嫦娥》皮肤 2019-09-13 14:04:52 +08:00
lyswhut
6075a9d22f 更新常见问题 2019-09-12 16:24:28 +08:00
lyswhut
b15cfa1892 新增复制播放标题的功能 2019-09-12 01:35:19 +08:00
lyswhut
955375f423 添加mac退出快捷键 2019-09-11 21:43:30 +08:00
lyswhut
2aa13a499a 修复mac不能复制粘贴的问题 2019-09-11 20:50:34 +08:00
lyswhut
fb0e61f3b0 修复等待事件未清空的bug 2019-09-10 21:49:09 +08:00
lyswhut
16149fdd38 修复:文件名包含特殊符号时导致下载失败的Bug 2019-09-10 13:00:55 +08:00
lyswhut
a432080141 修复 MAC 系统下软件关闭时再次从 dock 打开时报错的Bug 2019-09-10 09:03:57 +08:00
lyswhut
73072daa46 尝试修复Mac系统点击关闭时再打开的BUG 2019-09-10 02:03:09 +08:00
lyswhut
2f57345bd2 更新更新错误时的弹窗下载地址 2019-09-09 09:49:27 +08:00
lyswhut
06075d561d 降级node-downloader-helper包 2019-09-09 08:32:15 +08:00
lyswhut
db8ad7cde5 发布0.5.4版本 2019-09-09 08:29:04 +08:00
lyswhut
cacf39da69 暂时移除FLAC格式的meta信息修改 2019-09-09 08:26:18 +08:00
lyswhut
4dc7937321 修复更新弹窗可能导致的bug 2019-09-09 01:32:14 +08:00
lyswhut
1c462ce152 更新依赖包到最新,发布0.5.3版本 2019-09-09 01:05:30 +08:00
lyswhut
2014d2495e 发布0.5.2版本 2019-09-09 00:40:27 +08:00
lyswhut
dc5493f39f 更新免责声明 2019-09-09 00:40:00 +08:00
lyswhut
67ff5e5a4a 优化列表加载体验,移除失效源 2019-09-09 00:33:24 +08:00
lyswhut
22d56d31a9 更新关于软件说明 2019-09-08 01:59:43 +08:00
lyswhut
785427674a 修复下拉列表间隙 2019-09-08 00:41:45 +08:00
lyswhut
581f6c6e1c 更新常见问题说明 2019-09-07 20:03:08 +08:00
lyswhut
7d0be85e84 新增下拉按钮图标 2019-09-07 19:14:47 +08:00
lyswhut
04e75ab540 新增常见问题说明 2019-09-07 13:09:05 +08:00
lyswhut
c117e33d24 新增强迫症设置 2019-09-07 12:46:47 +08:00
lyswhut
cafdad6f9f 修复下载管理的一些Bug 2019-09-07 10:43:17 +08:00
lyswhut
05e212682c 更新选项名称 2019-09-06 13:26:11 +08:00
lyswhut
3f876202c1 尝试修复Mac系统报错问题 2019-09-06 11:47:39 +08:00
lyswhut
57c75799a9 发布0.5.1版本 2019-09-05 22:43:11 +08:00
lyswhut
5cbf2d8047 新增下载列表定位文件功能 2019-09-05 22:41:10 +08:00
lyswhut
3dd9ad9ead 新增右上角最小化/关闭按钮鼠标滑过符号 2019-09-05 21:36:00 +08:00
lyswhut
864bef483f 修复更新弹窗无法弹出的问题 2019-09-05 13:54:26 +08:00
lyswhut
9ea9fefe1d 修复百度源歌单全部分类无法加载的问题 2019-09-05 13:34:41 +08:00
lyswhut
0b953a1517 修复打包部署顺序Bug 2019-09-05 01:54:47 +08:00
lyswhut
80db293aca 发布0.5.0版本 2019-09-05 01:48:11 +08:00
lyswhut
809be41e64 新增:封面嵌入、歌词下载;修复Bug 2019-09-05 01:41:36 +08:00
lyswhut
8a6ee68e58 优化更新检查失败文字提示 2019-09-04 15:09:46 +08:00
lyswhut
da764721d9 新增单例应用功能 2019-09-04 13:08:32 +08:00
lyswhut
0586639e07 略微整理更新超时机制 2019-09-04 11:28:54 +08:00
lyswhut
0aeab00289 优化歌单列表动画 2019-09-04 09:10:06 +08:00
lyswhut
9564097f46 修复歌单无法翻页Bug 2019-09-04 08:58:29 +08:00
lyswhut
d4750f4c6e 添加标签背景动画 2019-09-04 01:24:43 +08:00
lyswhut
f1cc2fe72e 缓存electron-builder数据 2019-09-04 00:44:19 +08:00
lyswhut
6d90539e89 调换打包顺序 2019-09-04 00:41:54 +08:00
lyswhut
21f5ed6afd 发布0.4.0版本 2019-09-04 00:32:24 +08:00
lyswhut
4cbbf2ac81 新增版本更新超时功能 2019-09-04 00:31:34 +08:00
lyswhut
a035a2525a 完成歌单功能 2019-09-04 00:03:29 +08:00
lyswhut
9727601752 切换接口地址 2019-09-03 17:02:31 +08:00
lyswhut
9e9053c0eb 完善歌单功能 2019-09-03 02:49:48 +08:00
lyswhut
507d495f3f 修复http post请求封装的bug 2019-09-02 01:36:03 +08:00
lyswhut
a2f7547cdb 修复歌单接口bug,完善歌单页面 2019-09-02 00:57:57 +08:00
lyswhut
d48308d913 完成歌曲列表分离 2019-09-01 19:24:38 +08:00
lyswhut
78c0badd95 重构歌曲列表 2019-09-01 18:09:54 +08:00
lyswhut
c81c33a430 发布0.3.5版本 2019-08-30 13:11:02 +08:00
lyswhut
1786376a9e 优化请求,修复加载bug 2019-08-29 13:41:44 +08:00
lyswhut
aa973ea984 Update README.md
update
2019-08-29 08:37:18 +08:00
lyswhut
67aeeb0b2f Update README.md
更新readme
2019-08-29 08:36:21 +08:00
lyswhut
ee2b01673b 发布0.3.3版本 2019-08-29 00:58:34 +08:00
lyswhut
c392f7ee8d 修复接口失效问题 2019-08-29 00:53:05 +08:00
lyswhut
4adc950e29 新增百度源接口封装 2019-08-27 23:25:34 +08:00
lyswhut
78db5b55ec 统一调用方法 2019-08-27 23:25:18 +08:00
lyswhut
ad40fdcb55 新增酷狗歌单接口 2019-08-26 14:29:52 +08:00
lyswhut
c5ca510a75 新增TODO列表 2019-08-26 11:11:26 +08:00
lyswhut
13c64a95fa 修复手动定位播放进度条时存在偏差的问题 2019-08-25 00:52:47 +08:00
lyswhut
5d74a78bb7 更新readme 2019-08-25 00:38:08 +08:00
lyswhut
d638bae7c4 优化音量条的视觉效果 2019-08-25 00:23:47 +08:00
lyswhut
9faf8ed55e 新增酷狗排行榜其他音质下载 2019-08-24 21:07:12 +08:00
lyswhut
3aa23b0f9d 发布0.3.2版本 2019-08-24 19:36:34 +08:00
lyswhut
063bbc9f06 新增酷狗排行榜其他音质下载 2019-08-24 19:36:00 +08:00
lyswhut
16bb23b8a7 修复音量条主题适配 2019-08-24 19:00:22 +08:00
lyswhut
a26bef6317 发布v0.3.0版本 2019-08-24 16:19:46 +08:00
lyswhut
21c41d5af1 新增音量调整 2019-08-24 16:18:04 +08:00
lyswhut
b270096591 优化更新弹窗功能,新增非安装版弹窗提醒 2019-08-24 14:24:07 +08:00
lyswhut
be689f088d 更新readme 2019-08-24 10:31:53 +08:00
lyswhut
2410409342 更新readme 2019-08-23 20:42:05 +08:00
lyswhut
a24d682747 重命名appveyor文件 2019-08-23 20:12:36 +08:00
lyswhut
2ada44a2fc 修复取消任务栏进度条可能导致的bug 2019-08-23 20:10:09 +08:00
lyswhut
d6c7fb8dcc 新增MAC、Linux构建并兼容Linux 2019-08-23 19:28:54 +08:00
lyswhut
bb40a8612a 去除切换到临时接口功能 2019-08-22 23:57:10 +08:00
lyswhut
1182a6eba4 删除重复部分 2019-08-22 23:52:00 +08:00
lyswhut
0d303889ef 修正readme文字无法加粗的问题 2019-08-22 23:26:17 +08:00
lyswhut
2b3aab2faf 更新到v0.2.3版本 2019-08-22 23:02:59 +08:00
lyswhut
893d20b4e9 使用临时接口时,试听列表中的下载按钮仍然能点击的Bug 2019-08-22 00:42:42 +08:00
lyswhut
32e421356f 移除专辑栏隐藏设置 2019-08-22 00:29:32 +08:00
130 changed files with 8442 additions and 1916 deletions

View File

@@ -4,14 +4,15 @@ platform:
cache:
- node_modules
- '%APPDATA%\npm-cache'
# - '%USERPROFILE%\.electron'
- '%LOCALAPPDATA%\electron\Cache'
- '%LOCALAPPDATA%\electron-builder\Cache'
install:
- ps: Install-Product node 12 x64
- npm install
build_script:
- npm run pub:gh
- npm run publish:gh
test: off

17
.github/ISSUE_TEMPLATE/----.md vendored Normal file
View File

@@ -0,0 +1,17 @@
---
name: 功能请求
about: 为这个项目提出一个想法
title: 例如添加xxx功能、优化xxx功能
labels: ''
assignees: ''
---
**描述您想要的解决方案**
简洁明了地描述您要发生的事情。
**描述您考虑过的替代方案**
对您考虑过的所有替代解决方案或功能的简洁明了的描述。
**其他内容**
在此处添加有关功能请求的任何其他上下文或屏幕截图。

31
.github/ISSUE_TEMPLATE/--bug.md vendored Normal file
View File

@@ -0,0 +1,31 @@
---
name: 报告Bug
about: 创建报告以帮助我们改进
title: 例如:音乐无法播放
labels: ''
assignees: ''
---
**描述错误**
清楚简洁地说明错误是什么。
**重现**
重现行为的步骤:
1.转到“ ...”
2.点击“ ....”
3.向下滚动到“ ....”
4.看到错误
**预期行为**
对您期望发生的事情的简洁明了的描述。
**截图**
如果适用,请添加屏幕截图以帮助解释您的问题。
**环境:**
  -操作系统及版本:[例如Windows 10 64位 18362.156]
  -软件安装包及版本:[例如Windows 64位绿色版 1.0.0]
**其他内容**
在此处添加有关该问题的任何其他上下文。

3
.gitignore vendored
View File

@@ -34,6 +34,7 @@ build/Release
# Dependency directories
node_modules/
node_modules.bak*/
jspm_packages/
# TypeScript v1 declaration files
@@ -68,3 +69,5 @@ dist
publish/assets
publish/utils/githubToken.js
src/**/*-internal.js

43
.travis.yml Normal file
View File

@@ -0,0 +1,43 @@
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:
- node_modules
- $HOME/.cache/electron
- $HOME/.cache/electron-builder
- $HOME/.npm/_prebuilds
notifications:
email: false
script:
- |
if [ "$TRAVIS_OS_NAME" == "linux" ]; then
npm install && npm run publish:gh:linux
else
npm run publish:gh:mac
fi
before_cache:
- rm -rf $HOME/.cache/electron-builder/wine
# only run this script on pull requests and merges into
# the 'master' and 'prod' branches
branches:
only:
- master

View File

@@ -6,6 +6,293 @@ 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.8.2](https://github.com/lyswhut/lx-music-desktop/compare/v0.8.1...v0.8.2) - 2019-10-20
#### 修复
- 兼容旧版酷我源搜索列表过滤128k音质的bug0.8.1版本仅修复了酷我源的歌曲过滤问题,该修复仅对以后添加的歌曲有效,如果是之前添加的歌曲仍会出现这个问题,现修复对之前旧列表数据的兼容处理)
## [0.8.1](https://github.com/lyswhut/lx-music-desktop/compare/v0.8.0...v0.8.1) - 2019-10-20
#### 修复
- 修复酷我源搜索歌曲结果未添加128k音质导致播放128k音质时显示“该歌曲没有可播放的音频”的问题
## [0.8.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.7.0...v0.8.0) - 2019-10-19
#### 新增
- 新增网易云源歌曲搜索
- 新增网易云源歌单
- 新增各平台通过输入歌单链接或歌单ID打开歌单详情列表目前只适配了**网页版歌单链接**其他方式的歌单链接可能无法解析但你可想办法获取歌单ID后输入打开。注各平台歌单ID均为纯数字若遇到链接里存在歌单ID但无法解析的歌单链接可以到GitHub提交issue或发送邮件或加群830125506反馈
- 新增音量调整滑动功能,现在支持鼠标左右拖动调整音量了
#### 优化
- 优化搜索框搜索体验
- 优化音量条交互视觉效果
- 缓存歌单详情列表数据
#### 修复
- 修复QQ源歌单无法翻页Bug
- 修复默认列表没有创建时无法显示收藏列表的Bug
- 修复网易云128k直接试听
- 修复歌曲音质不存在时仍然播放或下载的Bug
- 修复调整音量时,调整的位置与鼠标点击的位置不一致的问题
## [0.7.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.6.2...v0.7.0) - 2019-10-07
#### 新增
- 新增“我的收藏”本地播放列表
- 新增缓存清理功能,可到**设置-其他**查看与清理软件缓存
- 新增QQ音乐源搜索
- 新增咪咕源搜索
- 新增咪咕源歌单
- 新增咪咕源排行榜
- 新增我的音乐列表歌曲源显示,默认关闭,可到**设置-列表设置**开启
#### 优化
- 优化选择框动画效果
- 尝试优化选我的音乐列表内容很多时多选的卡顿问题
#### 修复
- 修复列表延迟显示的Bug
- 修复QQ音源128k音质试听
## [0.6.2](https://github.com/lyswhut/lx-music-desktop/compare/v0.6.1...v0.6.2) - 2019-10-01
祝贺祖国成立70周年~
#### 新增
- 新增QQ音乐源歌单
#### 修复
- 修正火影皮肤名字
- 修复当试听列表为空时无法切到其他界面的Bug
- 修复百度源搜索结果为空时的接口处理Bug
- 恢复**酷狗**其他音质播放
## [0.6.1](https://github.com/lyswhut/lx-music-desktop/compare/v0.6.0...v0.6.1) - 2019-09-28
### 新增
- 新增试听列表**滚动条位置恢复**设置(可自动恢复到上次离开时的列表滚动位置),本功能默认开启,若不需要可到设置-列表设置将其关闭
- 新增 **《海贼王》** 皮肤,喜欢个性化的可以试试~
### 优化
- 新增DNS解析缓存加快请求速度
- 优化代码逻辑,减少软件对系统资源的占用
- 优化新版本信息检测,尽量减少弹出版本获取失败弹窗弹出的概率
- 优化下拉列表动画效果
### 修复
- 修复请求超时的逻辑处理Bug尝试修复请求无法取消导致的正在播放的歌曲与界面显示的信息不一致的问题
- 修复其他一些小Bug
### 移除
- 移除 `192k` 音质
- 移除酷我音源 `ape` 音质,无损推荐 `flac` 格式
## [0.6.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.5.5...v0.6.0) - 2019-09-21
### 新增
- 新增音乐**聚合搜索**,目前支持酷我、酷狗、百度源搜索
- 新增代理功能
### 优化
- 优化从《梦里嫦娥》皮肤切换到其他皮肤时侧栏动画的切换效果
### 修复
- 修复试听列表没有歌曲时会显示列表加载中的Bug
- 修复切换歌单列表详情时的UI Bug
## [0.5.5](https://github.com/lyswhut/lx-music-desktop/compare/v0.5.4...v0.5.5) - 2019-09-13
### 新增
- 月是故乡明,祝大家中秋快乐🥮~~新增个性皮肤 **《月里嫦娥》**,时间仓促,皮肤还不是很完善,可以试试喜不喜欢~😉
- 新增 MAC 版本退出快捷键支持
- 新增点击播放器中的歌曲标题可以复制标题的功能(遇到好听的歌曲方便分享)
### 修复
- 修复 MAC 系统下软件关闭时再次从 dock 打开时报错的Bug
- 修复下载的歌曲文件名中包含命名规则不允许的符号时下载失败的问题(若歌曲名包含这些符号会自动将其移除)
- 修复 MAC 版本不能复制粘贴的问题
## [0.5.4](https://github.com/lyswhut/lx-music-desktop/compare/v0.5.3...v0.5.4) - 2019-09-09
### 移除
- 下载的FLAC文件在修改歌曲信息后软件无法播放但使用本地播放器可以播放
- 为了稳妥起见暂时移除FLAC格式的meta信息修改
- MP3格式无此问题
## [0.5.3](https://github.com/lyswhut/lx-music-desktop/compare/v0.5.2...v0.5.3) - 2019-09-09
### 优化
- 更新所有依赖包到最新
### 修复
- 修复试听酷狗源的音乐仍然获取320k音质导致获取失败的Bug
## [0.5.2](https://github.com/lyswhut/lx-music-desktop/compare/v0.5.1...v0.5.2) - 2019-09-09
### 新增
- 新增强迫症设置-离开搜索界面时是否清空搜索框
- 设置-关于板块新增常见问题链接
- 歌单左上角的分类按钮添加一个**向下图标**,方便识别该按钮为下拉框(该按钮可选择歌单类型,请自行尝试)
### 优化
- 略微优化最小化按钮字符
- 优化试听列表的加载体验,当歌曲数过多时列表将延迟加载
### 修复
- 修复下载管理的一些Bug
### 移除
- 因接口失效移除网易云音源酷狗音源仅支持播放128k音质
## [0.5.1](https://github.com/lyswhut/lx-music-desktop/compare/v0.5.0...v0.5.1) - 2019-09-05
### 新增
- 新增右上角最小化/关闭按钮鼠标滑过符号
- 新增下载列表定位文件按钮
### 修复
- 修复百度源歌单全部分类无法加载的问题
- 修复更新弹窗无法弹出的问题
## [0.5.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.4.0...v0.5.0) - 2019-09-05
### 新增
- 新增**封面嵌入**(默认开启,可到设置-下载设置关闭)
- 新增**歌词下载**(默认关闭,可到设置-下载设置开启)
- 新增单例应用功能(实现软件单开功能,禁止软件多开)
### 优化
- 优化歌单列表动画
### 修复
- 修复歌单无法翻页的问题
- 修复在某些情况下,添加下载歌曲导致下载列表崩溃的问题
- 修复版本更新弹窗Bug
- 修复酷狗歌单推荐歌单出现在其他分类中的Bug
## [0.4.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.3.5...v0.4.0) - 2019-09-04
### 新增
- 新增**歌单**功能,目前支持酷我、酷狗、百度源歌单
- 在设置界面-关于洛雪音乐说明部分新增**最新版网盘下载地址**与**打赏地址**
- 新增酷狗 电音热歌榜、DJ热歌榜
- 新增版本更新超时功能对于部分无法访问GitHub的用户做更新超时提醒
### 移除
- **注意**0.4.0以前的版本即将失效请更新到0.4.0版本
## [0.3.5](https://github.com/lyswhut/lx-music-desktop/compare/v0.3.4...v0.3.5) - 2019-08-30
### 新增
- 新增**测试接口**,该接口同样速度较慢,但软件的大部分功能可用,**请自行切换到该接口**,找接口辛苦,且用且珍惜!
### 优化
- 取消需要刷新URL时windows任务栏进度显示错误状态现显示为暂停状态
### 修复
- 修复使用临时接口时在试听列表双击灰色歌曲仍然会进行播放的Bug
- 修复歌词加载Bug
## [0.3.4](https://github.com/lyswhut/lx-music-desktop/compare/v0.3.3...v0.3.4) - 2019-08-29
### 优化
- 减少接口不稳定带来的影响,适当增加请求等待时间
### 修复
- 修复播放过程中URL过期不会刷新URL的问题
## [0.3.3](https://github.com/lyswhut/lx-music-desktop/compare/v0.3.2...v0.3.3) - 2019-08-29
### 修复
- **messoer**的接口已经关闭,暂时切换到临时接口使用,部分功能受限。。。
- 修复设置界面更新出错时仍然显示更新下载中的问题
- 修复手动定位播放进度条时存在偏差的问题
- 屏蔽播放器中没有歌曲时对进度条的点击
## [0.3.2](https://github.com/lyswhut/lx-music-desktop/compare/v0.3.1...v0.3.2) - 2019-08-24
### 新增
- 新增酷狗排行榜其他音质下载
## [0.3.1](https://github.com/lyswhut/lx-music-desktop/compare/v0.3.0...v0.3.1) - 2019-08-24
### 修复
- 修复音量条主题适配
## [0.3.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.2.3...v0.3.0) - 2019-08-24
### 新增
- 新增**MAC**及**Linux**版本(需要的可自行下载)
- 新增音量调整
- 新增任务栏播放进度条控制选项(现在可在设置界面关闭在任务栏显示的播放进度)
- 新增更新出错时的弹窗提示
- 从该版本起,非安装版也会有更新弹窗提醒了,但仍然需要手动下载新版本更新,版本信息可到设置页面查看
### 修复
- 强制把临时接口设置回 `messoer` 接口
## [0.2.3](https://github.com/lyswhut/lx-music-desktop/compare/v0.2.2...v0.2.3) - 2019-08-22
### 新增
- 新增任务栏程序标题改变功能(播放歌曲时任务栏标题将显示当前播放的歌曲)
### 修复
- 使用临时接口时试听列表中的下载按钮仍然能点击的Bug
- 修复某些情况下歌曲链接未能缓存的问题
### 移除
- 移除临时接口(因服务器被攻击,本接口已关闭)
- 移除列表栏设置的隐藏专辑栏选项感觉这个设置并没有什么luan用并且还会打破布局
## [0.2.2](https://github.com/lyswhut/lx-music-desktop/compare/v0.2.1...v0.2.2) - 2019-08-21
### 修复

60
FAQ.md Normal file
View File

@@ -0,0 +1,60 @@
# lx-music-desktop 常见问题
在阅读本常见问题后仍然无法解决你的问题请提交issue或者加企鹅群`830125506`反馈,反馈时请**注明**已阅读常见问题!
## 软件为什么没有桌面歌词与自定义列表功能
洛雪音乐的最初定位不是作为播放器开发的,它主要用于**查找歌曲**,软件的播放功能仅用于试听,不建议用作为常用播放器使用,因此无桌面、界面歌词,不可自定义列表。
## 歌曲无法试听与下载
该问题解决顺序如下:
1. 尝试更新到最新版本
2. 尝试切换其他歌曲(或直接搜索该歌曲),若全部歌曲都无法试听与下载则进行下一步
3. 尝试到设置-接口来源切换到其他接口
4. 尝试切换网络,比如用手机开热点(目前存在某些网络无法访问接口服务器的情况)
5. 若还不行请到这个链接查看详情:<https://github.com/lyswhut/lx-music-desktop/issues/5>
6. 若没有在第5条链接中的第一条评论中看到接口无法使用的说明则应该是你网络无法访问接口服务器的问题如果接口有问题我会在那里说明。
想要知道是不是自己网络的问题可以看看`https://ts.tempmusic.tk`能不能在浏览器打开浏览器显示404是正常的如果不是404那就证明所在网络无法访问接口服务器。
若网页无法打开或打来不是404则应该是DNS的问题可以尝试以下办法
1. 改成自动获取试试
2. 手动把DNS改一下不要用360的DNS可以把DNS改成`114.114.114.114``8.8.8.8`
3. 软件设置里面的代理不要勾起来
## 软件安装包说明
软件发布页及网盘中有多个类型的安装文件,以下是对这些类型文件的说明:
文件名带 `win_` 的是在Windows系统上运行的版本<br>
其中安装版Setup可自动更新软件<br>
绿色版green为免安装版自动更新功能不可用
**`.dmg`** 结尾的文件为 MAC 版本;
**`.AppImage`**、**`.deb`** 结尾的为 Linux 版本。
带有`x64`的为64位的系统版本`x86`的为32位的系统版本若两个都带有的则为集合版安装时会自动根据系统位数选择对应的版本安装。
## 软件更新
软件启动时若发现新版本时会自动从本仓库下载安装包,下载完毕会弹窗提示更新。<br>
若下载未完成时软件被关闭,下次启动软件会再次自动下载。<br>
若还是**更新失败**可能是无法访问GitHub导致的这时需要手动更新即下载最新安装包直接覆盖安装即可。<br>
注意:**绿色版**的软件自动更新功能**不可用**,建议使用安装版!!<br>
注意:**Mac版**、**Linux deb**版不支持自动更新!
## Windows 7 下界面异常
当 win7 没有开启**透明效果**时界面将会显示异常,开启方法请自行百度。<br>
对于一些开启透明效果后仍然无法正常显示界面的系统,我也不知道是什么原因导致的,只能说正常的系统是没有这个问题的,如果你知道原因导致的欢迎反馈!
## 安装版安装失败,提示应用未安装
对于部分电脑出现安装失败的问题我也不懂什么原因,,可以尝试清理下安装文件,或者重启电脑试试。
## 缺少`xxx.dll`
这个是电脑缺少某些dll导致的正常的系统是没有这个问题的解决办法需自行百度弹出的错误信息看下别人是怎么解决的。

View File

@@ -3,7 +3,8 @@
<p align="center">
<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://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://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://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> -->
</p>
@@ -30,23 +31,23 @@
### 说明
一个基于 Electron + Vue 开发的 Windows 版音乐软件。
一个基于 Electron + Vue 开发的音乐软件。
所用技术栈:
- Electron 6.x
- Vue 2.x
已支持的平台:
- Windows 7 及以上
- Mac OS
- Linux
软件变化请查看:[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)<br>
软件下载请转到:[发布页面](https://github.com/lyswhut/lx-music-desktop/releases)
其他说明TODO
#### 关于软件更新
软件启动时若发现新版本时会自动从本仓库下载安装包,下载完毕会弹窗提示更新。<br>
若下载未完成时软件被关闭,下次启动软件会再次自动下载。<br>
目前暂未添加跳过更新某个版本的功能。<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#常见问题)
### 源码使用方法
@@ -59,7 +60,7 @@ npm run dev
# 构建免安装版
npm run pack:dir
# 构建安装包
# 构建安装包windows版
npm run pack
```
@@ -68,9 +69,20 @@ npm run pack
<p><a href="https://github.com/lyswhut/lx-music-desktop"><img width="100%" src="https://github.com/lyswhut/lx-music-desktop/blob/master/doc/images/app.png" alt="lx-music UI"></a></p>
### 常见问题
常见问题已移至:<https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md>
### 致谢
感谢 [@messoer](https://github.com/messoer) 提供的部分音乐API
感谢 [@messoer](https://github.com/messoer) 曾经提供的部分音乐API
### 免责声明
本项目**不开发或者破解直接获取音频数据**的功能,所有音频数据均来自**第三方接口**<br>
本软件仅用于**测试 `electron 6.x` 在各种系统上的兼容性**及用于**对比各大音乐平台歌单、排行榜等数据列表的差异性**,使用本软件产生的**任何涉及版权相关的数据**请于**24小时内删除**。<br>
本软件仅用于学习交流使用,禁止用于商业用途,使用本软件所造成的的后果由使用者承担!<br>
若对此有疑问请 mail to: lyswhut@qq.com
### 许可证

View File

@@ -7,11 +7,6 @@ module.exports = {
libraryTarget: 'commonjs2',
path: path.join(__dirname, '../../dist/electron'),
},
externals: [
// suppress electron-debug warning
// see https://github.com/SimulatedGREG/electron-vue/issues/498
{ 'electron-debug': 'electron-debug' },
],
resolve: {
alias: {
common: path.join(__dirname, '../../src/common'),

5
licenses/license_en.txt Normal file
View File

@@ -0,0 +1,5 @@
This program is only for learning to communicate!
Do not use for commercial purposes! !
All consequences of using this software are borne by the user!
By: lyswhut

5
licenses/license_zh.txt Normal file
View File

@@ -0,0 +1,5 @@
本程序仅用于学习交流使用!
请勿用于商业用途!!
使用本软件造成的一切后果由使用者承担!
By: 落雪无痕

2317
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,40 @@
{
"name": "lx-music-desktop",
"version": "0.2.2",
"version": "0.8.2",
"description": "一个免费的音乐下载助手",
"main": "./dist/electron/main.js",
"productName": "lx-music-desktop",
"scripts": {
"pack": "node build-config/pack.js && npm run pack:win",
"pack:win": "npm run pack:win:setup && npm run pack:win:7z",
"pack:win:setup": "cross-env TARGET=win_安装版 ARCH=x64_x86 electron-builder -w=nsis --x64 --ia32",
"pack:win:portable": "npm run pack:win:portable:x64_x86 && npm run pack:win:portable:x64 && npm run pack:win:portable:x86",
"pack:win:portable:x64_x86": "cross-env TARGET=便携版 ARCH=x64_x86 electron-builder -w=portable --x64 --ia32",
"pack:win:portable:x64": "cross-env TARGET=便携版 ARCH=x64 electron-builder -w=portable --x64",
"pack:win:portable:x86": "cross-env TARGET=便携版 ARCH=x86 electron-builder -w=portable --ia32",
"pack:win:7z": "npm run pack:win:7z:x64 && npm run pack:win:7z:x86",
"pack:win:7z:x64": "cross-env TARGET=win_绿色版 ARCH=x64 electron-builder -w=7z --x64",
"pack:win:7z:x86": "cross-env TARGET=win_绿色版 ARCH=x86 electron-builder -w=7z --ia32",
"publish": "node publish",
"pub:gh": "node build-config/pack.js && electron-builder --win -p always",
"pack": "node build-config/pack.js && electron-builder -w",
"publish:gh": "node build-config/pack.js && npm run publish:win",
"publish:win": "npm run publish:win:7z && npm run publish:win:setup",
"publish:win:setup": "cross-env TARGET=Setup ARCH=x64_x86 electron-builder -w=nsis --x64 --ia32 -p onTagOrDraft",
"publish:win:portable": "npm run publish:win:portable:x64_x86 && npm run publish:win:portable:x64 && npm run publish:win:portable:x86",
"publish:win:portable:x64_x86": "cross-env TARGET=portable ARCH=x64_x86 electron-builder -w=portable --x64 --ia32 -p onTagOrDraft",
"publish:win:portable:x64": "cross-env TARGET=portable ARCH=x64 electron-builder -w=portable --x64 -p onTagOrDraft",
"publish:win:portable:x86": "cross-env TARGET=portable ARCH=x86 electron-builder -w=portable --ia32 -p onTagOrDraft",
"publish:win:7z": "npm run publish:win:7z:x64 && npm run publish:win:7z:x86",
"publish:win:7z:x64": "cross-env TARGET=green ARCH=win_x64 electron-builder -w=7z --x64 -p always",
"publish:win:7z:x86": "cross-env TARGET=green ARCH=win_x86 electron-builder -w=7z --ia32 -p onTagOrDraft",
"publish:gh:mac": "node build-config/pack.js && npm run publish:mac",
"publish:mac": "npm run publish:mac:dmg",
"publish:mac:dmg": "electron-builder -m=dmg -p onTagOrDraft",
"publish:gh:linux": "node build-config/pack.js && npm run publish:linux",
"publish:linux": "npm run publish:linux:deb && npm run publish:linux:appImage",
"publish:linux:appImage": "cross-env ARCH=x64 electron-builder -l=AppImage -p onTagOrDraft",
"publish:linux:deb": "npm run publish:linux:deb:x64 && npm run publish:linux:deb:x86",
"publish:linux:deb:x64": "cross-env ARCH=x64 electron-builder -l=deb --x64 -p onTagOrDraft",
"publish:linux:deb:x86": "cross-env ARCH=x86 electron-builder -l=deb --ia32 -p onTagOrDraft",
"pack:linux": "node build-config/pack.js && electron-builder -l",
"pack:dir": "node build-config/pack.js && electron-builder --dir",
"dev": "node build-config/runner-dev.js",
@@ -29,54 +57,55 @@
"build": {
"appId": "cn.toside.music.desktop",
"directories": {
"output": "build"
"output": "./build"
},
"files": [
"dist/electron/**/*"
],
"extraResources": [
"./licenses"
],
"win": {
"icon": "src/static/icons/lunch.ico",
"icon": "./resources/icons/256x256.ico",
"legalTrademarks": "lyswhut",
"target": [
{
"arch": [
"ia32",
"x64"
],
"target": "nsis"
}
]
"artifactName": "${productName} v${version} ${env.ARCH} ${env.TARGET}.${ext}"
},
"mac": {
"icon": "./resources/icons/512x512.png",
"category": "public.app-category.music"
},
"linux": {
"target": [
{
"target": "AppImage",
"arch": [
"x64"
]
},
{
"arch": [
"ia32",
"x64"
],
"target": "deb"
},
{
"arch": [
"x64"
],
"target": "snap"
}
],
"maintainer": "lyswhut <lyswuhut@qq.com>"
"maintainer": "lyswhut <lyswuhut@qq.com>",
"artifactName": "${productName} v${version} ${env.ARCH}.${ext}"
},
"nsis": {
"oneClick": false,
"language": "2052",
"allowToChangeInstallationDirectory": true,
"differentialPackage": true,
"license": "./license.rtf"
"license": "./licenses/license.rtf",
"shortcutName": "lx-music"
},
"dmg": {
"contents": [
{
"x": 110,
"y": 150,
"name": "lx-music"
},
{
"x": 240,
"y": 150,
"type": "link",
"path": "/Applications",
"name": "lx-music"
}
],
"title": "洛雪音乐助手 v${version}"
},
"appImage": {
"license": "./licenses/license_zh.txt",
"category": "Audio"
},
"publish": [
{
@@ -90,51 +119,58 @@
"type": "git",
"url": "git+https://github.com/lyswhut/lx-music-desktop.git"
},
"keywords": [],
"author": "lyswhut",
"keywords": [
"music-player",
"electron-app",
"vuejs2"
],
"author": {
"name": "lyswhut",
"email": "lyswuhut@qq.com"
},
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/lyswhut/lx-music-desktop/issues"
},
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
"devDependencies": {
"@babel/core": "^7.5.5",
"@babel/core": "^7.6.4",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/polyfill": "^7.4.4",
"@babel/preset-env": "^7.5.5",
"autoprefixer": "^9.6.1",
"babel-eslint": "^10.0.2",
"@babel/polyfill": "^7.6.0",
"@babel/preset-env": "^7.6.3",
"autoprefixer": "^9.6.5",
"babel-eslint": "^10.0.3",
"babel-loader": "^8.0.6",
"babel-minify-webpack-plugin": "^0.3.1",
"babel-preset-minify": "^0.5.0",
"cfonts": "^2.4.4",
"babel-preset-minify": "^0.5.1",
"cfonts": "^2.4.5",
"chalk": "^2.4.2",
"changelog-parser": "^2.8.0",
"copy-webpack-plugin": "^5.0.4",
"core-js": "^3.2.1",
"cos-nodejs-sdk-v5": "^2.5.11",
"cross-env": "^5.2.0",
"core-js": "^3.3.2",
"cos-nodejs-sdk-v5": "^2.5.12",
"cross-env": "^6.0.3",
"css-loader": "^3.2.0",
"del": "^3.0.0",
"electron": "^6.0.2",
"del": "^5.1.0",
"electron": "^6.0.12",
"electron-builder": "^21.2.0",
"electron-debug": "^3.0.1",
"electron-devtools-installer": "^2.2.4",
"eslint": "^6.1.0",
"eslint-config-standard": "^13.0.1",
"eslint": "^6.5.1",
"eslint-config-standard": "^14.1.0",
"eslint-formatter-friendly": "^7.0.0",
"eslint-loader": "^2.2.1",
"eslint-loader": "^3.0.2",
"eslint-plugin-html": "^6.0.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-node": "^9.1.0",
"eslint-plugin-node": "^10.0.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-standard": "^4.0.1",
"file-loader": "^4.2.0",
"friendly-errors-webpack-plugin": "^1.7.0",
"html-webpack-plugin": "^3.2.0",
"less": "^3.9.0",
"less": "^3.10.3",
"less-loader": "^5.0.0",
"markdown-it": "^9.1.0",
"markdown-it": "^10.0.0",
"mini-css-extract-plugin": "^0.8.0",
"multispinner": "^0.2.1",
"optimize-css-assets-webpack-plugin": "^5.0.3",
@@ -144,31 +180,35 @@
"pug-plain-loader": "^1.0.0",
"raw-loader": "^3.1.0",
"rimraf": "^3.0.0",
"stylus": "^0.54.5",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"terser-webpack-plugin": "^1.4.1",
"url-loader": "^2.1.0",
"terser-webpack-plugin": "^2.1.3",
"url-loader": "^2.2.0",
"vue-loader": "^15.7.1",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.10",
"webpack": "^4.39.2",
"webpack-cli": "^3.3.6",
"webpack-dev-server": "^3.8.0",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.8.2",
"webpack-hot-middleware": "^2.25.0",
"webpack-merge": "^4.2.1"
"webpack-merge": "^4.2.2"
},
"dependencies": {
"axios": "^0.19.0",
"electron-log": "^3.0.7",
"electron-store": "^4.0.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",
"flac-metadata": "^0.1.1",
"js-htmlencode": "^0.3.0",
"lrc-file-parser": "^0.1.12",
"lrc-file-parser": "^0.1.14",
"node-downloader-helper": "^1.0.10",
"node-id3": "^0.1.11",
"request": "^2.88.0",
"vue": "^2.6.10",
"vue-electron": "^1.0.6",
"vue-router": "^3.1.2",
"vue-router": "^3.1.3",
"vuex": "^3.1.1",
"vuex-electron": "^1.0.3",
"vuex-router-sync": "^5.0.0"

View File

@@ -1,4 +1,3 @@
### 修复
#### 修复
- 修复下载过程中出错重试5次都失败后不会自动开始下一个任务的Bug
- 修复播放到一半URL过期时不会刷新URL直接播放下一首的问题
- 兼容旧版酷我源搜索列表过滤128k音质的bug0.8.1版本仅修复了酷我源的歌曲过滤问题,该修复仅对以后添加的歌曲有效,如果是之前添加的歌曲仍会出现这个问题,现修复对之前旧列表数据的兼容处理)

View File

@@ -1,7 +1,91 @@
{
"version": "0.2.2",
"desc": "<h3>修复</h3>\n<ul>\n<li>修复下载过程中出错重试5次都失败后不会自动开始下一个任务的Bug</li>\n<li>修复播放到一半URL过期时不会刷新URL直接播放下一首的问题</li>\n</ul>\n",
"version": "0.8.2",
"desc": "<h4>修复</h4>\n<ul>\n<li>兼容旧版酷我源搜索列表过滤128k音质的bug0.8.1版本仅修复了酷我源的歌曲过滤问题,该修复仅对以后添加的歌曲有效,如果是之前添加的歌曲仍会出现这个问题,现修复对之前旧列表数据的兼容处理)</li>\n</ul>\n",
"history": [
{
"version": "0.8.1",
"desc": "<h4>修复</h4>\n<ul>\n<li>修复酷我源搜索歌曲结果未添加128k音质导致播放128k音质时显示“该歌曲没有可播放的音频”的问题</li>\n</ul>\n"
},
{
"version": "0.8.0",
"desc": "<h4>新增</h4>\n<ul>\n<li>新增网易云源歌曲搜索</li>\n<li>新增网易云源歌单</li>\n<li>新增各平台通过输入歌单链接或歌单ID打开歌单详情列表目前只适配了<strong>网页版歌单链接</strong>其他方式的歌单链接可能无法解析但你可想办法获取歌单ID后输入打开。注各平台歌单ID均为纯数字若遇到链接里存在歌单ID但无法解析的歌单链接可以到GitHub提交issue或发送邮件或加群830125506反馈</li>\n<li>新增音量调整滑动功能,现在支持鼠标左右拖动调整音量了</li>\n</ul>\n<h4>优化</h4>\n<ul>\n<li>优化搜索框搜索体验</li>\n<li>优化音量条交互视觉效果</li>\n<li>缓存歌单详情列表数据</li>\n</ul>\n<h4>修复</h4>\n<ul>\n<li>修复QQ源歌单无法翻页Bug</li>\n<li>修复默认列表没有创建时无法显示收藏列表的Bug</li>\n<li>修复网易云128k直接试听</li>\n<li>修复歌曲音质不存在时仍然播放或下载的Bug</li>\n<li>修复调整音量时,调整的位置与鼠标点击的位置不一致的问题</li>\n</ul>\n"
},
{
"version": "0.7.0",
"desc": "<h4>新增</h4>\n<ul>\n<li>新增“我的收藏”本地播放列表</li>\n<li>新增缓存清理功能,可到<strong>设置-其他</strong>查看与清理软件缓存</li>\n<li>新增QQ音乐源搜索</li>\n<li>新增咪咕源搜索</li>\n<li>新增咪咕源歌单</li>\n<li>新增咪咕源排行榜</li>\n<li>新增我的音乐列表歌曲源显示,默认关闭,可到<strong>设置-列表设置</strong>开启</li>\n</ul>\n<h4>优化</h4>\n<ul>\n<li>优化选择框动画效果</li>\n<li>尝试优化选我的音乐列表内容很多时多选的卡顿问题</li>\n</ul>\n<h4>修复</h4>\n<ul>\n<li>修复列表延迟显示的Bug</li>\n<li>修复QQ音源128k音质试听</li>\n</ul>\n"
},
{
"version": "0.6.2",
"desc": "<p>祝贺祖国成立70周年~</p>\n<h4>新增</h4>\n<ul>\n<li>新增QQ音乐源歌单</li>\n</ul>\n<h4>修复</h4>\n<ul>\n<li>修正火影皮肤名字</li>\n<li>修复当试听列表为空时无法切到其他界面的Bug</li>\n<li>修复百度源搜索结果为空时的接口处理Bug</li>\n<li>恢复<strong>酷狗</strong>其他音质播放</li>\n</ul>\n"
},
{
"version": "0.6.1",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增试听列表<strong>滚动条位置恢复</strong>设置(可自动恢复到上次离开时的列表滚动位置),本功能默认开启,若不需要可到设置-列表设置将其关闭</li>\n<li>新增 <strong>《海贼王》</strong> 皮肤,喜欢个性化的可以试试~</li>\n</ul>\n<h3>优化</h3>\n<ul>\n<li>新增DNS解析缓存加快请求速度</li>\n<li>优化代码逻辑,减少软件对系统资源的占用</li>\n<li>优化新版本信息检测,尽量减少弹出版本获取失败弹窗弹出的概率</li>\n<li>优化下拉列表动画效果</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复请求超时的逻辑处理Bug尝试修复请求无法取消导致的正在播放的歌曲与界面显示的信息不一致的问题</li>\n<li>修复其他一些小Bug</li>\n</ul>\n<h3>移除</h3>\n<ul>\n<li>移除 <code>192k</code> 音质</li>\n<li>移除酷我音源 <code>ape</code> 音质,无损推荐 <code>flac</code> 格式</li>\n</ul>\n"
},
{
"version": "0.6.0",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增音乐<strong>聚合搜索</strong>,目前支持酷我、酷狗、百度源搜索</li>\n<li>新增代理功能</li>\n</ul>\n<h3>优化</h3>\n<ul>\n<li>优化从《梦里嫦娥》皮肤切换到其他皮肤时侧栏动画的切换效果</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复试听列表没有歌曲时会显示列表加载中的Bug</li>\n<li>修复切换歌单列表详情时的UI Bug</li>\n</ul>\n"
},
{
"version": "0.5.5",
"desc": "<h3>新增</h3>\n<ul>\n<li>月是故乡明,祝大家中秋快乐🥮~~新增个性皮肤**《月里嫦娥》**,时间仓促,皮肤还不是很完善,可以试试喜不喜欢~😉</li>\n<li>新增 MAC 版本退出快捷键支持</li>\n<li>新增点击播放器中的歌曲标题可以复制标题的功能(遇到好听的歌曲方便分享)</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复 MAC 系统下软件关闭时再次从 dock 打开时报错的Bug</li>\n<li>修复下载的歌曲文件名中包含命名规则不允许的符号时下载失败的问题(若歌曲名包含这些符号会自动将其移除)</li>\n<li>修复 MAC 版本不能复制粘贴的问题</li>\n</ul>\n"
},
{
"version": "0.5.4",
"desc": "<h3>移除</h3>\n<ul>\n<li>下载的FLAC文件在修改歌曲信息后软件无法播放但使用本地播放器可以播放</li>\n<li>为了稳妥起见暂时移除FLAC格式的meta信息修改</li>\n<li>MP3格式无此问题</li>\n</ul>\n"
},
{
"version": "0.5.3",
"desc": "<h3>优化</h3>\n<ul>\n<li>更新所有依赖包到最新</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复试听酷狗源的音乐仍然获取320k音质导致获取失败的Bug</li>\n</ul>\n"
},
{
"version": "0.5.2",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增强迫症设置-离开搜索界面时是否清空搜索框</li>\n<li>设置-关于板块新增常见问题链接</li>\n<li>歌单左上角的分类按钮添加一个<strong>向下图标</strong>,方便识别该按钮为下拉框(该按钮可选择歌单类型,请自行尝试)</li>\n</ul>\n<h3>优化</h3>\n<ul>\n<li>略微优化最小化按钮字符</li>\n<li>优化试听列表的加载体验,当歌曲数过多时列表将延迟加载</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复下载管理的一些Bug</li>\n</ul>\n<h3>移除</h3>\n<ul>\n<li>因接口失效移除网易云音源酷狗音源仅支持播放128k音质</li>\n</ul>\n"
},
{
"version": "0.5.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</ul>\n"
},
{
"version": "0.5.0",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增<strong>封面嵌入</strong>(默认开启,可到设置-下载设置关闭)</li>\n<li>新增<strong>歌词下载</strong>(默认关闭,可到设置-下载设置开启)</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>修复在某些情况下,添加下载歌曲导致下载列表崩溃的问题</li>\n<li>修复版本更新弹窗Bug</li>\n<li>修复酷狗歌单推荐歌单出现在其他分类中的Bug</li>\n</ul>\n"
},
{
"version": "0.4.0",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增<strong>歌单</strong>功能,目前支持酷我、酷狗、百度源歌单</li>\n<li>在设置界面-关于洛雪音乐说明部分新增<strong>最新版网盘下载地址</strong>与<strong>打赏地址</strong></li>\n<li>新增酷狗 电音热歌榜、DJ热歌榜</li>\n<li>新增版本更新超时功能对于部分无法访问GitHub的用户做更新超时提醒</li>\n</ul>\n<h3>移除</h3>\n<ul>\n<li><strong>注意</strong>0.4.0以前的版本即将失效请更新到0.4.0版本</li>\n</ul>\n"
},
{
"version": "0.3.5",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增<strong>测试接口</strong>,该接口同样速度较慢,但软件的大部分功能可用,<strong>请自行切换到该接口</strong>,找接口辛苦,且用且珍惜!</li>\n</ul>\n<h3>优化</h3>\n<ul>\n<li>取消需要刷新URL时windows任务栏进度显示错误状态现显示为暂停状态</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复使用临时接口时在试听列表双击灰色歌曲仍然会进行播放的Bug</li>\n<li>修复歌词加载Bug</li>\n</ul>\n"
},
{
"version": "0.3.4",
"desc": "<h3>优化</h3>\n<ul>\n<li>减少接口不稳定带来的影响,适当增加请求等待时间</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复播放过程中URL过期不会刷新URL的问题</li>\n</ul>\n"
},
{
"version": "0.3.3",
"desc": "<h3>修复</h3>\n<ul>\n<li><strong>messoer</strong>的接口已经关闭,暂时切换到临时接口使用,部分功能受限。。。</li>\n<li>修复设置界面更新出错时仍然显示更新下载中的问题</li>\n<li>修复手动定位播放进度条时存在偏差的问题</li>\n<li>屏蔽播放器中没有歌曲时对进度条的点击</li>\n</ul>\n"
},
{
"version": "0.3.2",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增酷狗排行榜其他音质下载</li>\n</ul>\n"
},
{
"version": "0.3.1",
"desc": "<h3>修复</h3>\n<ul>\n<li>修复音量条主题适配</li>\n</ul>\n"
},
{
"version": "0.3.0",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增<strong>MAC</strong>及<strong>Linux</strong>版本(需要的可自行下载)</li>\n<li>新增音量调整</li>\n<li>新增任务栏播放进度条控制选项(现在可在设置界面关闭在任务栏显示的播放进度)</li>\n<li>新增更新出错时的弹窗提示</li>\n<li>从该版本起,非安装版也会有更新弹窗提醒了,但仍然需要手动下载新版本更新,版本信息可到设置页面查看</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>强制把临时接口设置回 <code>messoer</code> 接口</li>\n</ul>\n"
},
{
"version": "0.2.3",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增任务栏程序标题改变功能(播放歌曲时任务栏标题将显示当前播放的歌曲)</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>使用临时接口时试听列表中的下载按钮仍然能点击的Bug</li>\n<li>修复某些情况下歌曲链接未能缓存的问题</li>\n</ul>\n<h3>移除</h3>\n<ul>\n<li>移除临时接口(因服务器被攻击,本接口已关闭)</li>\n<li>移除列表栏设置的隐藏专辑栏选项感觉这个设置并没有什么luan用并且还会打破布局</li>\n</ul>\n"
},
{
"version": "0.2.2",
"desc": "<h3>修复</h3>\n<ul>\n<li>修复下载过程中出错重试5次都失败后不会自动开始下一个任务的Bug</li>\n<li>修复播放到一半URL过期时不会刷新URL直接播放下一首的问题</li>\n</ul>\n"
},
{
"version": "0.2.1",
"desc": "<h3>优化</h3>\n<ul>\n<li>新增歌曲URL存储当URL无效时才重新获取以减少接口不稳定的影响</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复歌曲加载无法加载时自动切换混乱的Bug</li>\n<li>修复移除列表最后一首歌曲时播放器不停止播放的问题</li>\n</ul>\n"

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

BIN
resources/icons/512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

8
src/common/utils.js Normal file
View File

@@ -0,0 +1,8 @@
const log = require('electron-log')
exports.isLinux = process.platform == 'linux'
exports.isWin = process.platform == 'win32'
exports.isMac = process.platform == 'darwin'
exports.log = log

View File

@@ -3,13 +3,13 @@ html(lang="cn")
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1.0")
meta(http-equiv="X-UA-Compatible" content="ie=edge")
title= require('../package.json').name
title 洛雪音乐助手
body
#root
//- if htmlWebpackPlugin.options.isProd
//- script.
//- window.__static = '!{require('path').join(htmlWebpackPlugin.options.__dirname, '/static').replace(/\\/g, '\\\\')}'
if !htmlWebpackPlugin.options.browser && htmlWebpackPlugin.options.isProd
//- if !htmlWebpackPlugin.options.browser && htmlWebpackPlugin.options.isProd
script.
window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
window.__static = require('path').join(__dirname, '/resources').replace(/\\/g, '\\\\')

View File

@@ -1,4 +1,6 @@
require('./request')
require('./appName')
// require('./appName')
require('./progressBar')
require('./trafficLight')
require('./musicMeta')

View File

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

View File

@@ -1,11 +1,10 @@
const { mainOn } = require('../../common/icp')
module.exports = win => {
mainOn('progress', (event, params) => {
// console.log(params)
win.setProgressBar(params.status, {
mode: params.mode || 'normal',
})
})
}
mainOn('progress', (event, params) => {
// console.log(params)
global.mainWindow && global.mainWindow.setProgressBar(params.status, {
mode: params.mode || 'normal',
})
})

View File

@@ -7,7 +7,7 @@ const tasks = []
mainOn('request', (event, options) => {
// console.log(args)
if (!options) return
let index = fatchData(options, (err, resp) => {
let index = fetchData(options, (err, resp) => {
tasks[index] = null
if (err) {
console.log(err)
@@ -27,7 +27,7 @@ mainOn('cancelRequest', (event, index) => {
tasks[index] = null
})
const fatchData = (options, callback) => pushTask(tasks, request(options.url, {
const fetchData = (options, callback) => pushTask(tasks, request(options.url, {
method: options.method,
headers: options.headers,
Origin: options.origin,

View File

@@ -1,19 +1,22 @@
// const { app } = require('electron')
const { mainOn } = require('../../common/icp')
module.exports = win => {
mainOn('min', event => {
if (win) {
win.minimize()
}
})
// mainOn('max', event => {
// if (win) {
// win.maximize()
// }
// })
mainOn('close', event => {
if (win) {
win.close()
}
})
}
mainOn('min', event => {
if (global.mainWindow) {
global.mainWindow.minimize()
}
})
// mainOn('max', event => {
// if (global.mainWindow) {
// global.mainWindow.maximize()
// }
// })
mainOn('close', event => {
if (global.mainWindow) {
// global.mainWindowdow.destroy()
// console.log('close')
// app.quit()
global.mainWindow.close()
}
})

View File

@@ -1,10 +1,23 @@
const { app, BrowserWindow } = require('electron')
const { app, BrowserWindow, Menu } = require('electron')
const path = require('path')
// 单例应用程序
if (!app.requestSingleInstanceLock()) {
app.quit()
return
}
app.on('second-instance', (event, argv, cwd) => {
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore()
mainWindow.focus()
} else {
app.quit()
}
})
require('./events')
const progressBar = require('./events/progressBar')
const trafficLight = require('./events/trafficLight')
const autoUpdate = require('./utils/autoUpdate')
const { isLinux, isMac } = require('../common/utils')
const isDev = process.env.NODE_ENV !== 'production'
@@ -15,10 +28,11 @@ const isDev = process.env.NODE_ENV !== 'production'
let mainWindow
let winURL
let isFirstCheckedUpdate = true
if (isDev) {
global.__static = path.join(__dirname, '../static')
winURL = `http://localhost:9080`
winURL = 'http://localhost:9080'
} else {
global.__static = path.join(__dirname, '/static')
winURL = `file://${__dirname}/index.html`
@@ -28,13 +42,13 @@ function createWindow() {
/**
* Initial window options
*/
mainWindow = new BrowserWindow({
mainWindow = global.mainWindow = new BrowserWindow({
height: 590,
useContentSize: true,
width: 920,
frame: false,
transparent: true,
icon: path.join(global.__static, 'icons/lunch.ico'),
transparent: !isLinux,
// icon: path.join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'),
resizable: false,
maximizable: false,
fullscreenable: false,
@@ -47,23 +61,66 @@ function createWindow() {
mainWindow.loadURL(winURL)
mainWindow.on('close', () => {
mainWindow.setProgressBar(-1)
})
mainWindow.on('closed', () => {
mainWindow = null
mainWindow = global.mainWindow = null
})
// mainWindow.webContents.openDevTools()
trafficLight(mainWindow)
progressBar(mainWindow)
if (!isDev) autoUpdate(mainWindow)
if (!isDev) {
autoUpdate(isFirstCheckedUpdate)
isFirstCheckedUpdate = false
}
}
app.once('ready', createWindow)
if (isMac) {
const template = [
{
label: app.getName(),
submenu: [
{ label: '关于洛雪音乐', role: 'about' },
{ type: 'separator' },
{ label: '隐藏', role: 'hide' },
{ label: '显示其他', role: 'hideothers' },
{ label: '显示全部', role: 'unhide' },
{ type: 'separator' },
{ label: '退出', accelerator: 'Command+Q', click: () => app.quit() },
],
},
{
label: '窗口',
role: 'window',
submenu: [
{ label: '最小化', role: 'minimize' },
{ label: '关闭', role: 'close' },
],
},
{
label: '编辑',
submenu: [
{ label: '撤销', accelerator: 'CmdOrCtrl+Z', role: 'undo' },
{ label: '恢复', accelerator: 'Shift+CmdOrCtrl+Z', role: 'redo' },
{ type: 'separator' },
{ label: '剪切', accelerator: 'CmdOrCtrl+X', role: 'cut' },
{ label: '复制', accelerator: 'CmdOrCtrl+C', role: 'copy' },
{ label: '粘贴', accelerator: 'CmdOrCtrl+V', role: 'paste' },
{ label: '选择全部', accelerator: 'CmdOrCtrl+A', role: 'selectAll' },
],
},
]
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
} else {
Menu.setApplicationMenu(null)
}
app.on('ready', createWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
if (!isMac) app.quit()
})
app.on('activate', () => {
@@ -71,4 +128,3 @@ app.on('activate', () => {
createWindow()
}
})

View File

@@ -1,8 +1,9 @@
const log = require('electron-log')
const { log } = require('../../common/utils')
const { autoUpdater } = require('electron-updater')
const { mainOn } = require('../../common/icp')
autoUpdater.logger = log
// autoUpdater.autoDownload = false
autoUpdater.logger.transports.file.level = 'info'
log.info('App starting...')
@@ -20,7 +21,7 @@ log.info('App starting...')
function sendStatusToWindow(text) {
log.info(text)
// win.webContents.send('message', text)
// global.mainWindow.webContents.send('message', text)
}
@@ -55,26 +56,50 @@ function sendStatusToWindow(text) {
// })
let waitEvent = []
const handleSendEvent = action => {
if (global.mainWindow) {
setTimeout(() => { // 延迟发送事件,过早发送可能渲染进程还没启动完成
global.mainWindow.webContents.send(action.type, action.info)
}, 2000)
} else {
waitEvent.push(action)
}
}
module.exports = win => {
module.exports = isFirstCheckedUpdate => {
if (!isFirstCheckedUpdate) {
if (waitEvent.length) {
waitEvent.forEach((event, index) => {
setTimeout(() => { // 延迟发送事件,过早发送可能渲染进程还没启动完成
global.mainWindow.webContents.send(event.type, event.info)
}, 2000 * (index + 1))
})
waitEvent = []
}
return
}
autoUpdater.on('checking-for-update', () => {
sendStatusToWindow('Checking for update...')
})
autoUpdater.on('update-available', (ev, info) => {
autoUpdater.on('update-available', info => {
sendStatusToWindow('Update available.')
handleSendEvent({ type: 'update-available', info })
})
autoUpdater.on('update-not-available', (ev, info) => {
autoUpdater.on('update-not-available', info => {
sendStatusToWindow('Update not available.')
handleSendEvent({ type: 'update-not-available' })
})
autoUpdater.on('error', (ev, err) => {
autoUpdater.on('error', () => {
sendStatusToWindow('Error in auto-updater.')
handleSendEvent({ type: 'update-error' })
})
autoUpdater.on('download-progress', (ev, progressObj) => {
autoUpdater.on('download-progress', progressObj => {
sendStatusToWindow('Download progress...')
})
autoUpdater.on('update-downloaded', (ev, info) => {
autoUpdater.on('update-downloaded', info => {
sendStatusToWindow('Update downloaded.')
win.webContents.send('update-downloaded')
handleSendEvent({ type: 'update-downloaded' })
})
mainOn('quit-update', () => {
setTimeout(() => {

View File

@@ -0,0 +1,38 @@
const fs = require('fs')
const flac = require('flac-metadata')
module.exports = (filenPath, meta) => {
const reader = fs.createReadStream(filenPath)
const tempPath = filenPath + '.lxmtemp'
const writer = fs.createWriteStream(tempPath)
const processor = new flac.Processor()
if (meta.APIC) delete meta.APIC
const comments = []
for (const key in meta) {
comments.push(`${key.toUpperCase()}=${meta[key]}`)
}
const vendor = 'lx-music-desktop'
processor.on('preprocess', function(mdb) {
// Remove existing VORBIS_COMMENT block, if any.
if (mdb.type === flac.Processor.MDB_TYPE_VORBIS_COMMENT) {
mdb.remove()
}
// Inject new VORBIS_COMMENT block.
if (mdb.removed || mdb.isLast) {
let mdbVorbis = flac.data.MetaDataBlockVorbisComment.create(mdb.isLast, vendor, comments)
this.push(mdbVorbis.publish())
}
})
reader.pipe(processor).pipe(writer).on('finish', () => {
fs.unlink(filenPath, err => {
if (err) return console.log(err.message)
fs.rename(tempPath, filenPath, err => {
if (err) console.log(err.message)
})
})
})
}

17
src/main/utils/mp3Meta.js Normal file
View File

@@ -0,0 +1,17 @@
const NodeID3 = require('node-id3')
const path = require('path')
const fs = require('fs')
const request = require('request')
const extReg = /^(\.(?:jpe?g|png)).*$/
module.exports = (filePath, meta) => {
if (!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)
})
})
}

View File

@@ -0,0 +1,14 @@
const path = require('path')
const mp3Meta = require('./mp3Meta')
const flacMeta = require('./flacMeta')
exports.setMeta = (filePath, meta) => {
switch (path.extname(filePath)) {
case '.mp3':
mp3Meta(filePath, meta)
break
case '.flac':
flacMeta(filePath, meta)
break
}
}

View File

@@ -1,5 +1,5 @@
<template lang="pug">
#container(v-if="isProd" :class="theme" @mouseenter="enableIgnoreMouseEvents" @mouseleave="dieableIgnoreMouseEvents")
#container(v-if="isProd && !isLinux" :class="theme" @mouseenter="enableIgnoreMouseEvents" @mouseleave="dieableIgnoreMouseEvents")
core-aside#left
#right
core-toolbar#toolbar
@@ -18,30 +18,45 @@
</template>
<script>
import dnscache from 'dnscache'
import { mapMutations, mapGetters, mapActions } from 'vuex'
import { rendererOn } from '../common/icp'
import { isLinux } from '../common/utils'
window.ELECTRON_DISABLE_SECURITY_WARNINGS = process.env.ELECTRON_DISABLE_SECURITY_WARNINGS
const win = require('electron').remote.getCurrentWindow()
const body = document.body
dnscache({
enable: true,
ttl: 21600,
cachesize: 1000,
})
let win
let body
if (!isLinux) {
win = require('electron').remote.getCurrentWindow()
body = document.body
}
export default {
data() {
return {
isProd: process.env.NODE_ENV === 'production',
isLinux,
globalObj: {
apiSource: 'messoer',
apiSource: 'test',
proxy: {},
},
updateTimeout: null,
}
},
computed: {
...mapGetters(['electronStore', 'setting', 'theme', 'version']),
...mapGetters('list', ['defaultList']),
...mapGetters('list', ['defaultList', 'loveList']),
...mapGetters('download', {
downloadList: 'list',
downloadStatus: 'downloadStatus',
}),
},
mounted() {
document.body.classList.add(this.isLinux ? 'noTransparent' : 'transparent')
this.init()
},
watch: {
@@ -58,6 +73,13 @@ export default {
},
deep: true,
},
loveList: {
handler(n) {
// console.log(n)
this.electronStore.set('list.loveList', n)
},
deep: true,
},
downloadList: {
handler(n) {
this.electronStore.set('download.list', n)
@@ -74,33 +96,63 @@ export default {
},
methods: {
...mapActions(['getVersionInfo']),
...mapMutations(['setNewVersion', 'setVersionVisible']),
...mapMutations('list', ['initDefaultList']),
...mapMutations(['setNewVersion', 'setVersionModalVisible']),
...mapMutations('list', ['initList']),
...mapMutations('download', ['updateDownloadList']),
...mapMutations(['setSetting']),
init() {
if (this.isProd) {
if (this.isProd && !isLinux) {
body.addEventListener('mouseenter', this.dieableIgnoreMouseEvents)
body.addEventListener('mouseleave', this.enableIgnoreMouseEvents)
}
rendererOn('update-downloaded', () => {
this.getVersionInfo().then(body => {
this.setNewVersion(body)
this.$nextTick(() => {
this.setVersionVisible(true)
})
rendererOn('update-available', (e, info) => {
// this.showUpdateModal(true)
this.setNewVersion({
version: info.version,
})
})
rendererOn('update-error', () => {
if (!this.updateTimeout) return
this.setVersionModalVisible({ isError: true })
this.clearUpdateTimeout()
this.$nextTick(() => {
this.showUpdateModal()
})
})
rendererOn('update-downloaded', () => {
this.clearUpdateTimeout()
this.setVersionModalVisible({ isError: false })
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,
})
})
// 更新超时定时器
this.updateTimeout = setTimeout(() => {
this.updateTimeout = null
this.setVersionModalVisible({ isError: true })
this.$nextTick(() => {
this.showUpdateModal()
})
}, 180000)
this.initData()
this.globalObj.apiSource = this.setting.apiSource
this.globalObj.proxy = Object.assign({}, this.setting.network.proxy)
window.globalObj = this.globalObj
},
enableIgnoreMouseEvents() {
if (isLinux) return
win.setIgnoreMouseEvents(false)
// console.log('content enable')
},
dieableIgnoreMouseEvents() {
if (isLinux) return
// console.log('content disable')
win.setIgnoreMouseEvents(true, { forward: true })
},
@@ -111,13 +163,8 @@ export default {
},
initPlayList() {
let defaultList = this.electronStore.get('list.defaultList')
// console.log(defaultList)
if (defaultList) {
// defaultList.list.forEach(m => {
// m.typeUrl = {}
// })
this.initDefaultList(defaultList)
}
let loveList = this.electronStore.get('list.loveList')
this.initList({ defaultList, loveList })
},
initDownloadList() {
let downloadList = this.electronStore.get('download.list')
@@ -131,8 +178,28 @@ export default {
this.updateDownloadList(downloadList)
}
},
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
this.$nextTick(() => {
this.setVersionModalVisible({ isShow: true })
})
})
},
clearUpdateTimeout() {
if (!this.updateTimeout) return
clearTimeout(this.updateTimeout)
this.updateTimeout = null
},
},
beforeDestroy() {
this.clearUpdateTimeout()
if (this.isProd) {
body.removeEventListener('mouseenter', this.dieableIgnoreMouseEvents)
body.removeEventListener('mouseleave', this.enableIgnoreMouseEvents)
@@ -146,22 +213,34 @@ export default {
@import './assets/styles/layout.less';
body {
// background-color: #fff;
padding: @shadow-app;
user-select: none;
height: 100vh;
box-sizing: border-box;
}
.transparent {
padding: @shadow-app;
#container {
box-shadow: 0 0 @shadow-app rgba(0, 0, 0, 0.5);
border-radius: 4px;
background-color: transparent;
}
}
.noTransparent {
background-color: #fff;
}
#container {
position: relative;
display: flex;
height: 100%;
box-shadow: 0 0 @shadow-app rgba(0, 0, 0, 0.5);
// background-color: #fff;
border-radius: 4px;
overflow: hidden;
background: @color-theme-bgimg @color-theme-bgposition no-repeat;
background-size: @color-theme-bgsize;
transition: background-color @transition-theme;
background-color: @color-theme;
}
#left {
flex: none;
width: @width-app-left;
@@ -170,6 +249,8 @@ body {
flex: auto;
display: flex;
flex-flow: column nowrap;
transition: background-color @transition-theme;
background-color: @color-theme_2;
}
#toolbar, #player {
flex: none;
@@ -178,5 +259,17 @@ body {
flex: auto;
height: 0;
}
each(@themes, {
#container.@{value} {
background-color: ~'@{color-@{value}-theme}';
background-image: ~'@{color-@{value}-theme-bgimg}';
background-size: ~'@{color-@{value}-theme-bgsize}';
background-position: ~'@{color-@{value}-theme-bgposition}';
#right {
background-color: ~'@{color-@{value}-theme_2}';
}
}
})
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -36,11 +36,11 @@ table {
}
tbody {
tr {
border-top: 1px solid #e0e0e0;
border-top: 1px solid @color-theme_2-line;
// border-top: 1px solid rgba(0, 0, 0, 0.12);
transition: background-color 0.2s ease;
&:hover {
background-color: #eee;
background-color: @color-theme_2-hover;
}
&:first-child {
border-top: none;
@@ -103,6 +103,10 @@ strong {
font-weight: bold;
}
.underline {
text-decoration: underline;
}
svg {
transition: @transition-theme;
transition-property: fill;
@@ -168,5 +172,15 @@ each(@themes, {
background-color: ~'@{color-@{value}-scrollbar-thumb-hover}';
}
}
table {
tbody {
tr {
border-top-color: ~'@{color-@{value}-theme_2-line}';
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
}
}
}
}
})

View File

@@ -1,21 +1,28 @@
@import './colors.less';
@themes: green, yellow, blue, red, purple, orange, grey;
@themes: green, yellow, blue, red, purple, orange, grey, midAutumn, dhHyrz;
// Colors
// @color-theme: #03a678;
@color-theme: #4daf7c;
@color-theme-bgimg: none;
@color-theme-bgposition: center center;
@color-theme-bgsize: auto auto;
@color-theme-hover: fadeout(lighten(@color-theme, 10%), 30%);
@color-theme-active: fadeout(darken(@color-theme, 20%), 60%);
@color-theme-font: #fff;
@color-theme-font-label: lighten(@color-theme, 35%);
@color-theme_2: #fff;
@color-theme_2: rgba(255, 255, 255, .9);
@color-theme_2-background_1: #fff;
@color-theme_2-background_2: fadeout(@color-theme_2-background_1, 2%);
@color-theme_2-hover: fadeout(lighten(@color-theme, 10%), 70%);
@color-theme_2-active: fadeout(darken(@color-theme, 5%), 70%);
@color-theme_2-font: darken(@color-theme_2, 70%);
@color-theme_2-font-label: lighten(@color-theme, 40%);
@color-theme_2-font-label: fadeout(@color-theme_2-font, 50%);
@color-theme_2-line: lighten(@color-theme, 35%);
@color-theme-sidebar: @color-theme;
@color-btn: fadeout(darken(@color-theme, 5%), 15%);
@color-btn-background: fadeout(lighten(@color-theme, 35%), 70%);
@@ -41,6 +48,7 @@
@color-player-status-text: lighten(@color-theme_2-font, 10%);
@color-tab-btn-background: fadeout(lighten(@color-theme, 10%), 80%);
@color-tab-btn-background-hover: @color-theme_2-hover;
@color-tab-border-top: fadeout(lighten(@color-theme, 5%), 50%);
@color-tab-border-bottom: lighten(@color-theme, 5%);
@@ -50,15 +58,22 @@
@color-green-theme: #4daf7c;
@color-green-theme-bgimg: none;
@color-green-theme-bgposition: center 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%);
@color-green-theme-font: #fff;
@color-green-theme-font-label: lighten(@color-green-theme, 35%);
@color-green-theme_2: #fff;
@color-green-theme_2-background_1: #fff;
@color-green-theme_2-background_2: fadeout(@color-green-theme_2-background_1, 2%);
@color-green-theme_2-hover: fadeout(lighten(@color-green-theme, 10%), 70%);
@color-green-theme_2-active: fadeout(darken(@color-green-theme, 5%), 70%);
@color-green-theme_2-font: darken(@color-green-theme_2, 70%);
@color-green-theme_2-font-label: lighten(@color-green-theme, 40%);
@color-green-theme_2-font-label: fadeout(@color-green-theme_2-font, 50%);
@color-green-theme_2-line: lighten(@color-green-theme, 35%);
@color-green-theme-sidebar: @color-green-theme;
@color-green-btn: fadeout(darken(@color-green-theme, 5%), 15%);
@color-green-btn-background: fadeout(lighten(@color-green-theme, 35%), 70%);
@color-green-pagination-background: fadeout(lighten(@color-green-theme, 45%), 30%);
@@ -77,20 +92,28 @@
@color-green-player-progress-bar2: lighten(@color-green-theme, 12%);
@color-green-player-status-text: lighten(@color-green-theme_2-font, 10%);
@color-green-tab-btn-background: fadeout(lighten(@color-green-theme, 10%), 80%);
@color-green-tab-btn-background-hover: @color-green-theme_2-hover;
@color-green-tab-border-top: fadeout(lighten(@color-green-theme, 5%), 50%);
@color-green-tab-border-bottom: lighten(@color-green-theme, 5%);
@color-yellow-theme: #f2d35b;
@color-yellow-theme-bgimg: none;
@color-yellow-theme-bgposition: center 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%);
@color-yellow-theme-font: #fff;
@color-yellow-theme-font-label: lighten(@color-yellow-theme, 35%);
@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-font: darken(@color-yellow-theme_2, 70%);
@color-yellow-theme_2-font-label: lighten(@color-yellow-theme, 40%);
@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-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-pagination-background: fadeout(lighten(@color-yellow-theme, 30%), 30%);
@@ -109,19 +132,27 @@
@color-yellow-player-progress-bar2: lighten(@color-yellow-theme, 12%);
@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-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-orange-theme: #f5ab35;
@color-orange-theme-bgimg: none;
@color-orange-theme-bgposition: center 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%);
@color-orange-theme-font: #fff;
@color-orange-theme-font-label: lighten(@color-orange-theme, 35%);
@color-orange-theme_2: #fff;
@color-orange-theme_2-background_1: #fff;
@color-orange-theme_2-background_2: fadeout(@color-orange-theme_2-background_1, 2%);
@color-orange-theme_2-hover: fadeout(lighten(@color-orange-theme, 10%), 70%);
@color-orange-theme_2-active: fadeout(darken(@color-orange-theme, 5%), 70%);
@color-orange-theme_2-font: darken(@color-orange-theme_2, 70%);
@color-orange-theme_2-font-label: lighten(@color-orange-theme, 40%);
@color-orange-theme_2-font-label: fadeout(@color-orange-theme_2-font, 50%);
@color-orange-theme_2-line: lighten(@color-orange-theme, 35%);
@color-orange-theme-sidebar: @color-orange-theme;
@color-orange-btn: fadeout(darken(@color-orange-theme, 5%), 15%);
@color-orange-btn-background: fadeout(lighten(@color-orange-theme, 35%), 70%);
@color-orange-pagination-background: fadeout(lighten(@color-orange-theme, 35%), 30%);
@@ -140,19 +171,27 @@
@color-orange-player-progress-bar2: lighten(@color-orange-theme, 12%);
@color-orange-player-status-text: lighten(@color-orange-theme_2-font, 10%);
@color-orange-tab-btn-background: fadeout(lighten(@color-orange-theme, 10%), 80%);
@color-orange-tab-btn-background-hover: @color-orange-theme_2-hover;
@color-orange-tab-border-top: fadeout(lighten(@color-orange-theme, 5%), 50%);
@color-orange-tab-border-bottom: lighten(@color-orange-theme, 5%);
@color-blue-theme: #3498db;
@color-blue-theme-bgimg: none;
@color-blue-theme-bgposition: center 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%);
@color-blue-theme-font: #fff;
@color-blue-theme-font-label: lighten(@color-blue-theme, 35%);
@color-blue-theme_2: #fff;
@color-blue-theme_2-background_1: #fff;
@color-blue-theme_2-background_2: fadeout(@color-blue-theme_2-background_1, 2%);
@color-blue-theme_2-hover: fadeout(lighten(@color-blue-theme, 10%), 70%);
@color-blue-theme_2-active: fadeout(darken(@color-blue-theme, 5%), 70%);
@color-blue-theme_2-font: darken(@color-blue-theme_2, 70%);
@color-blue-theme_2-font-label: lighten(@color-blue-theme, 40%);
@color-blue-theme_2-font-label: fadeout(@color-blue-theme_2-font, 50%);
@color-blue-theme_2-line: lighten(@color-blue-theme, 35%);
@color-blue-theme-sidebar: @color-blue-theme;
@color-blue-btn: fadeout(darken(@color-blue-theme, 5%), 15%);
@color-blue-btn-background: fadeout(lighten(@color-blue-theme, 35%), 70%);
@color-blue-pagination-background: fadeout(lighten(@color-blue-theme, 40%), 30%);
@@ -171,10 +210,14 @@
@color-blue-player-progress-bar2: lighten(@color-blue-theme, 12%);
@color-blue-player-status-text: lighten(@color-blue-theme_2-font, 10%);
@color-blue-tab-btn-background: fadeout(lighten(@color-blue-theme, 10%), 80%);
@color-blue-tab-btn-background-hover: @color-blue-theme_2-hover;
@color-blue-tab-border-top: fadeout(lighten(@color-blue-theme, 5%), 50%);
@color-blue-tab-border-bottom: lighten(@color-blue-theme, 5%);
@color-red-theme: #d64541;
@color-red-theme-bgimg: none;
@color-red-theme-bgposition: center 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%);
@color-red-theme-font: #fff;
@@ -182,8 +225,12 @@
@color-red-theme_2: #fff;
@color-red-theme_2-hover: fadeout(lighten(@color-red-theme, 10%), 70%);
@color-red-theme_2-active: fadeout(darken(@color-red-theme, 5%), 70%);
@color-red-theme_2-background_1: #fff;
@color-red-theme_2-background_2: fadeout(@color-red-theme_2-background_1, 2%);
@color-red-theme_2-font: darken(@color-red-theme_2, 70%);
@color-red-theme_2-font-label: lighten(@color-red-theme, 40%);
@color-red-theme_2-font-label: fadeout(@color-red-theme_2-font, 50%);
@color-red-theme_2-line: lighten(@color-red-theme, 35%);
@color-red-theme-sidebar: @color-red-theme;
@color-red-btn: fadeout(darken(@color-red-theme, 5%), 15%);
@color-red-btn-background: fadeout(lighten(@color-red-theme, 35%), 70%);
@color-red-pagination-background: fadeout(lighten(@color-red-theme, 40%), 30%);
@@ -204,19 +251,27 @@
@color-red-tab-border-top: fadeout(lighten(@color-red-theme, 25%), 70%);
@color-red-tab-border-bottom: lighten(@color-red-theme, 35%);
@color-red-tab-btn-background: fadeout(lighten(@color-red-theme, 10%), 80%);
@color-red-tab-btn-background-hover: @color-red-theme_2-hover;
@color-red-tab-border-top: fadeout(lighten(@color-red-theme, 5%), 50%);
@color-red-tab-border-bottom: lighten(@color-red-theme, 5%);
@color-purple-theme: #9b59b6;
@color-purple-theme-bgimg: none;
@color-purple-theme-bgposition: center 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%);
@color-purple-theme-font: #fff;
@color-purple-theme-font-label: lighten(@color-purple-theme, 35%);
@color-purple-theme_2: #fff;
@color-purple-theme_2-background_1: #fff;
@color-purple-theme_2-background_2: fadeout(@color-purple-theme_2-background_1, 2%);
@color-purple-theme_2-hover: fadeout(lighten(@color-purple-theme, 10%), 70%);
@color-purple-theme_2-active: fadeout(darken(@color-purple-theme, 5%), 70%);
@color-purple-theme_2-font: darken(@color-purple-theme_2, 70%);
@color-purple-theme_2-font-label: lighten(@color-purple-theme, 40%);
@color-purple-theme_2-font-label: fadeout(@color-purple-theme_2-font, 50%);
@color-purple-theme_2-line: lighten(@color-purple-theme, 35%);
@color-purple-theme-sidebar: @color-purple-theme;
@color-purple-btn: fadeout(darken(@color-purple-theme, 5%), 15%);
@color-purple-btn-background: fadeout(lighten(@color-purple-theme, 35%), 70%);
@color-purple-pagination-background: fadeout(lighten(@color-purple-theme, 40%), 30%);
@@ -235,19 +290,27 @@
@color-purple-player-progress-bar2: lighten(@color-purple-theme, 12%);
@color-purple-player-status-text: lighten(@color-purple-theme_2-font, 10%);
@color-purple-tab-btn-background: fadeout(lighten(@color-purple-theme, 10%), 80%);
@color-purple-tab-btn-background-hover: @color-purple-theme_2-hover;
@color-purple-tab-border-top: fadeout(lighten(@color-purple-theme, 5%), 50%);
@color-purple-tab-border-bottom: lighten(@color-purple-theme, 5%);
@color-grey-theme: #6c7a89;
@color-grey-theme-bgimg: none;
@color-grey-theme-bgposition: center 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%);
@color-grey-theme-font: #fff;
@color-grey-theme-font-label: lighten(@color-grey-theme, 35%);
@color-grey-theme_2: #fff;
@color-grey-theme_2-background_1: #fff;
@color-grey-theme_2-background_2: fadeout(@color-grey-theme_2-background_1, 2%);
@color-grey-theme_2-hover: fadeout(lighten(@color-grey-theme, 10%), 70%);
@color-grey-theme_2-active: fadeout(darken(@color-grey-theme, 5%), 70%);
@color-grey-theme_2-font: darken(@color-grey-theme_2, 70%);
@color-grey-theme_2-font-label: lighten(@color-grey-theme, 40%);
@color-grey-theme_2-font-label: fadeout(@color-grey-theme_2-font, 50%);
@color-grey-theme_2-line: lighten(@color-grey-theme, 35%);
@color-grey-theme-sidebar: @color-grey-theme;
@color-grey-btn: fadeout(darken(@color-grey-theme, 5%), 15%);
@color-grey-btn-background: fadeout(lighten(@color-grey-theme, 35%), 70%);
@color-grey-pagination-background: fadeout(lighten(@color-grey-theme, 45%), 30%);
@@ -266,9 +329,88 @@
@color-grey-player-progress-bar2: lighten(@color-grey-theme, 12%);
@color-grey-player-status-text: lighten(@color-grey-theme_2-font, 10%);
@color-grey-tab-btn-background: fadeout(lighten(@color-grey-theme, 10%), 80%);
@color-grey-tab-btn-background-hover: @color-grey-theme_2-hover;
@color-grey-tab-border-top: fadeout(lighten(@color-grey-theme, 5%), 50%);
@color-grey-tab-border-bottom: lighten(@color-grey-theme, 5%);
@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-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%);
@color-midAutumn-theme-font: rgba(246, 233, 255, 0.9);
@color-midAutumn-theme-font-label: fadeout(lighten(@color-midAutumn-theme, 20%), 20%);
@color-midAutumn-theme_2: rgba(255, 255, 255, .93);
@color-midAutumn-theme_2-background_1: #eeedef;
@color-midAutumn-theme_2-background_2: fadeout(@color-midAutumn-theme_2-background_1, 2%);
@color-midAutumn-theme_2-hover: fadeout(lighten(@color-midAutumn-theme, 10%), 65%);
@color-midAutumn-theme_2-active: fadeout(darken(@color-midAutumn-theme, 5%), 70%);
@color-midAutumn-theme_2-font: darken(@color-midAutumn-theme_2, 70%);
@color-midAutumn-theme_2-font-label: lighten(@color-midAutumn-theme, 40%);
@color-midAutumn-theme_2-line: lighten(@color-midAutumn-theme, 54%);
@color-midAutumn-theme-sidebar: rgba(255, 255, 255, 0);
@color-midAutumn-btn: fadeout(darken(@color-midAutumn-theme, 5%), 15%);
@color-midAutumn-btn-background: fadeout(lighten(@color-midAutumn-theme, 35%), 70%);
@color-midAutumn-pagination-background: fadeout(lighten(@color-midAutumn-theme, 45%), 50%);
@color-midAutumn-pagination-hover: fadeout(lighten(@color-midAutumn-theme, 10%), 70%);
@color-midAutumn-pagination-active: fadeout(darken(@color-midAutumn-theme, 10%), 70%);
@color-midAutumn-pagination-select: fadeout(lighten(@color-midAutumn-theme, 10%), 55%);
@color-midAutumn-search-form-background: fadeout(lighten(@color-midAutumn-theme, 50%), 10%);
@color-midAutumn-search-list-hover: fadeout(darken(@color-midAutumn-theme, 10%), 70%);
@color-midAutumn-scrollbar-track: fadeout(@color-midAutumn-theme, 80%);
@color-midAutumn-scrollbar-thumb: fadeout(@color-midAutumn-theme, 60%);
@color-midAutumn-scrollbar-thumb-hover: fadeout(@color-midAutumn-theme, 40%);
@color-midAutumn-player-pic-c1: fadeout(@color-midAutumn-theme_2, 50%);
@color-midAutumn-player-pic-c2: darken(@color-midAutumn-theme_2, 30%);
@color-midAutumn-player-progress: darken(@color-midAutumn-theme_2, 6%);
@color-midAutumn-player-progress-bar1: darken(@color-midAutumn-theme_2, 12%);
@color-midAutumn-player-progress-bar2: lighten(@color-midAutumn-theme, 12%);
@color-midAutumn-player-status-text: lighten(@color-midAutumn-theme_2-font, 10%);
@color-midAutumn-tab-btn-background: fadeout(lighten(@color-midAutumn-theme, 10%), 80%);
@color-midAutumn-tab-btn-background-hover: @color-midAutumn-theme_2-hover;
@color-midAutumn-tab-border-top: fadeout(lighten(@color-midAutumn-theme, 5%), 50%);
@color-midAutumn-tab-border-bottom: lighten(@color-midAutumn-theme, 5%);
@color-dhHyrz-theme: rgb(87, 144, 167);
@color-dhHyrz-theme-bgimg: url(../images/hzwbg.jpeg);
@color-dhHyrz-theme-bgposition: center 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%);
@color-dhHyrz-theme-font: rgba(67, 118, 148, 0.9);
@color-dhHyrz-theme-font-label: fadeout(darken(@color-dhHyrz-theme, 20%), 20%);
@color-dhHyrz-theme_2: rgba(255, 255, 255, 0.8);
@color-dhHyrz-theme_2-background_1: #e9faff;
@color-dhHyrz-theme_2-background_2: fadeout(@color-dhHyrz-theme_2-background_1, 2%);
@color-dhHyrz-theme_2-hover: fadeout(lighten(@color-dhHyrz-theme, 10%), 65%);
@color-dhHyrz-theme_2-active: fadeout(darken(@color-dhHyrz-theme, 5%), 70%);
@color-dhHyrz-theme_2-font: darken(@color-dhHyrz-theme_2, 80%);
@color-dhHyrz-theme_2-font-label: lighten(@color-dhHyrz-theme, 10%);
@color-dhHyrz-theme_2-line: lighten(@color-dhHyrz-theme, 36%);
@color-dhHyrz-theme-sidebar: rgba(255, 255, 255, .3);
@color-dhHyrz-btn: fadeout(darken(@color-dhHyrz-theme, 5%), 15%);
@color-dhHyrz-btn-background: fadeout(lighten(@color-dhHyrz-theme, 35%), 70%);
@color-dhHyrz-pagination-background: fadeout(lighten(@color-dhHyrz-theme, 45%), 50%);
@color-dhHyrz-pagination-hover: fadeout(lighten(@color-dhHyrz-theme, 10%), 70%);
@color-dhHyrz-pagination-active: fadeout(darken(@color-dhHyrz-theme, 10%), 70%);
@color-dhHyrz-pagination-select: fadeout(lighten(@color-dhHyrz-theme, 10%), 55%);
@color-dhHyrz-search-form-background: fadeout(lighten(@color-dhHyrz-theme, 30%), 20%);
@color-dhHyrz-search-list-hover: fadeout(darken(@color-dhHyrz-theme, 10%), 50%);
@color-dhHyrz-scrollbar-track: fadeout(@color-dhHyrz-theme, 80%);
@color-dhHyrz-scrollbar-thumb: fadeout(@color-dhHyrz-theme, 60%);
@color-dhHyrz-scrollbar-thumb-hover: fadeout(@color-dhHyrz-theme, 40%);
@color-dhHyrz-player-pic-c1: fadeout(@color-dhHyrz-theme_2, 50%);
@color-dhHyrz-player-pic-c2: darken(@color-dhHyrz-theme_2, 30%);
@color-dhHyrz-player-progress: darken(@color-dhHyrz-theme_2, 6%);
@color-dhHyrz-player-progress-bar1: darken(@color-dhHyrz-theme_2, 12%);
@color-dhHyrz-player-progress-bar2: lighten(@color-dhHyrz-theme, 12%);
@color-dhHyrz-player-status-text: lighten(@color-dhHyrz-theme_2-font, 10%);
@color-dhHyrz-tab-btn-background: fadeout(lighten(@color-dhHyrz-theme, 10%), 80%);
@color-dhHyrz-tab-btn-background-hover: @color-dhHyrz-theme_2-hover;
@color-dhHyrz-tab-border-top: fadeout(lighten(@color-dhHyrz-theme, 5%), 50%);
@color-dhHyrz-tab-border-bottom: lighten(@color-dhHyrz-theme, 5%);
// Width
@width-app-left: 180px;

View File

@@ -9,15 +9,16 @@ div(:class="$style.aside")
dt 在线音乐
dd
router-link(:active-class="$style.active" to="search") 搜索
dd
router-link(:active-class="$style.active" to="songList") 歌单
dd
router-link(:active-class="$style.active" to="leaderboard") 排行榜
//- dd
router-link(:active-class="$style.active" to="recommend") 歌单
dl
dt 我的音乐
dd
router-link(:active-class="$style.active" to="list") {{defaultList.name}}
router-link(:active-class="$style.active" v-for="item in userList" :to="`list?id=${item._id}`" :key="item._id") {{item.name}}
router-link(:active-class="($route.query.id === defaultList.id || $route.query.id == '') ? $style.active : ''" :to="`list?id=${defaultList.id || ''}`") {{defaultList.name}}
router-link(:active-class="$route.query.id === loveList.id ? $style.active : ''" :to="`list?id=${loveList.id}`") {{loveList.name}}
router-link(:active-class="$route.query.id === item.id ? $style.active : ''" v-for="item in userList" :to="`list?id=${item._id}`" :key="item._id") {{item.name}}
dl
dt 其他
dd
@@ -43,7 +44,7 @@ export default {
}
},
computed: {
...mapGetters('list', ['defaultList', 'userList']),
...mapGetters('list', ['defaultList', 'loveList', 'userList']),
},
methods: {},
}
@@ -57,7 +58,7 @@ export default {
// box-shadow: 0 0 5px rgba(0, 0, 0, .3);
transition: @transition-theme;
transition-property: background-color;
background-color: @color-theme;
background-color: @color-theme-sidebar;
// background-color: @color-aside-background;
// border-right: 2px solid @color-theme;
-webkit-app-region: drag;
@@ -122,7 +123,7 @@ export default {
each(@themes, {
:global(#container.@{value}) {
.aside {
background-color: ~'@{color-@{value}-theme}';
background-color: ~'@{color-@{value}-theme-sidebar}';
}
.logo {
color: ~'@{color-@{value}-theme-font}';

View File

@@ -48,5 +48,26 @@ svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/19
path(fill='currentColor' d='M112.525,95.091L26.75,45.901C11.982,37.427,0,44.369,0,61.404v98.062c0,17.025,11.982,23.969,26.75,15.492l85.781-49.177C127.294,117.305,127.294,103.565,112.525,95.091z')
// 205.857 205.857
//- path(fill='currentColor' d='M174.522,0h-26.848c-9.885,0-17.897,8.013-17.897,17.899v62.533L37.513,2.522c-3.483-2.406-7.807-2.005-11.072-2.005c-13.061,0-13.004,11.7-13.004,14.666v175.983c0,2.507-0.057,14.666,13.004,14.666c3.265,0,7.589,0.401,11.072-2.005l92.265-77.91v62.016c0,9.885,8.012,17.898,17.897,17.898h26.848c9.885,0,17.898-8.013,17.898-17.898V17.899C192.421,8.013,184.408,0,174.522,0z')
g#icon-sound
// 0 0 291.063 291.064
path(fill='currentColor' d='M26.512,204.255h18.292l106.48,67.761c12.354,7.855,22.369,2.361,22.369-12.282v-69.397c16.933-8.854,28.501-26.559,28.501-46.983c0-20.425-11.568-38.129-28.501-46.986V31.645c0-14.639-10.18-20.401-22.731-12.873L44.804,82.443H26.512C11.866,82.443,0,94.311,0,108.955v68.789C0,192.387,11.866,204.255,26.512,204.255z')
path(fill='currentColor' d='M219.791,152.899c-0.818,11.185-4.039,21.758-9.569,31.426c-3.635,6.354-1.43,14.452,4.919,18.087c2.082,1.187,4.34,1.751,6.576,1.751c4.599,0,9.062-2.393,11.517-6.675c7.508-13.138,11.889-27.491,12.986-42.663c1.714-23.397-4.836-46.781-18.455-65.845c-4.256-5.96-12.536-7.332-18.491-3.081c-5.959,4.259-7.337,12.531-3.08,18.491C216.218,118.425,221.055,135.653,219.791,152.899z')
path(fill='currentColor' d='M290.7,158c3.34-45.736-16.508-89.592-53.097-117.318c-5.841-4.433-14.146-3.27-18.568,2.556c-4.428,5.838-3.283,14.151,2.558,18.568c29.401,22.281,45.355,57.521,42.668,94.252c-2.02,27.636-14.375,53.159-34.787,71.867c-5.396,4.95-5.758,13.339-0.808,18.729c2.609,2.854,6.188,4.298,9.771,4.298c3.194,0,6.41-1.154,8.953-3.484C272.805,224.175,288.184,192.408,290.7,158z')
g#icon-sdCard
// 0 0 291.13 291.13
path(fill='currentColor' d="M 50.981 282.415 c 1.722 -2.372 5.494 -4.293 8.419 -4.293 h 172.331 c 2.92 0 6.695 1.921 8.419 4.293 l 6.339 8.715 h 41.74 V 18.538 C 288.23 8.298 279.929 0 269.698 0 H 48.988 L 2.9 49.632 V 291.13 h 41.744 L 50.981 282.415 Z M 240.348 27.418 c 0 -2.926 2.371 -5.302 5.302 -5.302 h 17.668 c 2.931 0 5.303 2.376 5.303 5.302 v 50.373 c 0 2.928 -2.372 5.302 -5.303 5.302 H 245.65 c -2.931 0 -5.302 -2.375 -5.302 -5.302 V 27.418 Z M 197.929 27.418 c 0 -2.926 2.371 -5.302 5.302 -5.302 h 17.668 c 2.931 0 5.303 2.376 5.303 5.302 v 50.373 c 0 2.928 -2.372 5.302 -5.303 5.302 h -17.668 c -2.931 0 -5.302 -2.375 -5.302 -5.302 V 27.418 Z M 155.509 27.418 c 0 -2.926 2.372 -5.302 5.303 -5.302 h 17.668 c 2.931 0 5.303 2.376 5.303 5.302 v 50.373 c 0 2.928 -2.372 5.302 -5.303 5.302 h -17.668 c -2.931 0 -5.303 -2.375 -5.303 -5.302 V 27.418 Z M 113.088 27.418 c 0 -2.926 2.376 -5.302 5.302 -5.302 h 17.673 c 2.926 0 5.303 2.376 5.303 5.302 v 50.373 c 0 2.928 -2.377 5.302 -5.303 5.302 H 118.39 c -2.926 0 -5.302 -2.375 -5.302 -5.302 V 27.418 Z M 70.668 27.418 c 0 -2.926 2.377 -5.302 5.303 -5.302 h 17.673 c 2.926 0 5.302 2.376 5.302 5.302 v 50.373 c 0 2.928 -2.376 5.302 -5.302 5.302 H 75.971 c -2.926 0 -5.303 -2.375 -5.303 -5.302 V 27.418 Z M 28.25 104.303 V 53.93 c 0 -2.926 2.377 -5.302 5.303 -5.302 h 17.673 c 2.925 0 5.302 2.376 5.302 5.302 v 50.373 c 0 2.928 -2.377 5.303 -5.302 5.303 H 33.552 C 30.626 109.605 28.25 107.231 28.25 104.303 Z")
g#icon-musicFolder
// 0 0 247.498 247.498
path(fill='currentColor' d="M 0 200.188 c 0 14.645 11.871 26.513 26.512 26.513 h 194.475 c 14.639 0 26.512 -11.868 26.512 -26.513 V 86.192 c 0 -14.641 -11.873 -26.512 -26.512 -26.512 H 0 V 200.188 Z M 85.553 156.537 c 4.137 -0.869 8.2 -0.678 11.788 0.373 v -43.856 c 0 -3 2.11 -5.605 5.049 -6.224 l 53.047 -11.19 l 0.011 -0.005 c 0.196 -0.041 0.414 -0.041 0.621 -0.062 c 0.228 -0.021 0.445 -0.07 0.673 -0.07 c 0 0 0 0 0.011 0 c 0.093 0 0.171 0.021 0.259 0.025 c 0.331 0.011 0.663 0.034 0.984 0.102 c 0.201 0.041 0.383 0.111 0.579 0.171 c 0.197 0.062 0.404 0.106 0.601 0.184 c 0.207 0.091 0.404 0.207 0.606 0.318 c 0.155 0.085 0.315 0.155 0.471 0.256 c 0.182 0.117 0.337 0.269 0.508 0.407 c 0.155 0.121 0.311 0.228 0.445 0.362 c 0.146 0.143 0.27 0.311 0.404 0.471 c 0.124 0.153 0.269 0.295 0.383 0.471 c 0.135 0.197 0.238 0.414 0.342 0.621 c 0.088 0.153 0.176 0.295 0.254 0.456 c 0.14 0.323 0.238 0.668 0.326 1.012 c 0.01 0.065 0.041 0.122 0.062 0.186 v 0.006 c 0.042 0.223 0.052 0.46 0.073 0.688 c 0.021 0.202 0.062 0.404 0.062 0.604 c 0 0.005 0 0.005 0 0.01 v 58.148 c 0 0.367 -0.041 0.74 -0.113 1.103 c -0.135 8.196 -7.208 15.866 -17.233 17.999 c -11.319 2.413 -22.113 -3.2 -24.096 -12.521 c -1.983 -9.331 5.6 -18.838 16.93 -21.251 c 4.137 -0.87 8.202 -0.674 11.785 0.373 v -36.001 l -40.317 8.502 v 52.985 c 0 0.373 -0.042 0.74 -0.106 1.098 c -0.143 8.202 -7.216 15.876 -17.233 18.01 c -11.33 2.407 -22.121 -3.2 -24.104 -12.521 C 66.64 168.452 74.223 158.945 85.553 156.537 Z")
g#icon-musicFile
// -61 0 512 512
path(fill='currentColor' d='m295 120.5h86.230469l-111.230469-111.695312v86.53125c0 13.875 11.214844 25.164062 25 25.164062zm0 0')
path(fill='currentColor' d='m240 346.5c0 8.269531 6.730469 15 15 15s15-6.730469 15-15v-15.25h-15c-7.960938 0-15 6.324219-15 15.25zm0 0')
path(fill='currentColor' d='m295 150.5c-30.328125 0-55-24.746094-55-55.167969v-95.332031h-185c-30.328125 0-55 24.746094-55 55.167969v401.667969c0 30.417968 24.671875 55.164062 55 55.164062h280c30.328125 0 55-24.746094 55-55.167969v-306.332031zm5 196c0 24.8125-20.1875 45-45 45s-45-20.1875-45-45c0-25.507812 20.53125-45.25 45-45.25h15v-56.144531l-90 22.59375v108.925781c0 24.8125-20.1875 45-45 45s-45-20.1875-45-45c0-25.507812 20.53125-45.25 45-45.25h15v-75.375c0-6.878906 4.675781-12.875 11.347656-14.546875l120-30.125c9.46875-2.382813 18.652344 4.796875 18.652344 14.546875zm0 0')
path(fill='currentColor' d='m120 376.625c0 8.269531 6.730469 15 15 15s15-6.730469 15-15v-15.25h-15c-7.960938 0-15 6.324219-15 15.25zm0 0')
g#icon-down
// 0 0 451.847 451.847
path(fill='currentColor' d='M225.923,354.706c-8.098,0-16.195-3.092-22.369-9.263L9.27,151.157c-12.359-12.359-12.359-32.397,0-44.751c12.354-12.354,32.388-12.354,44.748,0l171.905,171.915l171.906-171.909c12.359-12.354,32.391-12.354,44.744,0c12.365,12.354,12.365,32.392,0,44.751L248.292,345.449C242.115,351.621,234.018,354.706,225.923,354.706z')
</template>

View File

@@ -7,7 +7,15 @@ div(:class="$style.player")
div(:class="$style.right")
div(:class="$style.column1")
div(:class="$style.container")
div(:class="$style.title") {{title}}
div(:class="$style.title" @click="handleCopy(title)" :title="title + '(点击复制)'") {{title}}
div(:class="$style.volumeContent")
div(:class="$style.volume")
div(:class="$style.volumeBar" :style="{ transform: `scaleX(${volume || 0})` }")
div(:class="$style.volumeMask" @mousedown="handleVolumeMsDown" ref="dom_volumeMask" :title="`当前音量:${parseInt(volume * 100)}%`")
//- div(:class="$style.playBtn" @click='handleNext' title="音量")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 291.063 291.064' space='preserve')
use(xlink:href='#icon-sound')
div(:class="$style.playBtn" @click='handleNext' title="下一首")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
use(xlink:href='#icon-nextMusic')
@@ -16,11 +24,11 @@ div(:class="$style.player")
use(xlink:href='#icon-pause')
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 170 170' space='preserve')
use(xlink:href='#icon-play')
div(:class="$style.column2" @click='setProgess' ref="dom_progress")
div(:class="$style.column2")
div(:class="$style.progress")
//- div(:class="[$style.progressBar, $style.progressBar1]" :style="{ width: progress + '%' }")
div(:class="[$style.progressBar, $style.progressBar2]" :style="{ width: (progress * 100 || 0) + '%' }")
//- 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.progressMask" @click='setProgess' ref="dom_progress")
div(:class="$style.column3")
span {{nowPlayTimeStr}}
span(:class="$style.statusText") {{status}}
@@ -41,15 +49,17 @@ div(:class="$style.player")
<script>
import Lyric from 'lrc-file-parser'
import { rendererSend } from '../../../common/icp'
import { formatPlayTime2, getRandom, checkPath } from '../../utils'
import { formatPlayTime2, getRandom, checkPath, setTitle, clipboardWriteText, debounce } from '../../utils'
import { mapGetters, mapActions, mapMutations } from 'vuex'
import { requestMsg } from '../../utils/message'
import { isMac } from '../../../common/utils'
export default {
data() {
return {
show: true,
audio: null,
volume: 0,
nowPlayTime: 0,
maxPlayTime: 0,
isPlay: false,
@@ -71,7 +81,15 @@ export default {
},
delayNextTimeout: null,
audioErrorTime: 0,
// retryNum: 0,
retryNum: 0,
isMac,
volumeEvent: {
isMsDown: false,
msDownX: 0,
msDownVolume: 0,
},
handleVolumeMsMoveFn: null,
handleVolumeMsUpFn: null,
}
},
computed: {
@@ -92,7 +110,6 @@ export default {
return this.maxPlayTime ? formatPlayTime2(this.maxPlayTime) : '00:00'
},
progress() {
// return 50
return this.nowPlayTime / this.maxPlayTime || 0
},
isAPITemp() {
@@ -100,8 +117,20 @@ export default {
},
},
mounted() {
this.setProgessWidth()
this.init()
this.$nextTick(() => {
this.setProgessWidth()
})
this.handleSaveVolume = debounce(volume => {
this.setVolume(volume)
}, 300)
document.addEventListener('mousemove', this.handleVolumeMsMoveFn = this.handleVolumeMsMove.bind(this))
document.addEventListener('mouseup', this.handleVolumeMsUpFn = this.handleVolumeMsUp.bind(this))
},
beforeDestroy() {
document.removeEventListener('mousemove', this.handleVolumeMsMoveFn)
document.removeEventListener('mouseup', this.handleVolumeMsUpFn)
},
watch: {
changePlay(n) {
@@ -139,6 +168,9 @@ export default {
if (n.toFixed(2) === o.toFixed(2)) return
this.sendProgressEvent(n, 'normal')
},
volume(n) {
this.handleSaveVolume(n)
},
},
methods: {
...mapActions('player', ['getUrl', 'getPic', 'getLrc']),
@@ -147,11 +179,14 @@ export default {
'fixPlayIndex',
'resetChangePlay',
]),
...mapMutations(['setVolume']),
...mapMutations('list', ['updateMusicInfo']),
init() {
this.audio = document.createElement('audio')
this.volume = this.audio.volume = this.setting.player.volume
this.audio.controls = false
this.audio.autoplay = true
this.audio.preload = 'auto'
this.audio.loop = this.setting.player.togglePlayMethod === 'singleLoop'
this.audio.addEventListener('playing', () => {
@@ -175,31 +210,17 @@ export default {
// console.log('code', this.audio.error.code)
if (!this.musicInfo.songmid) return
console.log('出错')
if (this.audio.error.code !== 1 && this.retryNum < 3) { // 若音频URL无效则尝试刷新3次URL
this.stopPlay()
if (this.listId != 'download' && this.audio.error.code !== 1 && this.retryNum < 3) { // 若音频URL无效则尝试刷新3次URL
// console.log(this.retryNum)
this.audioErrorTime = this.audio.currentTime // 记录出错的播放时间
this.retryNum++
this.setUrl(this.list[this.playIndex], true)
this.status = 'URL过期正在刷新URL...'
return
}
this.stopPlay()
this.sendProgressEvent(this.progress, 'error')
// let urls = this.player_info.targetSong.urls
// if (urls && urls.some((url, index) => {
// if (this.musicInfo.musicUrl.includes(url)) {
// let newUrl = urls[index + 1]
// if (!newUrl) return false
// this.musicInfo.musicUrl = this.musicInfo.musicUrl.replace(url, newUrl)
// // this.musicInfo.musicUrl = newUrl ? this.musicInfo.musicUrl.replace(url, newUrl) : this.setFormTag(this.musicInfo.musicUrl.replace(url, urls[0]))
// return true
// }
// })) {
// this.audio.src = this.musicInfo.musicUrl
// // console.log(this.musicInfo.musicUrl)
// } else {
// this.handleNext()
// }
this.sendProgressEvent(this.progress, 'error')
this.status = '音频加载出错5 秒后切换下一首'
this.addDelayNextTimeout()
})
@@ -209,12 +230,12 @@ export default {
this.audio.currentTime = this.audioErrorTime
this.audioErrorTime = 0
}
if (!this.targetSong.interval && this.listId != 'download') this.updateMusicInfo({ index: this.playIndex, data: { interval: formatPlayTime2(this.maxPlayTime) } })
if (!this.targetSong.interval && this.listId != 'download') this.updateMusicInfo({ id: 'default', index: this.playIndex, data: { interval: formatPlayTime2(this.maxPlayTime) } })
this.status = '音乐加载中...'
})
this.audio.addEventListener('loadstart', () => {
this.status = '音乐加载中...'
})
// this.audio.addEventListener('loadstart', () => {
// this.status = '开始加载音乐信息...'
// })
this.audio.addEventListener('canplay', () => {
console.log('加载完成开始播放')
// if (this.musicInfo.lrc) this.lyric.lrc.play(this.audio.currentTime * 1000)
@@ -258,6 +279,7 @@ export default {
this.audioErrorTime = 0
if (this.listId == 'download') {
console.log(targetSong.filePath)
if (!checkPath(targetSong.filePath) || !targetSong.isComplate || /\.ape$/.test(targetSong.filePath)) {
return this.list.length == 1 ? null : this.handleNext()
}
@@ -278,7 +300,7 @@ export default {
}
},
checkDelayNextTimeout() {
console.log(this.delayNextTimeout)
// console.log(this.delayNextTimeout)
if (this.delayNextTimeout) {
clearTimeout(this.delayNextTimeout)
this.delayNextTimeout = null
@@ -334,16 +356,17 @@ export default {
startPlay() {
this.isPlay = true
if (this.musicInfo.lrc) this.lyric.lrc.play(this.audio.currentTime * 1000)
this.setAppName()
this.setAppTitle()
this.sendProgressEvent(this.progress, 'normal')
},
stopPlay() {
this.isPlay = false
this.lyric.lrc.pause()
this.sendProgressEvent(this.progress, 'paused')
this.clearAppName()
this.clearAppTitle()
},
setProgess(e) {
if (!this.audio.src) return
this.audio.currentTime =
(e.offsetX / this.pregessWidth) * this.maxPlayTime
if (!this.isPlay) this.audio.play()
@@ -364,10 +387,11 @@ export default {
getPlayType(highQuality, songInfo) {
switch (songInfo.source) {
case 'wy':
case 'kg':
case 'tx':
// case 'kg':
return '128k'
}
let type = songInfo._types['192k'] ? '192k' : '128k'
let type = '128k'
if (highQuality && songInfo._types['320k']) type = '320k'
return type
},
@@ -395,12 +419,12 @@ export default {
}
},
setLrc(targetSong) {
this.musicInfo.lrc = targetSong.lyric
this.musicInfo.lrc = targetSong.lrc
let lrcP = this.musicInfo.lrc
? Promise.resolve()
: this.getLrc(targetSong).then(() => {
this.musicInfo.lrc = targetSong.lyric
this.musicInfo.lrc = targetSong.lrc
})
lrcP
@@ -408,8 +432,8 @@ export default {
this.lyric.lrc.setLyric(this.musicInfo.lrc)
if (this.isPlay && (this.musicInfo.url || this.listId == 'download')) this.lyric.lrc.play(this.audio.currentTime * 1000)
})
.catch(err => {
this.status = err.message
.catch(() => {
this.status = '歌词获取失败'
})
},
handleRemoveMusic() {
@@ -427,18 +451,43 @@ export default {
},
sendProgressEvent(status, mode) {
// console.log(status)
rendererSend('progress', {
this.setting.player.isShowTaskProgess && rendererSend('progress', {
status: status < 0.01 ? 0.01 : status,
mode: mode || 'normal',
})
},
setAppName() {
// rendererSend('appName', {
// name: `${this.musicInfo.name} - ${this.musicInfo.singer}`,
// })
setAppTitle() {
setTitle(`${this.musicInfo.name} - ${this.musicInfo.singer}`)
},
clearAppName() {
// rendererSend('appName')
clearAppTitle() {
setTitle()
},
handleVolumeMsDown(e) {
this.volumeEvent.isMsDown = true
this.volumeEvent.isMsMoved = false
this.volumeEvent.msDownX = e.clientX
let val = e.offsetX / 70
if (val < 0) val = 0
if (val > 1) val = 1
this.volume = val
this.volumeEvent.msDownVolume = val
// console.log(val)
if (this.audio) this.audio.volume = this.volume
},
handleVolumeMsUp(e) {
this.volumeEvent.isMsDown = false
},
handleVolumeMsMove(e) {
if (!this.volumeEvent.isMsDown) return
let val = this.volumeEvent.msDownVolume + (e.clientX - this.volumeEvent.msDownX) / 70
this.volume = val < 0 ? 0 : val > 1 ? 1 : val
if (this.audio) this.audio.volume = this.volume
// console.log(val)
},
handleCopy(text) {
clipboardWriteText(text)
},
},
}
@@ -452,8 +501,7 @@ export default {
height: @height-player;
// background-color: rgb(245, 245, 245);
transition: @transition-theme;
transition-property: background-color, border-color;
background-color: @color-theme_2;
transition-property: border-color;
border-top: 2px solid @color-theme;
box-sizing: border-box;
display: flex;
@@ -468,6 +516,7 @@ export default {
transition: @transition-theme;
transition-property: color;
flex: none;
padding: 2px;
svg {
fill: currentColor;
@@ -477,7 +526,7 @@ export default {
max-height: 100%;
transition: @transition-theme;
transition-property: border-color;
border: 2px solid @color-theme_2;
// border: 2px solid @color-theme_2-background_1;
}
}
.right {
@@ -511,6 +560,59 @@ export default {
.mixin-ellipsis-1;
}
.volume-content {
position: relative;
width: 100px;
display: flex;
align-items: center;
padding: 0 15px;
opacity: .5;
transition: opacity @transition-theme;
&:hover {
opacity: 1;
}
}
.volume {
// cursor: pointer;
width: 100%;
height: 0.25em;
border-radius: 10px;
// overflow: hidden;
transition: @transition-theme;
transition-property: background-color;
background-color: @color-player-progress;
// background-color: #f5f5f5;
position: relative;
border-radius: @radius-progress-border;
}
.volume-bar {
position: absolute;
left: 0;
top: 0;
transform: scaleX(0);
transform-origin: 0;
transition-property: transform;
transition-timing-function: ease;
width: 100%;
height: 100%;
border-radius: @radius-progress-border;
transition-duration: 0.2s;
background-color: @color-theme;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.2);
}
.volume-mask {
position: absolute;
left: 15px;
right: 15px;
top: 0;
width: 70%;
height: 100%;
cursor: pointer;
}
.play-btn {
+ .play-btn {
margin-left: 10px;
@@ -523,7 +625,7 @@ export default {
transition: @transition-theme;
transition-property: color;
color: @color-theme;
transition: opacity 0.2s ease;
transition: opacity 0.1s ease;
opacity: 1;
cursor: pointer;
@@ -539,7 +641,7 @@ export default {
.column2 {
flex: none;
padding: 3px 0;
cursor: pointer;
position: relative;
}
.progress {
@@ -554,12 +656,23 @@ export default {
position: relative;
border-radius: @radius-progress-border;
}
.progress-mask {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
.progress-bar {
position: absolute;
left: 0;
top: 0;
width: 0;
width: 100%;
height: 100%;
transform-origin: 0;
transition-property: transform;
transition-timing-function: ease-out;
border-radius: @radius-progress-border;
}
.progress-bar1 {
@@ -616,14 +729,13 @@ export default {
each(@themes, {
:global(#container.@{value}) {
.player {
background-color: ~'@{color-@{value}-theme_2}';
border-top-color: ~'@{color-@{value}-theme}';
}
.left {
color: ~'@{color-@{value}-theme}';
img {
border-color: ~'@{color-@{value}-theme_2}';
}
// img {
// border-color: ~'@{color-@{value}-theme_2-background_1}';
// }
}
.play-btn {
color: ~'@{color-@{value}-theme}';
@@ -631,6 +743,14 @@ each(@themes, {
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3));
}
}
.volume {
background-color: ~'@{color-@{value}-player-progress}';
}
.volume-bar {
background-color: ~'@{color-@{value}-theme}';
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
}
.progress {

View File

@@ -2,7 +2,9 @@
div(:class="$style.toolbar")
//- img(v-if="icon")
//- h1 {{title}}
material-search-input(:class="$style.input")
material-search-input(:class="$style.input"
@event="handleEvent" :list="tipList" :visibleList="visibleList"
v-model="searchText")
div(:class="$style.control")
button(type="button" :class="$style.min" title="最小化" @click="min")
//- button(type="button" :class="$style.max" @click="max")
@@ -11,22 +13,93 @@
<script>
import { rendererSend } from 'common/icp'
// import { mapGetters } from 'vuex'
import { mapGetters } from 'vuex'
import music from '../../utils/music'
import { debounce } from '../../utils'
export default {
// props: {
// color: {
// type: String,
// default: color,
// },
// icon: {
// type: Boolean,
// default: false,
// },
// },
data() {
return {
searchText: '',
visibleList: false,
tipList: [],
tipSearch: null,
}
},
computed: {
// ...mapGetters(['theme']),
...mapGetters(['route', 'setting']),
...mapGetters('search', {
storeSearchText: 'searchText',
}),
source() {
return this.setting.search.tempSearchSource
},
isAutoClearInput() {
return this.setting.odc.isAutoClearSearchInput
},
},
watch: {
route(n) {
if (this.isAutoClearInput && n.name != 'search' && this.searchText) this.searchText = ''
},
'storeSearchText'(n) {
if (n !== this.searchText) this.searchText = n
},
searchText(n) {
this.handleTipSearch()
},
},
mounted() {
this.tipSearch = debounce(() => {
if (this.searchText === '') {
this.tipList.splice(0, this.tipList.length)
music[this.source].tempSearch.cancelTempSearch()
return
}
music[this.source].tempSearch.search(this.searchText).then(list => {
this.tipList = list
}).catch(() => {})
}, 50)
},
methods: {
handleEvent({ action, data }) {
switch (action) {
case 'focus':
if (!this.visibleList) this.visibleList = true
if (this.searchText) this.handleTipSearch()
break
case 'blur':
setTimeout(() => {
if (this.visibleList) this.visibleList = false
}, 50)
break
case 'submit':
this.handleSearch()
break
case 'listClick':
this.searchText = this.tipList[data]
this.$nextTick(() => {
this.handleSearch()
})
}
},
handleTipSearch() {
if (!this.visibleList) this.visibleList = true
this.tipSearch()
},
handleSearch() {
if (this.visibleList) this.visibleList = false
setTimeout(() => {
this.$router.push({
path: 'search',
query: {
text: this.searchText,
},
}).catch(_ => _)
}, 200)
},
min() {
rendererSend('min')
},
@@ -48,7 +121,6 @@ export default {
display: flex;
height: @height-toolbar;
justify-content: flex-end;
background-color: @color-theme_2;
align-items: center;
padding-left: 15px;
-webkit-app-region: drag;
@@ -57,14 +129,10 @@ export default {
}
.input {
-webkit-app-region: no-drag;
position: absolute;
left: 15px;
top: 13px;
}
each(@themes, {
:global(#container.@{value}) {
.toolbar {
background-color: ~'@{color-@{value}-theme_2}';
}
}
})
// img {
// flex: none;
@@ -83,8 +151,14 @@ each(@themes, {
display: flex;
height: 100%;
-webkit-app-region: no-drag;
&:hover {
button:before {
opacity: 1;
}
}
button {
position: relative;
width: @height-toolbar;
background: none;
border: none;
@@ -99,11 +173,18 @@ each(@themes, {
content: ' ';
display: block;
border-radius: 50%;
width: 13px;
height: 13px;
width: 14px;
height: 14px;
transition: background-color 0.2s ease-in-out;
}
&:before {
display: block;
position: absolute;
opacity: 0;
transition: opacity @transition-theme;
}
&.min:after {
background-color: @color-minBtn;
}
@@ -116,6 +197,7 @@ each(@themes, {
&.min:hover:after {
background-color: lighten(@color-minBtn, 10%);
opacity: 1;
}
&.max:hover:after {
background-color: lighten(@color-maxBtn, 10%);
@@ -125,4 +207,25 @@ each(@themes, {
}
}
}
.min {
&:before {
content: ' ';
width: 8px;
height: 2px;
left: @height-toolbar / 2 - 4;
top: @height-toolbar / 2 - 1;
background-color: #fff;
}
}
.close {
&:before {
content: '×';
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
font-size: 14px;
line-height: 1;
color: #fff;
}
}
</style>

View File

@@ -13,7 +13,6 @@ div(:class="$style.view")
.view {
position: relative;
background-color: @color-theme_2;
> * {
position: absolute;
width: 100%;
@@ -22,12 +21,4 @@ div(:class="$style.view")
// overflow: hidden;
}
each(@themes, {
:global(#container.@{value}) {
.view {
background-color: ~'@{color-@{value}-theme_2}';
}
}
})
</style>

View File

@@ -1,16 +1,16 @@
<template lang="pug">
div(:class="$style.checkbox")
input(:type="need ? 'radio' : 'checkbox'" :id="id" :value="value" :name="name" @change="change" v-model="bool")
input(:type="need ? 'radio' : 'checkbox'" :class="$style.input" :id="id" :disabled="disabled" :value="value" :name="name" @change="change" v-model="bool")
label(:for="id" :class="$style.content")
div(v-if="indeterminate")
svg(v-show="indeterminate" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 32 448 448' space='preserve')
div(v-if="indeterminate" :class="$style.container")
svg(v-show="indeterminate" :class="$style.icon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 32 448 448' space='preserve')
use(xlink:href='#icon-check-indeterminate')
svg(v-show="!indeterminate" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 0 448 512' space='preserve')
svg(v-show="!indeterminate" :class="$style.icon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 0 448 512' space='preserve')
use(xlink:href='#icon-check-true')
div(v-else)
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 32 448 448' space='preserve')
div(v-else :class="$style.container")
svg(version='1.1' :class="$style.icon" xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 32 448 448' space='preserve')
use(xlink:href='#icon-check-true')
span(v-if="label != null" v-html="label")
span(v-if="label != null" v-html="label" :class="$style.label")
</template>
<script>
@@ -34,6 +34,10 @@ export default {
default: false,
},
label: {},
disabled: {
type: Boolean,
default: false,
},
indeterminate: {
type: Boolean,
default: false,
@@ -69,9 +73,9 @@ export default {
let bool = this.bool
if (this.indeterminate) {
bool = true
this.$nextTick(() => {
this.bool = true
})
// this.$nextTick(() => {
this.bool = true
// })
}
checked = bool
}
@@ -109,20 +113,25 @@ export default {
display: inline-block;
// font-size: 56px;
> input {
display: none;
&:checked {
+ .content {
> div {
&:after {
border-color: @color-theme;
}
svg {
transform: scale(1);
opacity: 1;
}
}
.input {
display: none;
&[disabled] {
+ .content {
opacity: .5;
}
}
&:checked {
+ .content {
.container {
&:after {
border-color: @color-theme;
}
}
.icon {
transform: scale(1);
// opacity: 1;
}
}
}
}
@@ -130,41 +139,41 @@ export default {
display: flex;
justify-content: center;
> div {
flex: none;
position: relative;
display: inline-block;
width: 1em;
height: 1em;
cursor: pointer;
display: flex;
color: @color-theme;
// border: 1px solid #ccc;
&:after {
position: absolute;
content: ' ';
top: 0;
bottom: 0;
left: 0;
right: 0;
border: 1px solid #ccc;
transition: border-color 0.2s ease;
border-radius: 15%;
}
svg {
transition: 0.2s ease;
transition-property: transform, opacity;
transform: scale(0.5);
opacity: 0;
}
}
.container {
flex: none;
position: relative;
display: inline-block;
width: 1em;
height: 1em;
cursor: pointer;
display: flex;
color: @color-theme;
// border: 1px solid #ccc;
&:after {
position: absolute;
content: ' ';
top: 0;
bottom: 0;
left: 0;
right: 0;
border: 1px solid @color-theme_2-font-label;
transition: border-color 0.2s ease;
border-radius: 15%;
}
}
.icon {
transition: 0.3s ease;
transition-property: transform;
transform: scale(0);
// opacity: 0;
}
> span {
flex: auto;
line-height: 1;
margin-left: 5px;
cursor: pointer;
}
.label {
flex: auto;
line-height: 1;
margin-left: 5px;
cursor: pointer;
}
each(@themes, {
@@ -173,7 +182,7 @@ each(@themes, {
> input {
&:checked {
+ .content {
> div {
.container {
&:after {
border-color: ~'@{color-@{value}-theme}';
}
@@ -183,11 +192,11 @@ each(@themes, {
}
}
.content {
> div {
.container {
color: ~'@{color-@{value}-theme}';
// border: 1px solid #ccc;
&:after {
border-color: #ccc;
border-color: ~'@{color-@{value}-theme_2-font-label}';
}
}
}

View File

@@ -5,7 +5,7 @@ material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
| {{ info.name }}
br
| {{ info.singer }}
material-btn(:class="$style.btn" :title="!checkSource(type.type) && '目前酷狗、网易音源仅支持下载128k音质'" :disabled="!checkSource(type.type)" :key="type.type" @click="handleClick(type.type)" v-for="type in info.types") {{getTypeName(type.type)}} {{ type.type.toUpperCase() }}{{ type.size && ` - ${type.size.toUpperCase()}` }}
material-btn(:class="$style.btn" :title="!checkSource(type.type) && '腾讯、网易音源仅支持下载128k音质'" :disabled="!checkSource(type.type)" :key="type.type" @click="handleClick(type.type)" v-for="type in info.types") {{getTypeName(type.type)}} {{ type.type.toUpperCase() }}{{ type.size && ` - ${type.size.toUpperCase()}` }}
</template>
@@ -52,7 +52,7 @@ export default {
checkSource(type) {
switch (this.musicInfo.source) {
case 'wy':
case 'kg':
case 'tx':
return type == '128k'
default:

View File

@@ -7,7 +7,7 @@ material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
| 请选择要优先下载的音质
material-btn(:class="$style.btn" @click="handleClick('128k')") 普通音质 - 128K
material-btn(:class="$style.btn" @click="handleClick('320k')") 高品音质 - 320K
material-btn(:class="$style.btn" @click="handleClick('ape')") 无损音质 - APE
//- material-btn(:class="$style.btn" @click="handleClick('ape')") 无损音质 - APE
material-btn(:class="$style.btn" @click="handleClick('flac')") 无损音质 - FLAC
</template>

View File

@@ -0,0 +1,90 @@
<template lang="pug">
input(:class="$style.input" :type="type" :placeholder="placeholder" v-model.trim="text" :disabled="disabled"
@focus="$emit('focus', $event)" @blur="$emit('blur', $event)" @input="$emit('input', text)" @change="$emit('change', text)"
@keyup.enter="submit")
</template>
<script>
export default {
props: {
placeholder: {
type: String,
default: '',
},
disabled: {
type: Boolean,
default: false,
},
value: {
type: String,
default: '',
},
type: {
type: String,
default: 'text',
},
},
data() {
return {
text: '',
}
},
watch: {
value(n) {
this.text = n
},
},
methods: {
handleInput() {
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.input {
display: inline-block;
border: none;
border-radius: 3px;
padding: 7px 8px;
color: @color-btn;
outline: none;
transition: background-color 0.2s ease;
background-color: @color-btn-background;
&[disabled] {
opacity: .4;
}
&:hover, &:focus {
background-color: @color-theme_2-hover;
}
&:active {
background-color: @color-theme_2-active;
}
}
.min {
padding: 3px 8px;
font-size: 12px;
}
each(@themes, {
:global(#container.@{value}) {
.input {
color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-btn-background}';
&:hover, &:focus {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&:active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
}
})
</style>

View File

@@ -0,0 +1,80 @@
<template lang="pug">
input(type="range" :class="[$style.range, min ? $style.min : '']" :disabled="disabled" v-model="val" input="handleInput" @change="handleChange")
</template>
<script>
export default {
props: {
min: {
type: Boolean,
},
disabled: {
type: Boolean,
default: false,
},
value: {
type: Number,
default: 0,
},
},
data() {
return {
val: 0,
}
},
mounted() {
this.val = this.value
},
methods: {
handleChange(e) {
this.$emit('input', this.val)
this.$emit('change', e)
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.range {
display: inline-block;
border-radius: .25em;
cursor: pointer;
color: @color-btn;
outline: none;
background: transparent;
// background-color: @color-btn-background;
&[disabled] {
opacity: .4;
}
&:hover {
background-color: @color-theme_2-hover;
}
&:active {
background-color: @color-theme_2-active;
}
}
.min {
}
// each(@themes, {
// :global(#container.@{value}) {
// .btn {
// color: ~'@{color-@{value}-btn}';
// background-color: ~'@{color-@{value}-btn-background}';
// &:hover {
// background-color: ~'@{color-@{value}-theme_2-hover}';
// }
// &:active {
// background-color: ~'@{color-@{value}-theme_2-active}';
// }
// }
// }
// })
</style>

View File

@@ -3,6 +3,9 @@ div(:class="$style.btns")
button(type="button" v-if="playBtn" title="播放" @click.stop="handleClick('play')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 287.386 287.386' space='preserve')
use(xlink:href='#icon-testPlay')
button(type="button" v-if="listAddBtn" title="添加到..." @click.stop="handleClick('listAdd')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 42 42' space='preserve')
use(xlink:href='#icon-addTo')
button(type="button" v-if="downloadBtn" title="下载" @click.stop="handleClick('download')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 475.078 475.077' space='preserve')
use(xlink:href='#icon-download')
@@ -15,6 +18,9 @@ div(:class="$style.btns")
button(type="button" v-if="pauseBtn" title="暂停" @click.stop="handleClick('pause')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 277.338 277.338' space='preserve')
use(xlink:href='#icon-pause')
button(type="button" v-if="fileBtn" title="定位文件" @click.stop="handleClick('file')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='-61 0 512 512' space='preserve')
use(xlink:href='#icon-musicFile')
button(type="button" v-if="removeBtn" title="移除" @click.stop="handleClick('remove')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 212.982 212.982' space='preserve')
use(xlink:href='#icon-delete')
@@ -53,10 +59,18 @@ export default {
type: Boolean,
default: true,
},
listAddBtn: {
type: Boolean,
default: true,
},
searchBtn: {
type: Boolean,
default: false,
},
fileBtn: {
type: Boolean,
default: false,
},
},
computed: {
...mapGetters(['userInfo']),

View File

@@ -134,7 +134,7 @@ export default {
flex-flow: column nowrap;
> * {
background-color: @color-theme_2;
background-color: @color-theme_2-background_2;
}
}
@@ -176,7 +176,7 @@ each(@themes, {
.content {
box-shadow: 0 0 3px rgba(0, 0, 0, .3);
> * {
background-color: ~'@{color-@{value}-theme_2}';
background-color: ~'@{color-@{value}-theme_2-background_2}';
}
}

View File

@@ -1,31 +1,54 @@
<template lang="pug">
div(:class="[$style.search, focus ? $style.active : '']")
div(:class="[$style.search, focus ? $style.active : '', big ? $style.big : '', small ? $style.small : '']")
div(:class="$style.form")
input(placeholder="Search for something..." v-model.trim="text"
@focus="handleFocus" @blur="handleBlur" @input="handleInput"
input(:placeholder="placeholder" v-model.trim="text"
@focus="handleFocus" @blur="handleBlur" @input="$emit('input', text)"
@change="sendEvent('change')"
@keyup.enter="handleSearch")
button(type="button" @click="handleSearch")
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')
use(xlink:href='#icon-search')
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')
use(xlink:href='#icon-search')
//- transition(name="custom-classes-transition"
//- enter-active-class="animated flipInX"
//- leave-active-class="animated flipOutX")
div(:class="$style.list" :style="listStyle")
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)")
span {{item}}
</template>
<script>
import { mapGetters } from 'vuex'
import music from '../../utils/music'
export default {
props: {
placeholder: {
type: String,
default: 'Search for something...',
},
list: {
type: Array,
},
visibleList: {
type: Boolean,
default: false,
},
value: {
type: String,
default: '',
},
big: {
type: Boolean,
default: false,
},
small: {
type: Boolean,
default: false,
},
},
data() {
return {
isShow: false,
text: '',
list: [],
index: null,
focus: false,
listStyle: {
@@ -33,55 +56,35 @@ export default {
},
}
},
computed: {
...mapGetters(['source']),
...mapGetters('search', ['info']),
},
watch: {
list(n) {
if (!this.visibleList) return
this.$nextTick(() => {
this.listStyle.height = this.$refs.dom_list.scrollHeight + 'px'
})
},
'info.text'(n) {
if (n !== this.text) this.text = n
value(n) {
this.text = n
},
visibleList(n) {
n ? this.showList() : this.hideList()
},
},
methods: {
handleTemplistClick(index) {
this.text = this.list[index]
this.handleSearch()
this.sendEvent('listClick', index)
},
handleFocus() {
this.focus = true
if (this.text) this.handleInput()
this.showList()
this.sendEvent('focus')
},
handleBlur() {
setTimeout(() => {
this.focus = false
this.hideList()
}, 200)
this.focus = false
this.sendEvent('blur')
},
handleSearch() {
this.hideList()
this.$router.push({
path: 'search',
query: {
text: this.text,
},
})
},
handleInput() {
if (this.text === '') {
this.list.splice(0, this.list.length)
music[this.source.id].tempSearch.cancelTempSearch()
return
}
if (!this.isShow) this.showList()
music[this.source.id].tempSearch.search(this.text).then(list => {
this.list = list
}).catch(() => {})
this.sendEvent('submit')
},
showList() {
this.isShow = true
@@ -91,6 +94,12 @@ export default {
this.isShow = false
this.listStyle.height = 0
},
sendEvent(action, data) {
this.$emit('event', {
action,
data,
})
},
},
}
</script>
@@ -100,9 +109,6 @@ export default {
@import '../../assets/styles/layout.less';
.search {
position: absolute;
left: 15px;
top: 13px;
border-radius: 3px;
transition: box-shadow .4s ease, background-color @transition-theme;
display: flex;
@@ -192,6 +198,19 @@ export default {
}
}
.big {
width: 500px;
// input {
// line-height: 30px;
// }
.form {
height: 30px;
button {
padding: 6px 10px;
}
}
}
each(@themes, {
:global(#container.@{value}) {

View File

@@ -1,7 +1,7 @@
<template lang="pug">
div(:class="$style.select")
div(:class="[$style.select, show ? $style.active : '']")
div(:class="$style.label" ref="dom_btn" @click="handleShow") {{value ? itemName ? list.find(l => l.id == value).name : value : ''}}
ul(:class="$style.list" ref="dom_list" :style="listStyle")
ul(:class="$style.list")
li(v-for="item in list" @click="handleClick(itemKey ? item[itemKey] : item)") {{itemName ? item[itemName] : item}}
</template>
@@ -28,25 +28,8 @@ export default {
data() {
return {
show: false,
listStyle: {
height: 0,
opacity: 0,
},
}
},
watch: {
show(n) {
this.$nextTick(() => {
if (n) {
this.listStyle.height = this.$refs.dom_list.scrollHeight + 'px'
this.listStyle.opacity = 1
} else {
this.listStyle.height = 0
this.listStyle.opacity = 0
}
})
},
},
mounted() {
document.addEventListener('click', this.handleHide)
},
@@ -80,6 +63,13 @@ export default {
.select {
font-size: 12px;
position: relative;
&.active {
.list {
transform: scaleY(1);
opacity: 1;
}
}
}
.label {
@@ -90,7 +80,7 @@ export default {
border-left: 2px solid @color-tab-border-bottom;
box-sizing: border-box;
text-align: center;
border-top-left-radius: 3px;
border-top-left-radius: 4px;
color: @color-btn;
cursor: pointer;
@@ -108,12 +98,13 @@ export default {
left: 0;
border-bottom: 2px solid @color-tab-border-bottom;
border-left: 2px solid @color-tab-border-bottom;
border-bottom-left-radius: 3px;
background-color: @color-theme_2;
overflow: hidden;
border-bottom-left-radius: 4px;
background-color: @color-theme_2-background_2;
opacity: 0;
transform: scaleY(0);
transform-origin: 0 0 0;
transition: .25s ease;
transition-property: height, opacity;
transition-property: transform, opacity;
z-index: 10;
li {
@@ -153,6 +144,7 @@ each(@themes, {
.list {
border-bottom-color: ~'@{color-@{value}-tab-border-bottom}';
border-left-color: ~'@{color-@{value}-tab-border-bottom}';
background-color: ~'@{color-@{value}-theme_2-background_2}';
li {
// color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-btn-background}';

View File

@@ -0,0 +1,239 @@
<template lang="pug">
div(:class="$style.songList")
transition(enter-active-class="animated fadeIn" leave-active-class="animated fadeOut")
div(v-if="list.length" :class="$style.list")
div(:class="$style.thead")
table
thead
tr
th.nobreak.center(style="width: 37px;")
material-checkbox(id="search_select_all" v-model="isSelectAll" @change="handleSelectAllData"
:indeterminate="isIndeterminate" :title="isSelectAll && !isIndeterminate ? '全不选' : '全选'")
th.nobreak(style="width: 25%;") 歌曲名
th.nobreak(style="width: 20%;") 歌手
th.nobreak(style="width: 20%;") 专辑
th.nobreak(style="width: 20%;") 操作
th.nobreak(style="width: 10%;") 时长
div.scroll(:class="$style.tbody" ref="dom_scrollContent")
table
tbody
tr(v-for='(item, index) in list' :key='item.songmid' @click="handleDoubleClick(index)")
td.nobreak.center(style="width: 37px;" @click.stop)
material-checkbox(:id="index.toString()" v-model="selectdList" @change="handleChangeSelect" :value="item")
td.break(style="width: 25%;")
| {{item.name}}
span.badge.badge-info(v-if="item._types['320k']") 高品质
span.badge.badge-success(v-if="item._types.ape || item._types.flac") 无损
td.break(style="width: 20%;") {{item.singer}}
td.break(style="width: 20%;") {{item.albumName}}
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)")
//- 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)')
td(style="width: 10%;") {{item.interval || '--/--'}}
div(:class="$style.pagination")
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")
</template>
<script>
import { mapGetters } from 'vuex'
import { scrollTo } from '../../utils'
export default {
name: 'MaterialSongList',
model: {
prop: 'selectdData',
event: 'input',
},
props: {
list: {
type: Array,
default() {
return []
},
},
page: {
type: Number,
required: true,
},
limit: {
type: Number,
required: true,
},
total: {
type: Number,
required: true,
},
selectdData: {
type: Array,
required: true,
},
source: {
type: String,
},
noItem: {
type: String,
default: '列表加载中...',
},
},
computed: {
...mapGetters(['setting']),
isAPITemp() {
return this.setting.apiSource == 'temp'
},
},
watch: {
selectdList(n) {
const len = n.length
if (len) {
this.isSelectAll = true
this.isIndeterminate = len !== this.list.length
this.isShowEditBtn = true
} else {
this.isSelectAll = false
this.isShowEditBtn = false
}
},
selectdData(n) {
const len = n.length
if (len) {
this.isSelectAll = true
this.isIndeterminate = len !== this.list.length
this.isShowEditBtn = true
this.selectdList = [...n]
} else {
this.isSelectAll = false
this.isShowEditBtn = false
this.resetSelect()
}
},
list(n) {
this.resetSelect()
if (!this.list.length) return
this.$nextTick(() => scrollTo(this.$refs.dom_scrollContent, 0))
},
},
data() {
return {
clickTime: 0,
clickIndex: -1,
isSelectAll: false,
isIndeterminate: false,
isShowEditBtn: false,
selectdList: [],
}
},
methods: {
handleDoubleClick(index) {
if (
window.performance.now() - this.clickTime > 400 ||
this.clickIndex !== index
) {
this.clickTime = window.performance.now()
this.clickIndex = index
return
}
this.emitEvent((this.source == 'kw' || !this.isAPITemp) ? 'testPlay' : 'search', index)
this.clickTime = 0
this.clickIndex = -1
},
handleListBtnClick(info) {
this.emitEvent('listBtnClick', info)
},
handleSelectAllData(isSelect) {
this.selectdList = isSelect ? [...this.list] : []
this.$emit('input', [...this.selectdList])
},
resetSelect() {
this.selectdList = false
this.selectdList = []
},
handleTogglePage(page) {
this.emitEvent('togglePage', page)
},
handleFlowBtnClick(action) {
this.emitEvent('flowBtnClick', action)
},
emitEvent(action, data) {
this.$emit('action', { action, data })
},
handleChangeSelect() {
this.$emit('input', [...this.selectdList])
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.song-list {
overflow: hidden;
height: 100%;
display: flex;
flex-flow: column nowrap;
}
.list {
position: relative;
font-size: 14px;
overflow: hidden;
display: flex;
flex-flow: column nowrap;
}
.thead {
flex: none;
}
.tbody {
flex: auto;
overflow-y: auto;
td {
font-size: 12px;
:global(.badge) {
margin-right: 3px;
&:first-child {
margin-left: 3px;
}
&:last-child {
margin-right: 0;
}
}
}
}
.pagination {
text-align: center;
padding: 15px 0;
// left: 50%;
// transform: translateX(-50%);
}
.noitem {
position: relative;
height: 100%;
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: center;
p {
font-size: 24px;
color: @color-theme_2-font-label;
}
}
each(@themes, {
:global(#container.@{value}) {
.noitem {
p {
color: ~'@{color-@{value}-theme_2-font-label}';
}
}
}
})
</style>

View File

@@ -95,15 +95,15 @@ export default {
margin-left: 0;
button {
border-top-left-radius: 3px;
// border-bottom-left-radius: 3px;
// border-bottom-left-radius: 4px;
}
}
&:last-child {
border-right: 2px solid @color-tab-border-top;
border-top-right-radius: 3px;
border-top-right-radius: 4px;
button {
border-top-right-radius: 3px;
// border-bottom-right-radius: 3px;
// border-bottom-right-radius: 4px;
}
}
button {
@@ -122,7 +122,7 @@ export default {
// border-left-color: @color-theme_2-hover;
// border-right-color: @color-theme_2-hover;
button {
background-color: @color-theme_2-hover;
background-color: @color-tab-btn-background-hover;
}
}
&:active {
@@ -133,7 +133,7 @@ export default {
}
}
&.active {
border-bottom-color: @color-theme_2;
border-bottom-color: @color-theme_2-background_1;
border-top-color: @color-tab-border-bottom;
border-left-color: @color-tab-border-bottom;
border-right-color: @color-tab-border-bottom;
@@ -144,7 +144,7 @@ export default {
width: 0;
}
button {
background-color: @color-theme_2;
background-color: @color-theme_2-background_1;
}
}
}
@@ -193,7 +193,7 @@ each(@themes, {
// border-left-color: ~'@{color-@{value}-theme_2-hover}';
// border-right-color: ~'@{color-@{value}-theme_2-hover}';
button {
background-color: ~'@{color-@{value}-theme_2-hover}';
background-color: ~'@{color-@{value}-tab-btn-background-hover}';
}
}
&:active {
@@ -209,7 +209,7 @@ each(@themes, {
border-left-color: ~'@{color-@{value}-tab-border-bottom}';
border-right-color: ~'@{color-@{value}-tab-border-bottom}';
button {
background-color: ~'@{color-@{value}-theme_2}';
background-color: ~'@{color-@{value}-theme_2-background_1}';
}
}
}

View File

@@ -0,0 +1,231 @@
<template lang="pug">
div(:class="[$style.tagList, show ? $style.active : '']")
div(:class="$style.label" ref="dom_btn" @click="handleShow")
span {{value.name}}
div(:class="$style.icon")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.847 451.847' space='preserve')
use(xlink:href='#icon-down')
div.scroll(:class="$style.list" @click.stop ref="dom_list")
div(:class="$style.tag" @click="handleClick(null)") 默认
dl(v-for="type in list")
dt(:class="$style.type") {{type.name}}
dd(:class="$style.tag" v-for="tag in type.list" @click="handleClick(tag)") {{tag.name}}
</template>
<script>
import { isChildren } from '../../utils'
export default {
props: {
list: {
type: Array,
default() {
return []
},
},
value: {
type: Object,
},
},
data() {
return {
show: false,
}
},
mounted() {
document.addEventListener('click', this.handleHide)
},
beforeDestroy() {
document.removeEventListener('click', this.handleHide)
},
methods: {
handleHide(e) {
// if (e && e.target.parentNode != this.$refs.dom_list && this.show) return this.show = false
if (e && (e.target == this.$refs.dom_btn || isChildren(this.$refs.dom_btn, e.target))) return
setTimeout(() => {
this.show = false
}, 50)
},
handleClick(item) {
if (!item) {
item = {
name: '默认',
id: null,
}
}
if (item.id === this.value.id) return this.handleShow()
this.$emit('input', item)
this.$emit('change', item)
this.handleShow()
},
handleShow() {
this.show = !this.show
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.tag-list {
font-size: 12px;
position: relative;
&.active {
.label {
.icon {
svg{
transform: rotate(180deg);
}
}
}
.list {
opacity: 1;
transform: scaleY(1);
}
}
}
.label {
padding: 8px 15px;
// background-color: @color-btn-background;
transition: background-color @transition-theme;
border-top: 2px solid @color-tab-border-bottom;
// border-left: 2px solid @color-tab-border-bottom;
box-sizing: border-box;
text-align: center;
// border-top-left-radius: 3px;
color: @color-btn;
cursor: pointer;
display: flex;
span {
flex: auto;
}
.icon {
flex: none;
margin-left: 7px;
line-height: 0;
svg {
width: 1em;
transition: transform .2s ease;
transform: rotate(0);
}
}
&:hover {
background-color: @color-theme_2-hover;
}
&:active {
background-color: @color-theme_2-active;
}
}
.list {
position: absolute;
top: 100%;
width: 646px;
left: 0;
border-bottom: 2px solid @color-tab-border-bottom;
border-right: 2px solid @color-tab-border-bottom;
border-bottom-right-radius: 5px;
background-color: @color-theme_2-background_2;
opacity: 0;
transform: scaleY(0);
overflow-y: auto;
transform-origin: 0 0 0;
max-height: 250px;
transition: .25s ease;
transition-property: transform, opacity;
z-index: 10;
padding: 10px;
box-sizing: border-box;
li {
cursor: pointer;
padding: 8px 15px;
// color: @color-btn;
text-align: center;
outline: none;
transition: background-color @transition-theme;
background-color: @color-btn-background;
box-sizing: border-box;
&:hover {
background-color: @color-theme_2-hover;
}
&:active {
background-color: @color-theme_2-active;
}
}
}
.type {
padding-top: 10px;
padding-bottom: 3px;
color: #999;
}
.tag {
display: inline-block;
margin: 5px;
background-color: @color-btn-background;
padding: 8px 10px;
border-radius: @radius-progress-border;
transition: background-color @transition-theme;
cursor: pointer;
&:hover {
background-color: @color-theme_2-hover;
}
&:active {
background-color: @color-theme_2-active;
}
}
each(@themes, {
:global(#container.@{value}) {
.label {
border-top-color: ~'@{color-@{value}-tab-border-bottom}';
// border-left-color: ~'@{color-@{value}-tab-border-bottom}';
color: ~'@{color-@{value}-btn}';
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&:active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
.list {
border-bottom-color: ~'@{color-@{value}-tab-border-bottom}';
border-right-color: ~'@{color-@{value}-tab-border-bottom}';
background-color: ~'@{color-@{value}-theme_2-background_2}';
// border-left-color: ~'@{color-@{value}-tab-border-bottom}';
li {
// color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-btn-background}';
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&:active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
}
.tag {
background-color: ~'@{color-@{value}-btn-background}';
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&:active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
}
})
</style>

View File

@@ -1,7 +1,7 @@
<template lang="pug">
material-modal(:show="version.showModal" @close="handleClose")
main(:class="$style.main" v-if="version.newVersion")
h2 🚀程序更新🚀
h2 {{ version.isError ? (isUnknow ? '❓ 版本信息获取失败 ❓' : '🌟发现新版本🌟') : '🚀程序更新🚀'}}
div.scroll(:class="$style.info")
div(:class="$style.current")
@@ -15,7 +15,22 @@ material-modal(:show="version.showModal" @close="handleClose")
h4 v{{ver.version}}
p(v-html="ver.desc")
div(:class="$style.footer")
div(:class="$style.footer" v-if="version.isError")
div(:class="$style.desc" v-if="!isUnknow")
p 发现有新版本啦但是自动更新功能出问题了
p
| 你现在可以选择继续使用当前版本或
strong 去发布页下载新版本
|
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
@@ -24,18 +39,18 @@ material-modal(:show="version.showModal" @close="handleClose")
| 或稍后
strong 关闭程序时
| 自动更新~
material-btn(:class="$style.btn" @click.onec="handleClick") 立即重启更新
material-btn(:class="$style.btn" @click.onec="handleRestartClick") 立即重启更新
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import { rendererSend } from '../../../common/icp'
import { checkVersion } from '../../utils'
import { checkVersion, openUrl, clipboardWriteText } from '../../utils'
export default {
computed: {
...mapGetters(['version']),
...mapGetters(['version', 'setting']),
history() {
if (!this.version.newVersion) return []
let arr = []
@@ -46,17 +61,33 @@ export default {
return arr
},
isUnknow() {
return this.version.newVersion.version == '0.0.0'
},
},
methods: {
...mapMutations(['setVersionVisible']),
...mapMutations(['setVersionModalVisible', 'setSetting']),
handleClose() {
this.setVersionVisible(false)
this.setVersionModalVisible({
isShow: false,
})
},
handleClick(event) {
handleIgnoreClick(event) {
this.handleClose()
// event.target.disabled = true
this.setSetting(Object.assign({}, this.setting, { ignoreVersion: this.version.newVersion.version }))
},
handleOpenUrl(url) {
openUrl(url)
},
handleRestartClick(event) {
this.handleClose()
event.target.disabled = true
rendererSend('quit-update')
},
handleCopy(text) {
clipboardWriteText(text)
},
},
}
</script>
@@ -68,7 +99,7 @@ export default {
.main {
position: relative;
padding: 15px;
max-width: 500px;
max-width: 450px;
min-width: 300px;
display: flex;
flex-flow: column nowrap;
@@ -98,6 +129,7 @@ export default {
line-height: 1.5;
overflow-y: auto;
height: 100%;
padding-right: 5px;
}
.current {
> p {
@@ -144,18 +176,21 @@ export default {
flex: 0 0 none;
.desc {
font-size: 12px;
padding: 10px 0;
padding-top: 10px;
color: @color-theme;
line-height: 1.2;
strong {
text-decoration: underline;
}
}
}
.btn {
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

@@ -0,0 +1,103 @@
<template lang="pug">
material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
main(:class="$style.main")
h2
| 添加&nbsp;
span(:class="$style.name") {{this.musicInfo && `${musicInfo.name}`}}
| &nbsp;...
material-btn(:class="$style.btn" :title="`把该歌曲添加到 ${item.name}`" :key="item.id" @click="handleClick(index)" v-for="(item, index) in lists") {{item.name}}
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
export default {
props: {
show: {
type: Boolean,
default: false,
},
musicInfo: {
type: Object,
},
bgClose: {
type: Boolean,
default: true,
},
excludeListId: {
type: Array,
default() {
return []
},
},
},
computed: {
...mapGetters('list', ['defaultList', 'loveList', 'userList']),
lists() {
return [
this.defaultList,
this.loveList,
...this.userList,
].filter(l => !this.excludeListId.includes(l.id))
},
},
methods: {
...mapMutations('list', ['listAdd']),
handleClick(index) {
this.listAdd({ id: this.lists[index].id, musicInfo: this.musicInfo })
this.$nextTick(() => {
this.handleClose()
})
},
handleClose() {
this.$emit('close')
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.main {
padding: 15px;
max-width: 300px;
min-width: 200px;
display: flex;
flex-flow: column nowrap;
justify-content: center;
h2 {
font-size: 13px;
color: @color-theme_2-font;
line-height: 1.3;
text-align: center;
margin-bottom: 15px;
}
}
.name {
color: @color-theme;
}
.btn {
display: block;
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
}
each(@themes, {
:global(#container.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
}
}
.name {
color: ~'@{color-@{value}-theme}';
}
}
})
</style>

View File

@@ -0,0 +1,97 @@
<template lang="pug">
material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
main(:class="$style.main")
h2 添加已选的 {{musicList.length}} 首歌曲到...
material-btn(:class="$style.btn" :title="`把该歌曲添加到 ${item.name}`" :key="item.id" @click="handleClick(index)" v-for="(item, index) in lists") {{item.name}}
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
export default {
props: {
show: {
type: Boolean,
default: false,
},
musicList: {
type: Array,
default() {
return []
},
},
bgClose: {
type: Boolean,
default: true,
},
excludeListId: {
type: Array,
default() {
return []
},
},
},
computed: {
...mapGetters('list', ['defaultList', 'loveList', 'userList']),
lists() {
return [
this.defaultList,
this.loveList,
...this.userList,
].filter(l => !this.excludeListId.includes(l.id))
},
},
methods: {
...mapMutations('list', ['listAddMultiple']),
handleClick(index) {
this.listAddMultiple({ id: this.lists[index].id, list: this.musicList })
this.$nextTick(() => {
this.handleClose(true)
})
},
handleClose(isSelect = false) {
this.$emit('close', isSelect)
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.main {
padding: 15px;
max-width: 300px;
min-width: 200px;
display: flex;
flex-flow: column nowrap;
justify-content: center;
h2 {
font-size: 13px;
color: @color-theme_2-font;
line-height: 1.3;
text-align: center;
margin-bottom: 15px;
}
}
.btn {
display: block;
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
}
each(@themes, {
:global(#container.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
}
}
}
})
</style>

View File

@@ -1,5 +1,5 @@
import Vue from 'vue'
// import { sync } from 'vuex-router-sync'
import { sync } from 'vuex-router-sync'
// Components
import './components'
@@ -11,7 +11,7 @@ import App from './App'
import router from './route'
import store from './store'
// sync(store, router)
sync(store, router)
if (!process.env.IS_WEB) {

View File

@@ -18,9 +18,9 @@ export default [
view: 'Leaderboard',
},
{
path: '/recommend',
name: 'recommend',
view: 'Recommend',
path: '/songList',
name: 'songList',
view: 'SongList',
},
{
path: '/list',

View File

@@ -3,10 +3,18 @@ import { httpGet } from '../utils/request'
import { author, name } from '../../../package.json'
export default {
getVersionInfo() {
getVersionInfo(state, retryNum = 0) {
return new Promise((resolve, reject) => {
httpGet(`https://raw.githubusercontent.com/${author}/${name}/master/publish/version.json`, (err, resp, body) => {
if (err) return reject(err)
httpGet(`https://raw.githubusercontent.com/${author.name}/${name}/master/publish/version.json`, {
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))
}
resolve(body)
})
})

View File

@@ -31,4 +31,7 @@ export default {
version(state) {
return state.version
},
route(state) {
return state.route
},
}

View File

@@ -3,6 +3,7 @@ import fs from 'fs'
import path from 'path'
import music from '../../utils/music'
import { getMusicType } from '../../utils/music/utils'
import { setMeta, saveLrc } from '../../utils'
// state
const state = {
@@ -21,6 +22,8 @@ const state = {
const dls = {}
const tryNum = {}
const filterFileName = /[\\/:*?#"<>|]/g
// getters
const getters = {
list: state => state.list || [],
@@ -72,9 +75,52 @@ const addTask = (list, type, store) => {
const getUrl = (downloadInfo, isRefresh) => {
const url = downloadInfo.musicInfo.typeUrl[downloadInfo.type]
if (!downloadInfo.musicInfo._types[downloadInfo.type]) {
// 兼容旧版酷我源搜索列表过滤128k音质的bug
if (!(downloadInfo.musicInfo.source == 'kw' && downloadInfo.type == '128k')) return Promise.reject(new Error('该歌曲没有可下载的音频'))
// return Promise.reject(new Error('该歌曲没有可下载的音频'))
}
return url && !isRefresh ? Promise.resolve({ url }) : music[downloadInfo.musicInfo.source].getMusicUrl(downloadInfo.musicInfo, downloadInfo.type).promise
}
/**
* 设置歌曲meta信息
* @param {*} downloadInfo
* @param {*} filePath
* @param {*} isEmbedPic
*/
const saveMeta = (downloadInfo, filePath, isEmbedPic) => {
if (downloadInfo.type === 'ape' || downloadInfo.type === 'flac') return
const promise = isEmbedPic
? downloadInfo.musicInfo.img
? Promise.resolve(downloadInfo.musicInfo.img)
: music[downloadInfo.musicInfo.source].getPic(downloadInfo.musicInfo).promise
: Promise.resolve()
promise.then(url => {
setMeta(filePath, {
title: downloadInfo.musicInfo.name,
artist: downloadInfo.musicInfo.singer,
album: downloadInfo.musicInfo.albumName,
APIC: url,
})
})
}
/**
* 保存歌词
* @param {*} downloadInfo
* @param {*} filePath
*/
const downloadLyric = (downloadInfo, filePath) => {
const promise = downloadInfo.musicInfo.lrc
? Promise.resolve(downloadInfo.musicInfo.lrc)
: music[downloadInfo.musicInfo.source].getLyric(downloadInfo.musicInfo).promise
promise.then(lrc => {
if (lrc) saveLrc(filePath.replace(/(mp3|flac|ape)$/, 'lrc'), lrc)
})
}
// actions
const actions = {
createDownload({ state, rootState, commit }, { musicInfo, type }) {
@@ -87,7 +133,7 @@ const actions = {
url: null,
fileName: `${rootState.setting.download.fileName
.replace('歌名', musicInfo.name)
.replace('歌手', musicInfo.singer)}.${ext}`,
.replace('歌手', musicInfo.singer)}.${ext}`.replace(filterFileName, ''),
progress: {
downloaded: 0,
total: 0,
@@ -133,8 +179,16 @@ const actions = {
method: 'get',
override: true,
onEnd() {
if (downloadInfo.progress.progress != '100.00') {
delete dls[downloadInfo.key]
return this.dispatch('download/startTask', downloadInfo)
}
commit('onEnd', downloadInfo)
_this.dispatch('download/startTask')
const filePath = path.join(options.path, options.fileName)
saveMeta(downloadInfo, filePath, rootState.setting.download.isEmbedPic)
if (rootState.setting.download.isDownloadLrc) downloadLyric(downloadInfo, filePath)
console.log('on complate')
},
onError(err) {

View File

@@ -1,15 +1,22 @@
// state
const state = {
defaultList: {
id: 'default',
name: '试听列表',
list: [],
},
loveList: {
id: 'love',
name: '我的收藏',
list: [],
},
userList: [],
}
// getters
const getters = {
defaultList: state => state.defaultList || {},
loveList: state => state.loveList || {},
userList: state => state.userList,
}
@@ -18,39 +25,72 @@ const actions = {
}
const getList = (state, id) => {
let targetList
switch (id) {
case 'default':
targetList = state.defaultList
break
case 'love':
targetList = state.loveList
break
default:
targetList = state.userList.find(l => l.id === id)
break
}
return targetList
}
// mitations
const mutations = {
initDefaultList(state, data) {
state.defaultList = data
initList(state, { defaultList, loveList }) {
if (defaultList !== undefined) state.defaultList.list = defaultList.list
if (loveList !== undefined) state.loveList.list = loveList.list
},
setDefaultList(state, list) {
state.defaultList.list = list
setList(state, { id, list }) {
const targetList = getList(state, id)
if (!targetList) return
targetList.list = list
},
defaultListAdd(state, musicInfo) {
if (state.defaultList.list.some(s => s.songmid === musicInfo.songmid)) return
state.defaultList.list.push(musicInfo)
listAdd(state, { id, musicInfo }) {
const targetList = getList(state, id)
if (!targetList) return
if (targetList.list.some(s => s.songmid === musicInfo.songmid)) return
targetList.list.push(musicInfo)
},
defaultListAddMultiple(state, list) {
listAddMultiple(state, { id, list }) {
let targetList = getList(state, id)
if (!targetList) return
targetList = targetList.list
list.forEach(musicInfo => {
if (state.defaultList.list.some(s => s.songmid === musicInfo.songmid)) return
state.defaultList.list.push(musicInfo)
if (targetList.some(s => s.songmid === musicInfo.songmid)) return
targetList.push(musicInfo)
})
},
defaultListRemove(state, index) {
state.defaultList.list.splice(index, 1)
listRemove(state, { id, index }) {
let targetList = getList(state, id)
if (!targetList) return
targetList.list.splice(index, 1)
},
updateMusicInfo(state, { index, data }) {
Object.assign(state.defaultList.list[index], data)
},
defaultListRemoveMultiple(state, list) {
listRemoveMultiple(state, { id, list }) {
let targetList = getList(state, id)
if (!targetList) return
targetList = targetList.list
list.forEach(musicInfo => {
let index = state.defaultList.list.indexOf(musicInfo)
let index = targetList.indexOf(musicInfo)
if (index < 0) return
state.defaultList.list.splice(index, 1)
targetList.splice(index, 1)
})
},
defaultListClear(state) {
state.defaultList.list.length = 0
listClear(state, id) {
let targetList = getList(state, id)
if (!targetList) return
targetList.list.length = 0
},
updateMusicInfo(state, { id, index, data }) {
let targetList = getList(state, id)
if (!targetList) return
Object.assign(targetList.list[index], data)
},
}

View File

@@ -23,6 +23,12 @@ const getters = {
// actions
const actions = {
getUrl({ commit, state }, { musicInfo, type, isRefresh }) {
if (!musicInfo._types[type]) {
// 兼容旧版酷我源搜索列表过滤128k音质的bug
if (!(musicInfo.source == 'kw' && type == '128k')) return Promise.reject(new Error('该歌曲没有可播放的音频'))
// return Promise.reject(new Error('该歌曲没有可播放的音频'))
}
if (urlRequest && urlRequest.cancelHttp) urlRequest.cancelHttp()
if (musicInfo.typeUrl[type] && !isRefresh) return Promise.resolve()
urlRequest = music[musicInfo.source].getMusicUrl(musicInfo, type)
@@ -56,13 +62,13 @@ const actions = {
// mitations
const mutations = {
setUrl(state, datas) {
datas.musicInfo.typeUrl[datas.type] = datas.url
datas.musicInfo.typeUrl = Object.assign({}, datas.musicInfo.typeUrl, { [datas.type]: datas.url })
},
getPic(state, datas) {
datas.musicInfo.img = datas.url
},
setLrc(state, datas) {
datas.musicInfo.lyric = datas.lrc
datas.musicInfo.lrc = datas.lrc
},
setList(state, { list, listId, index }) {
state.list = list

View File

@@ -1,41 +1,103 @@
import music from '../../utils/music'
const sources = []
const sourceList = {}
const sourceMaxPage = {}
for (const source of music.sources) {
const musicSearch = music[source.id].musicSearch
if (!musicSearch) continue
sources.push(source)
sourceList[source.id] = {
page: 1,
allPage: 0,
limit: 30,
total: 0,
list: [],
}
sourceMaxPage[source.id] = 0
}
sources.push({
id: 'all',
name: '聚合搜索',
})
// state
const state = {
sourceList,
list: [],
text: '',
page: 1,
limit: 30,
allPage: 1,
total: 0,
sourceMaxPage,
}
// getters
const getters = {
list: state => state.list || [],
limit: state => state.limit,
info: state => ({ page: state.page, text: state.text }),
listInfo: state => ({ allPage: state.allPage, total: state.total }),
sources: () => sources,
sourceList: state => state.sourceList || [],
searchText: state => state.text,
allList: state => ({ list: state.list, allPage: state.allPage, page: state.page, total: state.total, limit: state.limit, sourceMaxPage: state.sourceMaxPage }),
}
// actions
const actions = {
search({ commit, rootState }, { text, page, limit }) {
return music[rootState.setting.sourceId].musicSearch.search(text, page, limit)
.then(data => commit('setList', { list: data.list, allPage: data.allPage, total: data.total, text, page }))
if (rootState.setting.search.searchSource == 'all') {
let task = []
for (const source of sources) {
if (source.id == 'all') continue
task.push(music[source.id].musicSearch.search(text, page))
}
Promise.all(task).then(results => commit('setLists', { results, text, page }))
} else {
return music[rootState.setting.search.searchSource].musicSearch.search(text, page, limit)
.then(data => commit('setList', { text, page, ...data }))
}
},
}
// mitations
const mutations = {
setList(state, datas) {
state.list = datas.list
state.total = datas.total
state.allPage = datas.allPage
state.page = datas.page
let source = state.sourceList[datas.source]
source.list = datas.list
source.total = datas.total
source.allPage = datas.allPage
source.page = datas.page
source.limit = datas.limit
state.text = datas.text
},
setLists(state, { results, text, page }) {
let pages = []
let total = 0
let limit = 0
let list = []
for (const source of results) {
state.sourceMaxPage[source.source] = source.allPage
if (source.allPage < page) continue
list.push(...source.list)
pages.push(source.allPage)
total += source.total
limit += source.limit
}
list.sort((a, b) => b.name.charCodeAt(0) - a.name.charCodeAt(0))
state.allPage = Math.max(...pages)
state.total = total
state.limit = limit
state.page = page
state.text = text
state.list = list
},
clearList(state) {
for (const source of Object.keys(state.sourceList)) {
state.sourceList[source].list.length = 0
state.sourceList[source].page = 0
state.sourceList[source].allPage = 0
state.sourceList[source].total = 0
state.sourceMaxPage[source] = 0
}
state.list.length = 0
state.page = 0
state.allPage = 0

View File

@@ -0,0 +1,146 @@
import music from '../../utils/music'
const sortList = {}
const sources = []
const cache = new Map()
for (const source of music.sources) {
const songList = music[source.id].songList
if (!songList) continue
sortList[source.id] = songList.sortList
sources.push(source)
}
// state
const state = {
tags: {},
list: {
list: [],
total: 0,
page: 1,
limit: 30,
key: null,
},
listDetail: {
list: [],
desc: null,
total: 0,
page: 1,
limit: 30,
key: null,
info: {},
},
selectListInfo: {},
isVisibleListDetail: false,
}
sources.forEach(source => {
state.tags[source.id] = null
})
// getters
const getters = {
sourceInfo: () => ({ sources, sortList }),
tags: state => state.tags,
isVisibleListDetail: state => state.isVisibleListDetail,
selectListInfo: state => state.selectListInfo,
listData(state) {
return state.list
},
listDetail(state) {
return state.listDetail
},
}
// actions
const actions = {
getTags({ state, rootState, commit }) {
let source = rootState.setting.songList.source
return music[source].songList.getTags().then(result => commit('setTags', { tags: result, source }))
},
getList({ state, rootState, commit }, page) {
let source = rootState.setting.songList.source
let tabId = rootState.setting.songList.tagInfo.id
let sortId = rootState.setting.songList.sortId
// console.log(sortId)
let key = `slist__${source}__${sortId}__${tabId}__${page}`
if (state.list.list.length && state.list.key == key) return
return (cache.has(key) ? Promise.resolve(cache.get(key)) : music[source].songList.getList(sortId, tabId, page)).then(result => commit('setList', { result, key, page }))
},
getListDetail({ state, rootState, commit }, { id, page }) {
let source = rootState.setting.songList.source
let key = `sdetail__${source}__${id}__${page}`
if (state.listDetail.list.length && state.listDetail.key == key) return true
return (cache.has(key) ? Promise.resolve(cache.get(key)) : music[source].songList.getListDetail(id, page)).then(result => commit('setListDetail', { result, key, page }))
},
/* getListDetailAll({ state, rootState }, id) {
let source = rootState.setting.songList.source
let key = `sdetail__${source}__${id}__all`
if (cache.has(key)) return Promise.resolve(cache.get(key))
music[source].songList.getListDetail(id, 1).then(result => {
let data = { list: result.list, id }
if (result.total <= result.limit) {
data = { list: result.list, id }
cache.set(key, data)
return data
}
let maxPage = Math.ceil(result.total / result.limit)
const loadDetail = (loadPage = 1) => {
let task = []
let loadNum = 0
while (loadPage <= maxPage && loadNum < 3) {
task.push(music[source].songList.getListDetail(id, ++loadPage))
loadNum++
}
return loadPage == maxPage
? Promise.all(task)
: Promise.all(task).then(result => loadDetail(loadPage).then(result2 => [...result, ...result2]))
}
return loadDetail().then(result2 => {
console.log(result2)
})
})
}, */
}
// mitations
const mutations = {
setTags(state, { tags, source }) {
state.tags[source] = tags
},
setList(state, { result, key, page }) {
state.list.list = result.list
state.list.total = result.total
state.list.limit = result.limit
state.list.page = page
state.list.key = key
cache.set(key, result)
},
setListDetail(state, { result, key, page }) {
state.listDetail.list = result.list
state.listDetail.total = result.total
state.listDetail.limit = result.limit
state.listDetail.page = page
state.listDetail.key = key
state.listDetail.info = result.info || {}
cache.set(key, result)
},
setVisibleListDetail(state, bool) {
if (!bool) state.listDetail.list = []
state.isVisibleListDetail = bool
},
setSelectListInfo(state, info) {
state.selectListInfo = info
},
clearListDetail(state) {
state.listDetail.list = []
},
}
export default {
namespaced: true,
state,
getters,
actions,
mutations,
}

View File

@@ -2,8 +2,9 @@ export default {
setTheme(state, val) {
state.setting.themeId = val
},
setSource(state, val) {
state.setting.sourceId = val
setSearchSource(state, { searchSource, tempSearchSource }) {
if (searchSource != null) state.setting.search.searchSource = searchSource
if (tempSearchSource != null) state.setting.search.tempSearchSource = tempSearchSource
},
setSetting(state, val) {
state.setting = val
@@ -12,6 +13,14 @@ export default {
if (tabId != null) state.setting.leaderboard.tabId = tabId
if (source != null) state.setting.leaderboard.source = source
},
setSongList(state, { sortId, tagInfo, source }) {
if (tagInfo != null) state.setting.songList.tagInfo = tagInfo
if (sortId != null) state.setting.songList.sortId = sortId
if (source != null) state.setting.songList.source = source
},
setListScroll(state, { id, location }) {
state.setting.list.scroll.locations[id] = location
},
setNewVersion(state, val) {
// val.history.forEach(ver => {
// ver.desc = ver.desc.replace(/\n/g, '<br>')
@@ -19,7 +28,11 @@ export default {
// val.desc = val.desc.replace(/\n/g, '<br>')
state.version.newVersion = val
},
setVersionVisible(state, val) {
state.version.showModal = val
setVersionModalVisible(state, { isShow, isError }) {
if (isShow !== undefined) state.version.showModal = isShow
if (isError !== undefined) state.version.isError = isError
},
setVolume(state, val) {
state.setting.player.volume = val
},
}

View File

@@ -6,6 +6,7 @@ import { version } from '../../../package.json'
let electronStore = new Store()
const setting = updateSetting(electronStore.get('setting'))
electronStore.set('setting', setting)
process.versions.app = version
export default {
themes: [
@@ -44,11 +45,22 @@ export default {
name: '灰常美丽',
class: 'grey',
},
{
id: 7,
name: '月里嫦娥',
class: 'midAutumn',
},
{
id: 8,
name: '木叶之村',
class: 'dhHyrz',
},
],
version: {
version,
newVersion: null,
showModal: false,
isError: false,
},
userInfo: null,
setting,

View File

@@ -1,7 +1,9 @@
import fs from 'fs'
import { shell, remote } from 'electron'
import { shell, remote, clipboard } from 'electron'
import path from 'path'
import os from 'os'
import crypto from 'crypto'
import { rendererSend } from '../../common/icp'
/**
* 获取两个数之间的随机整数大于等于min小于max
@@ -13,8 +15,8 @@ export const getRandom = (min, max) => Math.floor(Math.random() * (max - min)) +
export const sizeFormate = size => {
// https://gist.github.com/thomseddon/3511330
if (!size) return '0 b'
let units = ['b', 'kB', 'MB', 'GB', 'TB']
if (!size) return '0 B'
let units = ['B', 'KB', 'MB', 'GB', 'TB']
let number = Math.floor(Math.log(size) / Math.log(1024))
return `${(size / Math.pow(1024, Math.floor(number))).toFixed(2)} ${units[number]}`
}
@@ -41,7 +43,8 @@ export const b64DecodeUnicode = str => {
export const decodeName = str => str.replace(/&apos;/g, '\'')
export const scrollTo = (element, to, duration = 300, fn = function() {}) => {
const start = element.scrollTop || element.scrollY
if (!element) return
const start = element.scrollTop || element.scrollY || 0
const change = to - start
const increment = 10
if (!change) {
@@ -161,33 +164,66 @@ export const isChildren = (parent, children) => {
* @param {*} setting
*/
export const updateSetting = setting => {
const defaultVersion = '1.0.3'
const defaultVersion = '1.0.12'
const defaultSetting = {
version: defaultVersion,
player: {
togglePlayMethod: 'listLoop',
highQuality: false,
isShowTaskProgess: true,
volume: 1,
},
list: {
isShowAlbumName: true,
isShowSource: false,
scroll: {
enable: true,
locations: {},
},
},
download: {
savePath: path.join(os.homedir(), 'Desktop'),
fileName: '歌名 - 歌手',
maxDownloadNum: 3,
isDownloadLrc: false,
isEmbedPic: true,
},
leaderboard: {
source: 'kw',
tabId: 'kwbiaosb',
},
songList: {
source: 'kg',
sortId: '5',
tagInfo: {
name: '默认',
id: null,
},
},
odc: {
isAutoClearSearchInput: false,
},
search: {
searchSource: 'kw',
tempSearchSource: 'kw',
},
network: {
proxy: {
enable: false,
host: '',
port: '',
username: '',
password: '',
},
},
themeId: 0,
sourceId: 'kw',
apiSource: 'messoer',
apiSource: 'test',
randomAnimate: true,
ignoreVersion: null,
}
const overwriteSetting = {
version: defaultVersion,
sourceId: 'kw',
}
@@ -198,6 +234,7 @@ export const updateSetting = setting => {
objectDeepMerge(defaultSetting, overwriteSetting)
setting = defaultSetting
}
if (setting.apiSource != 'temp') setting.apiSource = 'test' // 强制设置回 test 接口源
return setting
}
@@ -209,3 +246,126 @@ export const openUrl = url => {
shell.openExternal(url)
}
/**
* 设置标题
*/
let dom_title = document.getElementsByTagName('title')[0]
export const setTitle = title => {
dom_title.innerText = title || '洛雪音乐助手'
}
/**
* 创建 MD5 hash
* @param {*} str
*/
export const toMD5 = str => crypto.createHash('md5').update(str).digest('hex')
/**
* 复制文本到剪贴板
* @param {*} str
*/
export const clipboardWriteText = str => clipboard.writeText(str)
/**
* 设置音频 meta 信息
* @param {*} filePath
* @param {*} meta
*/
export const setMeta = (filePath, meta) => {
rendererSend('setMusicMeta', { filePath, meta })
}
/**
* 保存歌词文件
* @param {*} filePath
* @param {*} lrc
*/
export const saveLrc = (filePath, lrc) => {
fs.writeFile(filePath, lrc, 'utf8', err => {
if (err) console.log(err)
})
}
/**
* 生成节流函数
* @param {*} fn
* @param {*} delay
*/
export const throttle = (fn, delay = 100) => {
let timer = null
let _args = null
return function(...args) {
_args = args
if (timer) return
timer = setTimeout(() => {
timer = null
fn.apply(this, _args)
}, delay)
}
}
/**
* 生成防抖函数
* @param {*} fn
* @param {*} delay
*/
export const debounce = (fn, delay = 100) => {
let timer = null
let _args = null
return function(...args) {
_args = args
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
timer = null
fn.apply(this, _args)
}, delay)
}
}
const async_removeItem = (arr, num, callback) => window.requestAnimationFrame(() => {
let len = arr.length
if (len > num) {
arr.splice(0, num)
return async_removeItem(arr, num, callback)
} else {
arr.splice(0, len)
return callback()
}
})
const async_addItem = (arr, newArr, num, callback) => window.requestAnimationFrame(() => {
let len = newArr.length
if (len > num) {
arr.push(...newArr.splice(0, num))
return async_addItem(arr, newArr, num, callback)
} else {
arr.push(...newArr.splice(0, len))
return callback()
}
})
/**
* 异步设置数组
* @param {*} from 原数组
* @param {*} to 设置后的数组内容
* @param {*} num 每次设置的个数
*/
export const asyncSetArray = (from, to, num = 100) => new Promise(resolve => {
async_removeItem(from, num, () => {
async_addItem(from, Array.from(to), num, () => {
resolve()
})
})
})
/**
* 获取缓存大小
* @param {*} win
*/
export const getCacheSize = () => remote.getCurrentWindow().webContents.session.getCacheSize()
/**
* 清除缓存
* @param {*} win
*/
export const clearCache = () => remote.getCurrentWindow().webContents.session.clearCache()

View File

@@ -1,6 +1,7 @@
export const requestMsg = {
fail: '请求异常😮,可以多试几次,若还是不行就换一首吧。。。',
unachievable: '哦No😱...接口无法访问了!已帮你切换到临时接口,重试下看能不能播放吧~',
notConnectNetwork: '无法连接网络',
unachievable: '哦No😱...接口无法访问了!',
// unachievable: '哦No😱...接口无法访问了!已帮你切换到临时接口,重试下看能不能播放吧~',
notConnectNetwork: '无法连接到服务器',
cancelRequest: '取消http请求',
}

View File

@@ -1,24 +1,38 @@
import kw_api_messoer from './kw/api-messoer'
import kw_api_temp from './kw/api-temp'
import tx_api_messoer from './tx/api-messoer'
import kg_api_messoer from './kg/api-messoer'
import wy_api_messoer from './wy/api-messoer'
import bd_api_messoer from './bd/api-messoer'
import kw_api_test from './kw/api-test'
import tx_api_test from './tx/api-test'
import kg_api_test from './kg/api-test'
import wy_api_test from './wy/api-test'
import bd_api_test from './bd/api-test'
import mg_api_test from './mg/api-test'
// import kw_api_internal from './kw/api-internal'
// import tx_api_internal from './tx/api-internal'
// import kg_api_internal from './kg/api-internal'
// import wy_api_internal from './wy/api-internal'
// import bd_api_internal from './bd/api-internal'
const apis = {
kw_api_messoer,
tx_api_messoer,
kg_api_messoer,
wy_api_messoer,
bd_api_messoer,
kw_api_test,
tx_api_test,
kg_api_test,
wy_api_test,
bd_api_test,
mg_api_test,
// kw_api_internal,
// tx_api_internal,
// kg_api_internal,
// wy_api_internal,
// bd_api_internal,
kw_api_temp,
}
const getAPI = source => {
switch (window.globalObj.apiSource) {
case 'messoer':
return apis[`${source}_api_messoer`]
// case 'messoer':
// return apis[`${source}_api_messoer`]
case 'test':
return apis[`${source}_api_test`]
case 'temp':
return apis[`${source}_api_temp`]
}
@@ -34,6 +48,8 @@ export default source => {
return getAPI('wy')
case 'bd':
return getAPI('bd')
case 'mg':
return getAPI('mg')
default:
return getAPI('kw')
}

View File

@@ -0,0 +1,44 @@
import { httpFatch } from '../../request'
import { requestMsg } from '../../message'
const api_internal = {
successCode: 2200,
// getMusicUrl(songInfo, type) {
// const requestObj = httpFatch(`http://play.taihe.com/data/music/songlink`, {
// method: 'post',
// headers: {
// Origin: 'http://play.taihe.com',
// },
// formData: {
// songIds: songInfo.songmid,
// },
// })
// requestObj.promise = requestObj.promise.then(({ body }) => {
// console.log(body)
// return Promise.reject()
// // if (body.error_code !== this.successCode) return this.getMusicUrl(songInfo, type)
// // return body.code === 200 ? Promise.resolve({ type, url: body.result.bitrate.file_link }) : Promise.reject(new Error(requestMsg.fail))
// })
// return requestObj
// },
getMusicInfo(songInfo, tryNum = 0) {
const requestObj = httpFatch(`http://tingapi.ting.baidu.com/v1/restserver/ting?from=webapp_music&method=baidu.ting.song.baseInfos&song_id=${songInfo.songmid}`)
requestObj.promise = requestObj.promise.then(({ body }) => {
if (body.error_code !== this.successCode) return tryNum > 5 ? Promise.reject(new Error(requestMsg.fail)) : this.getMusicInfo(songInfo, ++tryNum)
return body ? Promise.resolve(body.result.items[0]) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
getPic(songInfo) {
const requestObj = this.getMusicInfo(songInfo)
requestObj.promise = requestObj.promise.then(info => info.pic_premium)
return requestObj
},
getLyric(songInfo) {
const requestObj = this.getMusicInfo(songInfo)
requestObj.promise.then(info => httpFatch(info.lrclink).promise)
return requestObj
},
}
export default api_internal

View File

@@ -1,37 +0,0 @@
import { httpFatch } from '../../request'
import { requestMsg } from '../../message'
const api_messoer = {
getMusicUrl(songInfo, type) {
const requestObj = httpFatch(`https://v1.itooi.cn/baidu/url?id=${songInfo.songmid}&quality=${type.replace(/k$/, '')}&isRedirect=0`, {
method: 'get',
timeout: 5000,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
getPic(songInfo, size = '500') {
const requestObj = httpFatch(`https://v1.itooi.cn/baidu/pic?id=${songInfo.songmid}&imageSize=${size}&isRedirect=0`, {
method: 'get',
timeout: 5000,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve(body.data) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
getLyric(songInfo) {
const requestObj = httpFatch(`https://v1.itooi.cn/baidu/lrc?id=${songInfo.songmid}&isRedirect=0`, {
method: 'get',
timeout: 5000,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body ? Promise.resolve(body) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
}
export default api_messoer

View File

@@ -0,0 +1,20 @@
import { httpFetch } from '../../request'
import { requestMsg } from '../../message'
import { headers, timeout } from '../options'
const api_test = {
getMusicUrl(songInfo, type) {
const requestObj = httpFetch(`http://ts.tempmusic.tk/url/bd/${songInfo.songmid}/${type}`, {
method: 'get',
timeout,
headers,
family: 4,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 0 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
}
export default api_test

View File

@@ -1,18 +1,33 @@
import leaderboard from './leaderboard'
import api_source from '../api-source'
import musicInfo from './musicInfo'
import songList from './songList'
import { httpFetch } from '../../request'
import musicSearch from './musicSearch'
const bd = {
leaderboard,
songList,
musicSearch,
getMusicUrl(songInfo, type) {
return api_source('bd').getMusicUrl(songInfo, type)
},
getLyric(songInfo) {
return api_source('bd').getLyric(songInfo)
},
getPic(songInfo) {
return api_source('bd').getPic(songInfo)
const requestObj = this.getMusicInfo(songInfo)
requestObj.promise = requestObj.promise.then(info => info.pic_premium)
return requestObj
},
getLyric(songInfo) {
const requestObj = this.getMusicInfo(songInfo)
requestObj.promise = requestObj.promise.then(info => httpFetch(info.lrclink).promise.then(resp => resp.body))
return requestObj
},
// getLyric(songInfo) {
// return api_source('bd').getLyric(songInfo)
// },
// getPic(songInfo) {
// return api_source('bd').getPic(songInfo)
// },
getMusicInfo(songInfo) {
return musicInfo.getMusicInfo(songInfo.songmid)
},

View File

@@ -1,4 +1,4 @@
import { httpFatch } from '../../request'
import { httpFetch } from '../../request'
// import { formatPlayTime } from '../../index'
// import jshtmlencode from 'js-htmlencode'
@@ -66,7 +66,7 @@ export default {
requestObj: null,
getData(url) {
if (this.requestObj) this.requestObj.cancelHttp()
this.requestObj = httpFatch(url)
this.requestObj = httpFetch(url)
return this.requestObj.promise
},
filterData(rawList) {
@@ -85,7 +85,7 @@ export default {
size,
}
types.push({ type: 'flac', size })
_types['flac'] = {
_types.flac = {
size,
}
}
@@ -126,6 +126,7 @@ export default {
list,
limit: this.limit,
page: parseInt(info[3]),
source: 'bd',
}
})
},

View File

@@ -1,10 +1,19 @@
import { httpFatch } from '../../request'
import { httpFetch } from '../../request'
export default {
cache: {},
getMusicInfo(songmid) {
const requestObj = httpFatch(`https://musicapi.qianqian.com/v1/restserver/ting?method=baidu.ting.song.getSongLink&format=json&from=bmpc&version=1.0.0&version_d=11.1.6.0&songid=${songmid}&type=1&res=1&s_protocol=1&aac=2&project=tpass`)
if (this.cache[songmid]) {
return { promise: Promise.resolve(this.cache[songmid]) }
}
const requestObj = httpFetch(`https://musicapi.qianqian.com/v1/restserver/ting?method=baidu.ting.song.getSongLink&format=json&from=bmpc&version=1.0.0&version_d=11.1.6.0&songid=${songmid}&type=1&res=1&s_protocol=1&aac=2&project=tpass`)
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.error_code == 22000 ? body.reqult.songinfo : Promise.reject(new Error('获取音乐信息失败'))
// console.log(body)
if (body.error_code == 22000) {
this.cache[songmid] = body.result.songinfo
return body.result.songinfo
}
return Promise.reject(new Error('获取音乐信息失败'))
})
return requestObj
},

View File

@@ -0,0 +1,89 @@
// import '../../polyfill/array.find'
// import jshtmlencode from 'js-htmlencode'
import { httpFetch } from '../../request'
import { formatPlayTime } from '../../index'
// import { debug } from '../../utils/env'
// import { formatSinger } from './util'
let searchRequest
export default {
limit: 30,
total: 0,
page: 0,
allPage: 1,
musicSearch(str, page) {
if (searchRequest && searchRequest.cancelHttp) searchRequest.cancelHttp()
searchRequest = httpFetch(`http://tingapi.ting.baidu.com/v1/restserver/ting?from=android&version=5.6.5.6&method=baidu.ting.search.merge&format=json&query=${encodeURIComponent(str)}&page_no=${page}&page_size=${this.limit}&type=0&data_source=0&use_cluster=1`)
return searchRequest.promise.then(({ body }) => body)
},
handleResult(rawData) {
let ids = new Set()
const list = []
if (!rawData) return list
rawData.forEach(item => {
if (ids.has(item.song_id)) return
ids.add(item.song_id)
const types = []
const _types = {}
let size = null
let itemTypes = item.all_rate.split(',')
if (itemTypes.includes('128')) {
types.push({ type: '128k', size })
_types['128k'] = {
size,
}
}
if (itemTypes.includes('320')) {
types.push({ type: '320k', size })
_types['320k'] = {
size,
}
}
if (itemTypes.includes('flac')) {
types.push({ type: 'flac', size })
_types.flac = {
size,
}
}
// types.reverse()
list.push({
singer: item.author.replace(',', '、'),
name: item.title,
albumName: item.album_title,
albumId: item.album_id,
source: 'bd',
interval: formatPlayTime(parseInt(item.file_duration)),
songmid: item.song_id,
img: null,
lrc: null,
types,
_types,
typeUrl: {},
})
})
return list
},
search(str, page = 1, { limit } = {}) {
if (limit != null) this.limit = limit
return this.musicSearch(str, page).then(result => {
if (!result || result.error_code !== 22000) return this.search(str, page, { limit })
let list = this.handleResult(result.result.song_info.song_list)
if (list == null) return this.search(str, page, { limit })
this.total = result.result.song_info.total
this.page = page
this.allPage = Math.ceil(this.total / this.limit)
return Promise.resolve({
list,
allPage: this.allPage,
limit: this.limit,
total: this.total,
source: 'bd',
})
})
},
}

View File

@@ -0,0 +1,261 @@
import { httpFetch } from '../../request'
import { formatPlayTime, toMD5 } from '../../index'
import CryptoJS from 'crypto-js'
export default {
_requestObj_tags: null,
_requestObj_list: null,
_requestObj_listRecommend: null,
_requestObj_listDetail: null,
limit_list: 20,
limit_song: 1000,
successCode: 22000,
sortList: [
{
name: '最热',
id: '1',
},
{
name: '最新',
id: '0',
},
],
aesPassEncod(jsonData) {
let timestamp = Math.floor(Date.now() / 1000)
let privateKey = toMD5('baidu_taihe_music_secret_key' + timestamp).substr(8, 16)
let key = CryptoJS.enc.Utf8.parse(privateKey)
let iv = CryptoJS.enc.Utf8.parse(privateKey)
let arrData = []
let strData = ''
for (let key in jsonData) arrData.push(key)
arrData.sort()
for (let i = 0; i < arrData.length; i++) {
let key = arrData[i]
strData +=
(i === 0 ? '' : '&') + key + '=' + encodeURIComponent(jsonData[key])
}
let JsonFormatter = {
stringify(cipherParams) {
let jsonObj = {
ct: cipherParams.ciphertext.toString(CryptoJS.enc.Base64),
}
if (cipherParams.iv) {
jsonObj.iv = cipherParams.iv.toString()
}
if (cipherParams.salt) {
jsonObj.s = cipherParams.salt.toString()
}
return jsonObj
},
parse(jsonStr) {
let jsonObj = JSON.parse(jsonStr)
let cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Base64.parse(jsonObj.ct),
})
if (jsonObj.iv) {
cipherParams.iv = CryptoJS.enc.Hex.parse(jsonObj.iv)
}
if (jsonObj.s) {
cipherParams.salt = CryptoJS.enc.Hex.parse(jsonObj.s)
}
return cipherParams
},
}
let encrypted = CryptoJS.AES.encrypt(strData, key, {
iv: iv,
blockSize: 16,
mode: CryptoJS.mode.CBC,
format: JsonFormatter,
})
let ciphertext = encrypted.toString().ct
let sign = toMD5('baidu_taihe_music' + ciphertext + timestamp)
let jsonRet = {
timestamp: timestamp,
param: ciphertext,
sign: sign,
}
return jsonRet
},
createUrl(param, method) {
let data = this.aesPassEncod(param)
return `http://musicmini.qianqian.com/v1/restserver/ting?method=${method}&time=${Date.now()}&timestamp=${data.timestamp}&param=${data.param}&sign=${data.sign}`
},
getTagsUrl() {
return this.createUrl({
from: 'qianqianmini',
type: 'diy',
version: '10.1.8',
}, 'baidu.ting.ugcdiy.getChannels')
},
getListUrl(sortType, tagName, page) {
return this.createUrl({
channelname: tagName || '全部',
from: 'qianqianmini',
offset: (page - 1) * this.limit_list,
order_type: sortType,
size: this.limit_list,
version: '10.1.8',
}, 'baidu.ting.ugcdiy.getChanneldiy')
},
getListDetailUrl(list_id, page) {
return this.createUrl({
list_id,
offset: (page - 1) * this.limit_song,
size: this.limit_song,
withcount: '1',
withsong: '1',
}, 'baidu.ting.ugcdiy.getBaseInfo')
},
// 获取标签
getTags(tryNum = 0) {
if (this._requestObj_tags) this._requestObj_tags.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_tags = httpFetch(this.getTagsUrl())
return this._requestObj_tags.promise.then(({ body }) => {
if (body.error_code !== this.successCode) return this.getTags(++tryNum)
return {
hotTag: this.filterInfoHotTag(body.result.hot),
tags: this.filterTagInfo(body.result.tags),
source: 'bd',
}
})
},
filterInfoHotTag(rawList) {
return rawList.map(item => ({
name: item,
id: item,
source: 'bd',
}))
},
filterTagInfo(rawList) {
return rawList.map(type => ({
name: type.first,
list: type.second.map(item => ({
parent_id: type.first,
parent_name: type.first,
id: item,
name: item,
source: 'bd',
})),
}))
},
// 获取列表数据
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)
)
return this._requestObj_list.promise.then(({ body }) => {
if (body.error_code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
return {
list: this.filterList(body.diyInfo),
total: body.nums,
page,
limit: this.limit_list,
source: 'bd',
}
})
},
/**
* 格式化播放数量
* @param {*} num
*/
formatPlayCount(num) {
if (num > 100000000) return parseInt(num / 10000000) / 10 + '亿'
if (num > 10000) return parseInt(num / 1000) / 10 + '万'
return num
},
filterList(rawData) {
return rawData.map(item => ({
play_count: this.formatPlayCount(item.listen_num),
id: item.list_id,
author: item.username,
name: item.title,
// time: item.publish_time,
img: item.list_pic_large || item.list_pic,
grade: item.grade,
desc: item.desc || item.tag,
source: 'bd',
}))
},
// 获取歌曲列表内的音乐
getListDetail(id, page, tryNum = 0) {
if (this._requestObj_listDetail) {
this._requestObj_listDetail.cancelHttp()
}
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_listDetail = httpFetch(this.getListDetailUrl(id, page))
return this._requestObj_listDetail.promise.then(({ body }) => {
if (body.error_code !== this.successCode) return this.getListDetail(id, page, ++tryNum)
let listData = this.filterData(body.result.songlist)
return {
list: listData,
page,
limit: this.limit_song,
total: body.result.song_num,
source: 'bd',
info: {
name: body.result.info.list_title,
img: body.result.info.list_pic,
desc: body.result.info.list_desc,
author: body.result.info.userinfo.username,
play_count: this.formatPlayCount(body.result.listen_num),
},
}
})
},
filterData(rawList) {
// console.log(rawList)
return rawList.map(item => {
const types = []
const _types = {}
let size = null
let itemTypes = item.all_rate.split(',')
if (itemTypes.includes('128')) {
types.push({ type: '128k', size })
_types['128k'] = {
size,
}
}
if (itemTypes.includes('320')) {
types.push({ type: '320k', size })
_types['320k'] = {
size,
}
}
if (itemTypes.includes('flac')) {
types.push({ type: 'flac', size })
_types.flac = {
size,
}
}
// types.reverse()
return {
singer: item.author.replace(',', '、'),
name: item.title,
albumName: item.album_title,
albumId: item.album_id,
source: 'bd',
interval: formatPlayTime(parseInt(item.file_duration)),
songmid: item.song_id,
img: item.pic_s500,
lrc: null,
types,
_types,
typeUrl: {},
}
})
},
}
// getList
// getTags
// getListDetail

View File

@@ -2,6 +2,7 @@ import kw from './kw'
import kg from './kg'
import tx from './tx'
import wy from './wy'
import mg from './mg'
import bd from './bd'
export default {
sources: [
@@ -21,6 +22,10 @@ export default {
name: '网易音乐',
id: 'wy',
},
{
name: '咪咕音乐',
id: 'mg',
},
{
name: '百度音乐',
id: 'bd',
@@ -30,5 +35,6 @@ export default {
kg,
tx,
wy,
mg,
bd,
}

View File

@@ -1,11 +1,13 @@
import { httpFatch } from '../../request'
import { requestMsg } from '../../message'
import { headers, timeout } from '../options'
const api_messoer = {
getMusicUrl(songInfo, type) {
const requestObj = httpFatch(`https://v1.itooi.cn/kugou/url?id=${songInfo._types[type].hash}&quality=${type.replace(/k$/, '')}&isRedirect=0`, {
method: 'get',
timeout: 5000,
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
@@ -15,7 +17,8 @@ const api_messoer = {
getPic(songInfo) {
const requestObj = httpFatch(`https://v1.itooi.cn/kugou/pic?id=${songInfo.hash}&isRedirect=0`, {
method: 'get',
timeout: 5000,
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve(body.data) : Promise.reject(new Error(requestMsg.fail))
@@ -25,7 +28,8 @@ const api_messoer = {
getLyric(songInfo) {
const requestObj = httpFatch(`https://v1.itooi.cn/kugou/lrc?id=${songInfo.hash}&isRedirect=0`, {
method: 'get',
timeout: 5000,
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body ? Promise.resolve(body) : Promise.reject(new Error(requestMsg.fail))

View File

@@ -0,0 +1,44 @@
import { httpFetch } from '../../request'
import { requestMsg } from '../../message'
import { headers, timeout } from '../options'
const api_test = {
getMusicUrl(songInfo, type) {
const requestObj = httpFetch(`http://ts.tempmusic.tk/url/kg/${songInfo._types[type].hash}/${type}`, {
method: 'get',
timeout,
headers,
family: 4,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 0 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
getPic(songInfo) {
const requestObj = httpFetch(`http://ts.tempmusic.tk/pic/kg/${songInfo.hash}`, {
method: 'get',
timeout,
headers,
family: 4,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 0 ? Promise.resolve(body.data) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
getLyric(songInfo) {
const requestObj = httpFetch(`http://ts.tempmusic.tk/lrc/kg/${songInfo.hash}`, {
method: 'get',
timeout,
headers,
family: 4,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 0 ? Promise.resolve(body.data) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
}
export default api_test

View File

@@ -1,18 +1,29 @@
import leaderboard from './leaderboard'
import api_source from '../api-source'
import songList from './songList'
import musicSearch from './musicSearch'
import pic from './pic'
import lyric from './lyric'
const kg = {
leaderboard,
songList,
musicSearch,
getMusicUrl(songInfo, type) {
return api_source('kg').getMusicUrl(songInfo, type)
},
getLyric(songInfo) {
return api_source('kg').getLyric(songInfo)
return lyric.getLyric(songInfo)
},
// getLyric(songInfo) {
// return api_source('kg').getLyric(songInfo)
// },
getPic(songInfo) {
return api_source('kg').getPic(songInfo)
return pic.getPic(songInfo)
},
// getPic(songInfo) {
// return api_source('kg').getPic(songInfo)
// },
}
export default kg

View File

@@ -10,7 +10,7 @@ export default {
},
{
id: 'kgwlhgb',
name: '网络红歌榜',
name: '网络榜',
bangid: '23784',
},
{
@@ -30,29 +30,29 @@ export default {
},
{
id: 'kggfjqb',
name: '古风金曲榜',
name: '古风榜',
bangid: '33161',
},
{
id: 'kgyyjqb',
name: '粤语金曲榜',
name: '粤语榜',
bangid: '33165',
},
{
id: 'kgomjqb',
name: '欧美金曲榜',
name: '欧美榜',
bangid: '33166',
},
// {
// id: 'kgdyrgb',
// name: '电音热歌榜',
// bangid: '33160',
// },
// {
// id: 'kgjdrgb',
// name: 'DJ热歌榜',
// bangid: '24971',
// },
{
id: 'kgdyrgb',
name: '电音榜',
bangid: '33160',
},
{
id: 'kgjdrgb',
name: 'DJ热歌榜',
bangid: '24971',
},
// {
// id: 'kghyxgb',
// name: '华语新歌榜',
@@ -159,6 +159,7 @@ export default {
list: listData,
limit,
page,
source: 'kg',
}
})
},

View File

@@ -0,0 +1,33 @@
import { httpFetch } from '../../request'
export default {
getIntv(interval) {
let intvArr = interval.split(':')
let intv = 0
let unit = 1
while (intvArr.length) {
intv += (intvArr.pop()) * unit
unit *= 60
}
return parseInt(intv)
},
getLyric(songInfo, tryNum = 0) {
let requestObj = httpFetch(`http://m.kugou.com/app/i/krc.php?cmd=100&keyword=${encodeURIComponent(songInfo.name)}&hash=${songInfo.hash}&timelength=${songInfo._interval || this.getIntv(songInfo.interval)}&d=0.38664927426725626`, {
headers: {
'KG-RC': 1,
'KG-THash': 'expand_search_manager.cpp:852736169:451',
'User-Agent': 'KuGou2012-9020-ExpandSearchManager',
},
})
requestObj.promise = requestObj.promise.then(({ body, statusCode }) => {
if (statusCode !== 200) {
if (tryNum > 5) return Promise.reject('歌词获取失败')
let tryRequestObj = this.getLyric(songInfo, ++tryNum)
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
return tryRequestObj.promise
}
return body
})
return requestObj
},
}

View File

@@ -0,0 +1,93 @@
// import '../../polyfill/array.find'
// import jshtmlencode from 'js-htmlencode'
import { httpFetch } from '../../request'
import { formatPlayTime, sizeFormate } from '../../index'
// import { debug } from '../../utils/env'
// import { formatSinger } from './util'
let searchRequest
export default {
limit: 30,
total: 0,
page: 0,
allPage: 1,
musicSearch(str, page) {
if (searchRequest && searchRequest.cancelHttp) searchRequest.cancelHttp()
searchRequest = httpFetch(`http://ioscdn.kugou.com/api/v3/search/song?keyword=${encodeURIComponent(str)}&page=${page}&pagesize=${this.limit}&showtype=10&plat=2&version=7910&tag=1&correct=1&privilege=1&sver=5`)
return searchRequest.promise.then(({ body }) => body)
},
handleResult(rawData) {
// console.log(rawData)
let ids = new Set()
const list = []
rawData.forEach(item => {
if (ids.has(item.audio_id)) return
ids.add(item.audio_id)
const types = []
const _types = {}
if (item.filesize !== 0) {
let size = sizeFormate(item.filesize)
types.push({ type: '128k', size, hash: item.hash })
_types['128k'] = {
size,
hash: item.hash,
}
}
if (item['320filesize'] !== 0) {
let size = sizeFormate(item['320filesize'])
types.push({ type: '320k', size, hash: item['320hash'] })
_types['320k'] = {
size,
hash: item['320hash'],
}
}
if (item.sqfilesize !== 0) {
let size = sizeFormate(item.sqfilesize)
types.push({ type: 'flac', size, hash: item.sqhash })
_types.flac = {
size,
hash: item.sqhash,
}
}
list.push({
singer: item.singername,
name: item.songname,
albumName: item.album_name,
albumId: item.album_id,
songmid: item.audio_id,
source: 'kg',
interval: formatPlayTime(item.duration),
_interval: item.duration,
img: null,
lrc: null,
hash: item.hash,
types,
_types,
typeUrl: {},
})
})
return list
},
search(str, page = 1, { limit } = {}) {
if (limit != null) this.limit = limit
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
return this.musicSearch(str, page).then(result => {
if (!result || result.errcode !== 0) return this.search(str, page, { limit })
let list = this.handleResult(result.data.info)
if (list == null) return this.search(str, page, { limit })
this.total = result.data.total
this.page = page
this.allPage = Math.ceil(this.total / this.limit)
return Promise.resolve({
list,
allPage: this.allPage,
limit: this.limit,
total: this.total,
source: 'kg',
})
})
},
}

View File

@@ -0,0 +1,44 @@
import { httpFetch } from '../../request'
export default {
getPic(songInfo) {
const requestObj = httpFetch(
'http://media.store.kugou.com/v1/get_res_privilege',
{
method: 'POST',
headers: {
'KG-RC': 1,
'KG-THash': 'expand_search_manager.cpp:852736169:451',
'User-Agent': 'KuGou2012-9020-ExpandSearchManager',
},
body: {
appid: 1001,
area_code: '1',
behavior: 'play',
clientver: '9020',
need_hash_offset: 1,
relate: 1,
resource: [
{
album_audio_id: songInfo.songmid,
album_id: songInfo.albumId,
hash: songInfo.hash,
id: 0,
name: `${songInfo.singer} - ${songInfo.name}.mp3`,
type: 'audio',
},
],
token: '',
userid: 2626431536,
vip: 1,
},
}
)
requestObj.promise = requestObj.promise.then(({ body }) => {
if (body.error_code !== 0) return Promise.reject('图片获取失败')
let info = body.data[0].info
return info.imgsize ? info.image.replace('{size}', info.imgsize[0]) : info.image
})
return requestObj
},
}

View File

@@ -0,0 +1,286 @@
import { httpFetch } from '../../request'
import { formatPlayTime, sizeFormate } from '../../index'
export default {
_requestObj_tags: null,
_requestObj_listInfo: null,
_requestObj_list: null,
_requestObj_listRecommend: null,
_requestObj_listDetail: null,
currentTagInfo: {
id: undefined,
info: undefined,
},
sortList: [
{
name: '推荐',
id: '5',
},
{
name: '最热',
id: '6',
},
{
name: '最新',
id: '7',
},
{
name: '热藏',
id: '3',
},
{
name: '飙升',
id: '8',
},
],
regExps: {
listData: /global\.data = (\[.+\]);/,
listInfo: /global = {[\s\S]+?name: "(.+)"[\s\S]+?pic: "(.+)"[\s\S]+?};/,
},
getInfoUrl(tagId) {
return tagId
? `http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_smarty=1&cdn=cdn&t=5&c=${tagId}`
: 'http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_smarty=1&'
},
getSongListUrl(sortId, tagId, page) {
if (tagId == null) tagId = ''
return `http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_ajax=1&cdn=cdn&t=${sortId}&c=${tagId}&p=${page}`
},
getSongListDetailUrl(id) {
return `http://www2.kugou.kugou.com/yueku/v9/special/single/${id}-5-9999.html`
},
/**
* 格式化播放数量
* @param {*} num
*/
formatPlayCount(num) {
if (num > 100000000) return parseInt(num / 10000000) / 10 + '亿'
if (num > 10000) return parseInt(num / 1000) / 10 + '万'
return num
},
filterInfoHotTag(rawData) {
const result = []
if (rawData.status !== 1) return result
for (const key of Object.keys(rawData.data)) {
let tag = rawData.data[key]
result.push({
id: tag.id,
name: tag.special_name,
})
}
return result
},
filterTagInfo(rawData) {
const result = []
for (const name of Object.keys(rawData)) {
result.push({
name,
list: rawData[name].data.map(tag => ({
parent_id: tag.parent_id,
parent_name: tag.pname,
id: tag.id,
name: tag.name,
source: 'kg',
})),
})
}
return result
},
getSongList(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.getSongListUrl(sortId, tagId, page)
)
return this._requestObj_list.promise.then(({ body }) => {
if (body.status !== 1) return this.getSongList(sortId, tagId, page, ++tryNum)
return this.filterList(body.special_db)
})
},
getSongListRecommend(tryNum = 0) {
if (this._requestObj_listRecommend) this._requestObj_listRecommend.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_listRecommend = httpFetch(
'http://everydayrec.service.kugou.com/guess_special_recommend',
{
method: 'post',
headers: {
'User-Agent': 'KuGou2012-8275-web_browser_event_handler',
},
body: {
appid: 1001,
clienttime: 1566798337219,
clientver: 8275,
key: 'f1f93580115bb106680d2375f8032d96',
mid: '21511157a05844bd085308bc76ef3343',
platform: 'pc',
userid: '262643156',
return_min: 6,
return_max: 15,
},
}
)
return this._requestObj_listRecommend.promise.then(({ body }) => {
if (body.status !== 1) return this.getSongListRecommend(++tryNum)
return this.filterList(body.data.special_list)
})
},
filterList(rawData) {
return rawData.map(item => ({
play_count: item.total_play_count || this.formatPlayCount(item.play_count),
id: item.specialid,
author: item.nickname,
name: item.specialname,
time: item.publish_time || item.publishtime,
img: item.img || item.imgurl,
grade: item.grade,
desc: item.intro,
source: 'kg',
}))
},
getListDetail(id, page, tryNum = 0) { // 获取歌曲列表内的音乐
if (this._requestObj_listDetail) this._requestObj_listDetail.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_listDetail = httpFetch(this.getSongListDetailUrl(id))
return this._requestObj_listDetail.promise.then(({ body }) => {
let listData = body.match(this.regExps.listData)
let listInfo = body.match(this.regExps.listInfo)
if (!listData) return this.getListDetail(id, page, ++tryNum)
listData = this.filterData(JSON.parse(listData[1]))
let name
let pic
if (listInfo) {
name = listInfo[1]
pic = listInfo[2]
}
return {
list: listData,
page: 1,
limit: 10000,
total: listData.length,
source: 'kg',
info: {
name,
img: pic,
// desc: body.result.info.list_desc,
// author: body.result.info.userinfo.username,
// play_count: this.formatPlayCount(body.result.listen_num),
},
}
})
},
filterData(rawList) {
// console.log(rawList)
return rawList.map(item => {
const types = []
const _types = {}
if (item.filesize !== 0) {
let size = sizeFormate(item.filesize)
types.push({ type: '128k', size, hash: item.hash })
_types['128k'] = {
size,
hash: item.hash,
}
}
if (item.filesize_320 !== 0) {
let size = sizeFormate(item.filesize_320)
types.push({ type: '320k', size, hash: item.hash_320 })
_types['320k'] = {
size,
hash: item.hash_320,
}
}
if (item.filesize_ape !== 0) {
let size = sizeFormate(item.filesize_ape)
types.push({ type: 'ape', size, hash: item.hash_ape })
_types.ape = {
size,
hash: item.hash_ape,
}
}
if (item.filesize_flac !== 0) {
let size = sizeFormate(item.filesize_flac)
types.push({ type: 'flac', size, hash: item.hash_flac })
_types.flac = {
size,
hash: item.hash_flac,
}
}
return {
singer: item.singername,
name: item.songname,
albumName: item.album_name,
albumId: item.album_id,
songmid: item.audio_id,
source: 'kg',
interval: formatPlayTime(item.duration / 1000),
img: null,
lrc: null,
hash: item.hash,
types,
_types,
typeUrl: {},
}
})
},
// 获取列表信息
getListInfo(tagId, tryNum = 0) {
if (this._requestObj_listInfo) this._requestObj_listInfo.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_listInfo = httpFetch(this.getInfoUrl(tagId))
return this._requestObj_listInfo.promise.then(({ body }) => {
if (body.status !== 1) return this.getListInfo(tagId, ++tryNum)
return {
limit: body.data.params.pagesize,
page: body.data.params.p,
total: body.data.params.total,
source: 'kg',
}
})
},
// 获取列表数据
getList(sortId, tagId, page) {
let tasks = [this.getSongList(sortId, tagId, page)]
tasks.push(
this.currentTagInfo.id === tagId
? Promise.resolve(this.currentTagInfo.info)
: this.getListInfo(tagId).then(info => {
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]) => {
if (recommendList) list.unshift(...recommendList)
return {
list,
...info,
}
})
},
// 获取标签
getTags(tryNum = 0) {
if (this._requestObj_tags) this._requestObj_tags.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_tags = httpFetch(this.getInfoUrl())
return this._requestObj_tags.promise.then(({ body }) => {
if (body.status !== 1) return this.getTags(++tryNum)
return {
hotTag: this.filterInfoHotTag(body.data.hotTag),
tags: this.filterTagInfo(body.data.tagids),
source: 'kg',
}
})
},
}
// getList
// getTags
// getListDetail

View File

@@ -1,11 +1,13 @@
import { httpFatch } from '../../request'
import { requestMsg } from '../../message'
import { headers, timeout } from '../options'
const api_messoer = {
getMusicUrl(songInfo, type) {
const requestObj = httpFatch(`https://v1.itooi.cn/kuwo/url?id=${songInfo.songmid}&quality=${type.replace(/k$/, '')}&isRedirect=0`, {
method: 'get',
timeout: 5000,
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
@@ -15,7 +17,8 @@ const api_messoer = {
getPic(songInfo) {
const requestObj = httpFatch(`https://v1.itooi.cn/kuwo/pic?id=${songInfo.songmid}&isRedirect=0`, {
method: 'get',
timeout: 5000,
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve(body.data) : Promise.reject(new Error(requestMsg.fail))

View File

@@ -1,24 +1,19 @@
import { httpFatch } from '../../request'
import { httpFetch } from '../../request'
import { headers, timeout } from '../options'
const api_temp = {
getMusicUrl(songInfo, type) {
const requestObj = httpFatch(`https://www.stsky.cn/api/temp/getMusicUrl.php?id=${songInfo.songmid}&type=${type}`, {
const requestObj = httpFetch(`http://tm.tempmusic.tk/url/kw/${songInfo.songmid}/${type}`, {
method: 'get',
headers,
timeout,
family: 4,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 0 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(body.msg))
})
return requestObj
},
getPic(songInfo) {
const requestObj = httpFatch(`https://www.stsky.cn/api/temp/getPic.php?size=320&songmid=${songInfo.songmid}`, {
method: 'get',
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 0 ? Promise.resolve(body.data) : Promise.reject(new Error(body.msg))
})
return requestObj
},
}
export default api_temp

View File

@@ -0,0 +1,31 @@
import { httpFetch } from '../../request'
import { requestMsg } from '../../message'
import { headers, timeout } from '../options'
const api_test = {
// getMusicUrl(songInfo, type) {
// const requestObj = httpFetch(`http://45.32.53.128:3002/m/kw/u/${songInfo.songmid}/${type}`, {
// method: 'get',
// headers,
// timeout,
// })
// requestObj.promise = requestObj.promise.then(({ body }) => {
// return body.code === 0 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(body.msg))
// })
// return requestObj
// },
getMusicUrl(songInfo, type) {
const requestObj = httpFetch(`http://ts.tempmusic.tk/url/kw/${songInfo.songmid}/${type}`, {
method: 'get',
timeout,
headers,
family: 4,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 0 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
}
export default api_test

View File

@@ -4,7 +4,9 @@ import musicSearch from './musicSearch'
import { formatSinger } from './util'
import leaderboard from './leaderboard'
import lyric from './lyric'
import pic from './pic'
import api_source from '../api-source'
import songList from './songList'
const kw = {
_musicInfoRequestObj: null,
@@ -31,6 +33,7 @@ const kw = {
tempSearch,
musicSearch,
leaderboard,
songList,
getLyric(songInfo) {
// let singer = songInfo.singer.indexOf('、') > -1 ? songInfo.singer.split('、')[0] : songInfo.singer
return lyric.getLyric(songInfo.songmid)
@@ -91,7 +94,7 @@ const kw = {
},
getPic(songInfo) {
return api_source('kw').getPic(songInfo)
return pic.getPic(songInfo)
},
}

View File

@@ -1,5 +1,7 @@
import { httpGet, cancelHttp } from '../../request'
import { formatPlayTime } from '../../index'
import { formatPlayTime, decodeName } from '../../index'
import { formatSinger } from './util'
export default {
list: [
@@ -117,35 +119,35 @@ export default {
size: null,
}
}
if (formats.indexOf('MP3192')) {
types.push({ type: '192k', size: null })
_types['192k'] = {
size: null,
}
}
// if (formats.indexOf('MP3192')) {
// types.push({ type: '192k', size: null })
// _types['192k'] = {
// size: null,
// }
// }
if (formats.indexOf('MP3H')) {
types.push({ type: '320k', size: null })
_types['320k'] = {
size: null,
}
}
if (formats.indexOf('AL')) {
types.push({ type: 'ape', size: null })
_types['ape'] = {
size: null,
}
}
// if (formats.indexOf('AL')) {
// types.push({ type: 'ape', size: null })
// _types.ape = {
// size: null,
// }
// }
if (formats.indexOf('ALFLAC')) {
types.push({ type: 'flac', size: null })
_types['flac'] = {
_types.flac = {
size: null,
}
}
// types.reverse()
return {
singer: item.artist,
name: item.name,
albumName: item.album,
singer: formatSinger(decodeName(item.artist)),
name: decodeName(item.name),
albumName: decodeName(item.album),
albumId: item.albumid,
songmid: item.id,
source: 'kw',
@@ -185,6 +187,7 @@ export default {
list,
limit: this.limit,
page,
source: 'kw',
}
})
},

View File

@@ -1,4 +1,4 @@
import { httpFatch } from '../../request'
import { httpFetch } from '../../request'
export default {
formatTime(time) {
@@ -10,7 +10,7 @@ export default {
return `[ti:${songinfo.songName}]\n[ar:${songinfo.artist}]\n[al:${songinfo.album}]\n[by:]\n[offset:0]\n${lrclist ? lrclist.map(l => `[${this.formatTime(l.time)}]${l.lineLyric}\n`).join('') : '暂无歌词'}`
},
getLyric(songId) {
const requestObj = httpFatch(`http://m.kuwo.cn/newh5/singles/songinfoandlrc?musicId=${songId}`)
const requestObj = httpFetch(`http://m.kuwo.cn/newh5/singles/songinfoandlrc?musicId=${songId}`)
requestObj.promise = requestObj.promise.then(({ body }) => {
return this.transformLrc(body.data)
})

View File

@@ -66,12 +66,12 @@ export default {
size: info[3].toLocaleUpperCase(),
}
break
case 'ape':
types.push({ type: 'ape', size: info[3] })
_types.ape = {
size: info[3].toLocaleUpperCase(),
}
break
// case 'ape':
// types.push({ type: 'ape', size: info[3] })
// _types.ape = {
// size: info[3].toLocaleUpperCase(),
// }
// break
case 'mp3':
switch (info[1]) {
case '320':
@@ -81,11 +81,11 @@ export default {
}
break
case '192':
types.push({ type: '192k', size: info[3] })
_types['192k'] = {
size: info[3].toLocaleUpperCase(),
}
break
// types.push({ type: '192k', size: info[3] })
// _types['192k'] = {
// size: info[3].toLocaleUpperCase(),
// }
// break
case '128':
types.push({ type: '128k', size: info[3] })
_types['128k'] = {
@@ -121,7 +121,7 @@ export default {
}
return result
},
search(str, page = 1, { limit }) {
search(str, page = 1, { limit } = {}) {
if (limit != null) this.limit = limit
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
return this.musicSearch(str, page).then(result => {
@@ -138,6 +138,8 @@ export default {
list,
allPage: this.allPage,
total: this.total,
limit: this.limit,
source: 'kw',
})
})
},

View File

@@ -0,0 +1,9 @@
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)
return requestObj
},
}

View File

@@ -0,0 +1,231 @@
import { httpFetch } from '../../request'
import { formatPlayTime, decodeName } from '../../index'
import { formatSinger } from './util'
export default {
_requestObj_tags: null,
_requestObj_hotTags: null,
_requestObj_list: null,
_requestObj_listDetail: null,
limit_list: 25,
limit_song: 1000,
successCode: 200,
sortList: [
{
name: '最新',
id: 'new',
},
{
name: '最热',
id: 'hot',
},
],
tagsUrl: 'http://wapi.kuwo.cn/api/pc/classify/playlist/getTagList?cmd=rcm_keyword_playlist&user=0&prod=kwplayer_pc_9.0.5.0&vipver=9.0.5.0&source=kwplayer_pc_9.0.5.0&loginUid=0&loginSid=0&appUid=76039576',
hotTagUrl: 'http://wapi.kuwo.cn/api/pc/classify/playlist/getRcmTagList?loginUid=0&loginSid=0&appUid=76039576',
getListUrl({ sortId, id, type, page }) {
if (!id) return `http://wapi.kuwo.cn/api/pc/classify/playlist/getRcmPlayList?loginUid=0&loginSid=0&appUid=76039576&&pn=${page}&rn=${this.limit_list}&order=${sortId}`
switch (type) {
case '10000': return `http://wapi.kuwo.cn/api/pc/classify/playlist/getTagPlayList?loginUid=0&loginSid=0&appUid=76039576&pn=${page}&id=${id}&rn=${this.limit_list}`
case '43': return `http://mobileinterfaces.kuwo.cn/er.s?type=get_pc_qz_data&f=web&id=${id}&prod=pc`
}
// http://wapi.kuwo.cn/api/pc/classify/playlist/getTagPlayList?loginUid=0&loginSid=0&appUid=76039576&id=173&pn=1&rn=100
},
getListDetailUrl(id, page) {
// http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=2858093057&pn=0&rn=100&encode=utf8&keyset=pl2012&identity=kuwo&pcmp4=1&vipver=MUSIC_9.0.5.0_W1&newver=1
return `http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=${id}&pn=${page - 1}&rn=${this.limit_song}&encode=utf8&keyset=pl2012&identity=kuwo&pcmp4=1&vipver=MUSIC_9.0.5.0_W1&newver=1`
// http://mobileinterfaces.kuwo.cn/er.s?type=get_pc_qz_data&f=web&id=140&prod=pc
},
// http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=2849349915&pn=0&rn=100&encode=utf8&keyset=pl2012&identity=kuwo&pcmp4=1&vipver=MUSIC_9.0.5.0_W1&newver=1
// 获取标签
getTag(tryNum = 0) {
if (this._requestObj_tags) this._requestObj_tags.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_tags = httpFetch(this.tagsUrl)
return this._requestObj_tags.promise.then(({ body }) => {
if (body.code !== this.successCode) return this.getTag(++tryNum)
return this.filterTagInfo(body.data)
})
},
// 获取标签
getHotTag(tryNum = 0) {
if (this._requestObj_hotTags) this._requestObj_hotTags.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_hotTags = httpFetch(this.hotTagUrl)
return this._requestObj_hotTags.promise.then(({ body }) => {
if (body.code !== this.successCode) return this.getHotTag(++tryNum)
return this.filterInfoHotTag(body.data[0].data)
})
},
filterInfoHotTag(rawList) {
return rawList.map(item => ({
id: `${item.id}-${item.digest}`,
name: item.name,
source: 'kw',
}))
},
filterTagInfo(rawList) {
return rawList.map(type => ({
name: type.name,
list: type.data.map(item => ({
parent_id: type.id,
parent_name: type.name,
id: `${item.id}-${item.digest}`,
name: item.name,
source: 'kw',
})),
}))
},
// 获取列表数据
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'))
let id
let type
if (tagId) {
let arr = tagId.split('-')
id = arr[0]
type = arr[1]
} else {
id = null
}
this._requestObj_list = httpFetch(this.getListUrl({ sortId, id, type, page }))
return this._requestObj_list.promise.then(({ body }) => {
if (!id || type == '10000') {
if (body.code !== this.successCode) return this.getList(sortId, id, type, page, ++tryNum)
return {
list: this.filterList(body.data.data),
total: body.data.total,
page: body.data.pn,
limit: body.data.rn,
source: 'kw',
}
} else if (!body.length) {
return this.getList(sortId, id, type, page, ++tryNum)
}
return {
list: this.filterList2(body),
total: 1000,
page,
limit: 1000,
source: 'kw',
}
})
},
/**
* 格式化播放数量
* @param {*} num
*/
formatPlayCount(num) {
if (num > 100000000) return parseInt(num / 10000000) / 10 + '亿'
if (num > 10000) return parseInt(num / 1000) / 10 + '万'
return num
},
filterList(rawData) {
return rawData.map(item => ({
play_count: this.formatPlayCount(item.listencnt),
id: item.id,
author: item.uname,
name: item.name,
// time: item.publish_time,
img: item.img,
grade: item.favorcnt / 10,
desc: item.desc,
source: 'kw',
}))
},
filterList2(rawData) {
const list = []
rawData.forEach(item => {
if (!item.label) return
list.push(...item.list.map(item => ({
play_count: item.play_count === undefined ? null : this.formatPlayCount(item.listencnt),
id: item.id,
author: item.uname,
name: item.name,
// time: item.publish_time,
img: item.img,
grade: item.favorcnt / 10,
desc: item.desc,
})))
})
return list
},
// 获取歌曲列表内的音乐
getListDetail(id, page, tryNum = 0) {
if (this._requestObj_listDetail) {
this._requestObj_listDetail.cancelHttp()
}
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_listDetail = httpFetch(this.getListDetailUrl(id, page))
return this._requestObj_listDetail.promise.then(({ body }) => {
if (body.result !== 'ok') return this.getListDetail(id, page, ++tryNum)
return {
list: this.filterListDetail(body.musiclist),
page,
limit: body.rn,
total: body.total,
source: 'kw',
info: {
name: body.title,
img: body.pic,
desc: body.info,
author: body.uname,
play_count: this.formatPlayCount(body.playnum),
},
}
})
},
filterListDetail(rawData) {
// console.log(rawList)
return rawData.map((item, inedx) => {
let formats = item.formats.split('|')
let types = []
let _types = {}
if (formats.indexOf('MP3128')) {
types.push({ type: '128k', size: null })
_types['128k'] = {
size: null,
}
}
if (formats.indexOf('MP3H')) {
types.push({ type: '320k', size: null })
_types['320k'] = {
size: null,
}
}
if (formats.indexOf('ALFLAC')) {
types.push({ type: 'flac', size: null })
_types.flac = {
size: null,
}
}
return {
singer: formatSinger(decodeName(item.artist)),
name: decodeName(item.name),
albumName: decodeName(item.album),
albumId: item.albumid,
songmid: item.id,
source: 'kw',
interval: formatPlayTime(parseInt(item.duration)),
img: null,
lrc: null,
types,
_types,
typeUrl: {},
}
})
},
getTags() {
return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({ tags, hotTag, source: 'kw' }))
},
}
// getList
// getTags
// getListDetail

View File

@@ -8,10 +8,7 @@ export default {
_musicTempSearchRequestObj: null,
_musicTempSearchPromiseCancelFn: null,
tempSearch(str) {
if (this._musicTempSearchRequestObj != null) {
cancelHttp(this._musicTempSearchRequestObj)
this._musicTempSearchPromiseCancelFn(new Error('取消http请求'))
}
this.cancelTempSearch()
return new Promise((resolve, reject) => {
this._musicTempSearchPromiseCancelFn = reject
this._musicTempSearchRequestObj = httpGet(`http://www.kuwo.cn/api/www/search/searchKey?key=${encodeURIComponent(str)}`, (err, resp, body) => {

View File

@@ -0,0 +1,20 @@
import { httpFetch } from '../../request'
import { requestMsg } from '../../message'
import { headers, timeout } from '../options'
const api_test = {
getMusicUrl(songInfo, type) {
const requestObj = httpFetch(`http://ts.tempmusic.tk/url/mg/${songInfo.copyrightId}/${type}`, {
method: 'get',
timeout,
headers,
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 requestObj
},
}
export default api_test

View File

@@ -0,0 +1,23 @@
import api_source from '../api-source'
import leaderboard from './leaderboard'
import songList from './songList'
import musicSearch from './musicSearch'
import pic from './pic'
import lyric from './lyric'
const mg = {
songList,
musicSearch,
leaderboard,
getMusicUrl(songInfo, type) {
return api_source('mg').getMusicUrl(songInfo, type)
},
getLyric(songInfo) {
return lyric.getLyric(songInfo)
},
getPic(songInfo) {
return pic.getPic(songInfo)
},
}
export default mg

View File

@@ -0,0 +1,130 @@
import { httpFetch } from '../../request'
// import { formatPlayTime } from '../../index'
// import jshtmlencode from 'js-htmlencode'
export default {
limit: 200,
list: [
{
id: 'mgyyb',
name: '音乐榜',
bangid: '23603703',
},
{
id: 'mgysb',
name: '影视榜',
bangid: '23603721',
},
{
id: 'mghybnd',
name: '华语内地榜',
bangid: '23603926',
},
{
id: 'mghyjqbgt',
name: '华语港台榜',
bangid: '23603954',
},
{
id: 'mgomb',
name: '欧美榜',
bangid: '23603974',
},
{
id: 'mgrhb',
name: '日韩榜',
bangid: '23603982',
},
{
id: 'mgwlb',
name: '网络榜',
bangid: '23604058',
},
{
id: 'mgclb',
name: '彩铃榜',
bangid: '23604023',
},
{
id: 'mgktvb',
name: 'KTV榜',
bangid: '23604040',
},
{
id: 'mgrcb',
name: '原创榜',
bangid: '23604032',
},
],
getUrl(id, page) {
return `http://m.music.migu.cn/migu/remoting/cms_list_tag?nid=${id}&pageSize=${this.limit}&pageNo=${page - 1}`
},
requestObj: null,
getData(url) {
if (this.requestObj) this.requestObj.cancelHttp()
this.requestObj = httpFetch(url)
return this.requestObj.promise
},
filterData(rawList) {
// console.log(rawList)
const list = []
rawList.forEach(({ songData }) => {
if (!songData) return
const types = []
const _types = {}
let size = null
types.push({ type: '128k', size })
_types['128k'] = {
size,
}
if (songData.hasHQqq === '1') {
types.push({ type: '320k', size })
_types['320k'] = {
size,
}
}
if (songData.hasSQqq === '1') {
types.push({ type: 'flac', size })
_types.flac = {
size,
}
}
// types.reverse()
list.push({
singer: songData.singerName.join('、'),
name: songData.songName,
// albumName: songData.album_title,
// albumId: songData.album_id,
source: 'mg',
interval: null,
songmid: songData.songId,
copyrightId: songData.copyrightId,
img: songData.picL || songData.M || songData.picS,
lrc: null,
types,
_types,
typeUrl: {},
})
})
return list
},
getList(id, page) {
let type = this.list.find(s => s.id === id)
if (!type) return Promise.reject()
return this.getData(this.getUrl(type.bangid, page)).then(({ statusCode, body }) => {
if (statusCode !== 200) return Promise.reject(new Error('获取列表失败'))
const list = this.filterData(body.result.results)
return {
total: body.result.totalCount,
list,
limit: body.result.pageSize,
page,
source: 'mg',
}
})
},
}

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