diff --git a/CHANGELOG.md b/CHANGELOG.md index 338f487b..204f26d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,39 @@ 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/). +## [1.1.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.0.1...v1.1.0) - 2020-09-18 + +### 新增 + +- 在歌单详情界面新增播放当前歌单按钮、收藏歌单按钮,注:播放歌单不会将歌曲添加到试听列表 +- 新增`不允许将歌词窗口拖出主屏幕之外`的设置项,默认开启,在连接多个屏幕时想要拖动到其他屏幕时可关闭此设置 +- 新增大部分平台的歌词翻译,感谢 @InoriHimea 提供的[krc解码算法](https://github.com/lyswhut/lx-music-desktop/issues/296#issuecomment-683285784) +- 新增`显示歌词翻译`设置,默认开启,仅支持某些平台,注:无论该设置是否开启,嵌入或下载歌词时都不会带上翻译 +- 新增`显示切换动画`设置,默认开启,关闭时将基本禁用软件内的所有切换动画 +- 播放状态栏新增桌面歌词的开关、播放模式的切换、歌曲的收藏按钮,Thanks to @andylow for the [icon](https://github.com/lyswhut/lx-music-desktop/pull/309)! + +### 修复 + +- 修复使用全局快捷键还原窗口时,窗口没有获取焦点的问题 +- 修复我的列表搜索对最后一个字符的匹配问题 +- 修复窗口在`较小`模式下最小化/关闭按钮不居中的问题 + +### 优化 + +- 桌面歌词当前播放行改为上下居中 +- 为区分静音状态,静音时音量条会变淡,调整音量条时将会取消静音 +- 优化随机播放机制,现在通过`下一曲`切换歌曲时,直到播放完整个列表之前将不会再随机到之前播放过的歌曲,并且通过`上一曲`可以正确播放上一首歌曲 +- 当下载目录没有写入权限时将显示没有写入权限的提示 + +### 移除 + +- 移除默认的全局声音媒体快捷键接管 +- 移除对百度音乐的支持,因百度音乐原有的大部分API失效,而且该平台相对其他平台来说音乐太少了,可有可无,以后再看情况恢复 + +### 其他 + +- 更新electron到 10.1.2 + ## [1.0.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.0.0...v1.0.1) - 2020-07-25 ### 优化 diff --git a/FAQ.md b/FAQ.md index f5002b70..2d88ff9b 100644 --- a/FAQ.md +++ b/FAQ.md @@ -35,7 +35,7 @@ ## 播放整个歌单或排行榜 -播放在线列表内的歌曲需要将它们都添加到我的列表才能播放,你可以全选列表内的歌曲然后添加到现有列表或者新创建的列表,然后去播放改列表内的歌曲。 +播放在线列表内的歌曲需要将它们都添加到我的列表才能播放,你可以全选列表内的歌曲然后添加到现有列表或者新创建的列表,然后去播放该列表内的歌曲。 ## 桌面歌词显示异常 @@ -108,6 +108,7 @@ Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看下面的* - 若你之前可以安装成功,但现在安装失败,就去**控制面板-程序和功能**或用第三方卸载工具看下有没有之前的版本残留,若同时在不同路径下安装了多个版本就可能会出现该问题,这种情况卸载掉所有版本重新安装即可 - 清理安装路径下的残留文件 +- 清理注册表(建议用清理工具清理) ## 缺少`xxx.dll` @@ -118,7 +119,7 @@ Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看下面的* ## 杀毒软件提示有病毒或恶意行为 -本人只能保证我写的代码不包含任何**恶意代码**、**收集用户信息**的行为,并且软件代码已开源,请自行查阅,软件安装包也是由CI拉取源代码构建,构建日志:[windows包](https://ci.appveyor.com/project/lyswhut/lx-music-desktop)、[Mac/Linux包](https://travis-ci.org/lyswhut/lx-music-desktop)
+本人只能保证我写的代码不包含任何**恶意代码**、**收集用户信息**的行为,并且软件代码已开源,请自行查阅,软件安装包也是由CI拉取源代码构建,构建日志:[windows包](https://ci.appveyor.com/project/lyswhut/lx-music-desktop)、[Mac/Linux包](https://travis-ci.com/github/lyswhut/lx-music-desktop)
尽管如此,但这不意味着软件是100%安全的,由于软件使用了第三方依赖,当这些依赖存在恶意行为时([供应链攻击](https://docs.microsoft.com/zh-cn/windows/security/threat-protection/intelligence/supply-chain-malware)),软件也将会受到牵连,所以我只能尽量选择使用较多人用、信任度较高的依赖。
当然,以上说明建立的前提是在你所用的安装包是从**本项目主页上写的链接**下载的,或者有相关能力者还可以下载源代码自己构建安装包。 diff --git a/README.md b/README.md index f57bf9a6..93febdc8 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

Release version Build status - Build status + Build status Electron version Dev branch version @@ -36,7 +36,7 @@ 所用技术栈: -- Electron 9 +- Electron 10 - Vue 2 已支持的平台: @@ -47,7 +47,7 @@ 软件变化请查看:[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)
软件下载请转到:[发布页面](https://github.com/lyswhut/lx-music-desktop/releases)
-或者到网盘下载(网盘内有MAC、windows版):`https://t-s.lanzous.com/b0bf2cfa/` 密码:`glqw`
+或者到网盘下载(网盘内有MAC、windows版):`https://www.lanzoux.com/b0bf2cfa/` 密码:`glqw`
使用常见问题请转至:[常见问题](https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md) ### 源码使用方法 diff --git a/build-config/css-loader.config.js b/build-config/css-loader.config.js index b6cb4288..9f2aa3d4 100644 --- a/build-config/css-loader.config.js +++ b/build-config/css-loader.config.js @@ -3,6 +3,6 @@ const isDev = process.env.NODE_ENV === 'development' module.exports = { modules: { localIdentName: isDev ? '[folder]-[name]--[local]--[hash:base64:5]' : '[hash:base64:5]', + exportLocalsConvention: 'camelCase', }, - localsConvention: 'camelCase', } diff --git a/build-config/pack.js b/build-config/pack.js index 1ff5470e..c956c3d1 100644 --- a/build-config/pack.js +++ b/build-config/pack.js @@ -14,7 +14,7 @@ const okayLog = chalk.bgGreen.white(' OKAY ') + ' ' function build() { - del.sync(['dist/electron', 'build']) + del.sync(['dist/electron/**', 'build/**']) const spinners = new Spinnies({ color: 'blue' }) spinners.add('main', { text: 'main building' }) diff --git a/build-config/renderer-lyric/webpack.config.base.js b/build-config/renderer-lyric/webpack.config.base.js index a74a2606..ed08fd5e 100644 --- a/build-config/renderer-lyric/webpack.config.base.js +++ b/build-config/renderer-lyric/webpack.config.base.js @@ -1,9 +1,12 @@ const path = require('path') const VueLoaderPlugin = require('vue-loader/lib/plugin') const HTMLPlugin = require('html-webpack-plugin') +const MiniCssExtractPlugin = require('mini-css-extract-plugin') const vueLoaderConfig = require('../vue-loader.config') +const { mergeCSSLoader } = require('../utils') +const isDev = process.env.NODE_ENV === 'development' module.exports = { target: 'electron-renderer', @@ -46,6 +49,28 @@ module.exports = { loader: 'babel-loader', exclude: /node_modules/, }, + { + test: /\.css$/, + oneOf: mergeCSSLoader(), + }, + { + test: /\.less$/, + oneOf: mergeCSSLoader({ + loader: 'less-loader', + options: { + sourceMap: true, + }, + }), + }, + { + test: /\.styl(:?us)?$/, + oneOf: mergeCSSLoader({ + loader: 'stylus-loader', + options: { + sourceMap: true, + }, + }), + }, { test: /\.pug$/, oneOf: [ @@ -98,5 +123,11 @@ module.exports = { __dirname, }), new VueLoaderPlugin(), + new MiniCssExtractPlugin({ + // Options similar to the same options in webpackOptions.output + // both options are optional + filename: isDev ? '[name].css' : '[name].[contenthash:8].css', + chunkFilename: isDev ? '[id].css' : '[id].[contenthash:8].css', + }), ], } diff --git a/build-config/renderer-lyric/webpack.config.dev.js b/build-config/renderer-lyric/webpack.config.dev.js index 5baf32fa..871afd3e 100644 --- a/build-config/renderer-lyric/webpack.config.dev.js +++ b/build-config/renderer-lyric/webpack.config.dev.js @@ -6,37 +6,9 @@ const { merge } = require('webpack-merge') const baseConfig = require('./webpack.config.base') -const { mergeCSSLoaderDev } = require('../utils') - module.exports = merge(baseConfig, { mode: 'development', devtool: 'eval-source-map', - module: { - rules: [ - { - test: /\.css$/, - oneOf: mergeCSSLoaderDev(), - }, - { - test: /\.less$/, - oneOf: mergeCSSLoaderDev({ - loader: 'less-loader', - options: { - sourceMap: true, - }, - }), - }, - { - test: /\.styl(:?us)?$/, - oneOf: mergeCSSLoaderDev({ - loader: 'stylus-loader', - options: { - sourceMap: true, - }, - }), - }, - ], - }, plugins: [ new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin(), diff --git a/build-config/renderer-lyric/webpack.config.prod.js b/build-config/renderer-lyric/webpack.config.prod.js index 89986194..e151deac 100644 --- a/build-config/renderer-lyric/webpack.config.prod.js +++ b/build-config/renderer-lyric/webpack.config.prod.js @@ -1,6 +1,5 @@ const path = require('path') const webpack = require('webpack') -const MiniCssExtractPlugin = require('mini-css-extract-plugin') const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin') const TerserPlugin = require('terser-webpack-plugin') const CopyWebpackPlugin = require('copy-webpack-plugin') @@ -8,7 +7,6 @@ const { merge } = require('webpack-merge') const baseConfig = require('./webpack.config.base') -const { mergeCSSLoaderProd } = require('../utils') const { dependencies } = require('../../package.json') let whiteListedModules = ['vue'] @@ -20,32 +18,6 @@ module.exports = merge(baseConfig, { externals: [ ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)), ], - module: { - rules: [ - { - test: /\.css$/, - oneOf: mergeCSSLoaderProd(), - }, - { - test: /\.less$/, - oneOf: mergeCSSLoaderProd({ - loader: 'less-loader', - options: { - sourceMap: true, - }, - }), - }, - { - test: /\.styl(:?us)?$/, - oneOf: mergeCSSLoaderProd({ - loader: 'stylus-loader', - options: { - sourceMap: true, - }, - }), - }, - ], - }, plugins: [ new CopyWebpackPlugin({ patterns: [ @@ -60,9 +32,6 @@ module.exports = merge(baseConfig, { NODE_ENV: '"production"', }, }), - new MiniCssExtractPlugin({ - filename: '[name].css', - }), new webpack.NamedChunksPlugin(), ], optimization: { diff --git a/build-config/renderer/webpack.config.base.js b/build-config/renderer/webpack.config.base.js index ea4f652f..fdf69a36 100644 --- a/build-config/renderer/webpack.config.base.js +++ b/build-config/renderer/webpack.config.base.js @@ -1,9 +1,12 @@ const path = require('path') const VueLoaderPlugin = require('vue-loader/lib/plugin') const HTMLPlugin = require('html-webpack-plugin') +const MiniCssExtractPlugin = require('mini-css-extract-plugin') const vueLoaderConfig = require('../vue-loader.config') +const { mergeCSSLoader } = require('../utils') +const isDev = process.env.NODE_ENV === 'development' module.exports = { target: 'electron-renderer', @@ -46,6 +49,28 @@ module.exports = { loader: 'babel-loader', exclude: /node_modules/, }, + { + test: /\.css$/, + oneOf: mergeCSSLoader(), + }, + { + test: /\.less$/, + oneOf: mergeCSSLoader({ + loader: 'less-loader', + options: { + sourceMap: true, + }, + }), + }, + { + test: /\.styl(:?us)?$/, + oneOf: mergeCSSLoader({ + loader: 'stylus-loader', + options: { + sourceMap: true, + }, + }), + }, { test: /\.pug$/, oneOf: [ @@ -98,5 +123,11 @@ module.exports = { __dirname, }), new VueLoaderPlugin(), + new MiniCssExtractPlugin({ + // Options similar to the same options in webpackOptions.output + // both options are optional + filename: isDev ? '[name].css' : '[name].[contenthash:8].css', + chunkFilename: isDev ? '[id].css' : '[id].[contenthash:8].css', + }), ], } diff --git a/build-config/renderer/webpack.config.dev.js b/build-config/renderer/webpack.config.dev.js index 5baf32fa..871afd3e 100644 --- a/build-config/renderer/webpack.config.dev.js +++ b/build-config/renderer/webpack.config.dev.js @@ -6,37 +6,9 @@ const { merge } = require('webpack-merge') const baseConfig = require('./webpack.config.base') -const { mergeCSSLoaderDev } = require('../utils') - module.exports = merge(baseConfig, { mode: 'development', devtool: 'eval-source-map', - module: { - rules: [ - { - test: /\.css$/, - oneOf: mergeCSSLoaderDev(), - }, - { - test: /\.less$/, - oneOf: mergeCSSLoaderDev({ - loader: 'less-loader', - options: { - sourceMap: true, - }, - }), - }, - { - test: /\.styl(:?us)?$/, - oneOf: mergeCSSLoaderDev({ - loader: 'stylus-loader', - options: { - sourceMap: true, - }, - }), - }, - ], - }, plugins: [ new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin(), diff --git a/build-config/renderer/webpack.config.prod.js b/build-config/renderer/webpack.config.prod.js index 89986194..e151deac 100644 --- a/build-config/renderer/webpack.config.prod.js +++ b/build-config/renderer/webpack.config.prod.js @@ -1,6 +1,5 @@ const path = require('path') const webpack = require('webpack') -const MiniCssExtractPlugin = require('mini-css-extract-plugin') const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin') const TerserPlugin = require('terser-webpack-plugin') const CopyWebpackPlugin = require('copy-webpack-plugin') @@ -8,7 +7,6 @@ const { merge } = require('webpack-merge') const baseConfig = require('./webpack.config.base') -const { mergeCSSLoaderProd } = require('../utils') const { dependencies } = require('../../package.json') let whiteListedModules = ['vue'] @@ -20,32 +18,6 @@ module.exports = merge(baseConfig, { externals: [ ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)), ], - module: { - rules: [ - { - test: /\.css$/, - oneOf: mergeCSSLoaderProd(), - }, - { - test: /\.less$/, - oneOf: mergeCSSLoaderProd({ - loader: 'less-loader', - options: { - sourceMap: true, - }, - }), - }, - { - test: /\.styl(:?us)?$/, - oneOf: mergeCSSLoaderProd({ - loader: 'stylus-loader', - options: { - sourceMap: true, - }, - }), - }, - ], - }, plugins: [ new CopyWebpackPlugin({ patterns: [ @@ -60,9 +32,6 @@ module.exports = merge(baseConfig, { NODE_ENV: '"production"', }, }), - new MiniCssExtractPlugin({ - filename: '[name].css', - }), new webpack.NamedChunksPlugin(), ], optimization: { diff --git a/build-config/utils.js b/build-config/utils.js index 0a6a9e72..e3a9b5e0 100644 --- a/build-config/utils.js +++ b/build-config/utils.js @@ -2,62 +2,21 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin') const cssLoaderConfig = require('./css-loader.config') const chalk = require('chalk') -// merge css-loader in development -exports.mergeCSSLoaderDev = beforeLoader => { - const loader = [ - // 这里匹配 ` diff --git a/src/renderer/components/material/InputRange.vue b/src/renderer/components/material/InputRange.vue deleted file mode 100644 index d63577a7..00000000 --- a/src/renderer/components/material/InputRange.vue +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - diff --git a/src/renderer/components/material/ListButtons.vue b/src/renderer/components/material/ListButtons.vue index 4e5fd3e4..aa588b26 100644 --- a/src/renderer/components/material/ListButtons.vue +++ b/src/renderer/components/material/ListButtons.vue @@ -1,31 +1,31 @@ diff --git a/src/renderer/components/material/SearchList.vue b/src/renderer/components/material/SearchList.vue index 4e55a03a..a070478f 100644 --- a/src/renderer/components/material/SearchList.vue +++ b/src/renderer/components/material/SearchList.vue @@ -239,7 +239,7 @@ export default { handleSearch() { if (!this.text.length) return this.resultList = [] let list = [] - let rxp = new RegExp(this.text.split('').join('.*'), 'i') + let rxp = new RegExp(this.text.split('').join('.*') + '.*', 'i') for (const item of this.list) { if (rxp.test(`${item.name}${item.singer}${item.albumName ? item.albumName : ''}`)) list.push(item) } diff --git a/src/renderer/components/material/VersionModal.vue b/src/renderer/components/material/VersionModal.vue index feaa7ab7..58a6fa79 100644 --- a/src/renderer/components/material/VersionModal.vue +++ b/src/renderer/components/material/VersionModal.vue @@ -46,7 +46,7 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV | 你可以去  strong.hover.underline(@click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" title="点击打开") 软件发布页 |  或  - strong.hover.underline(@click="handleOpenUrl('https://www.lanzous.com/b906260/')" title="点击打开") 网盘 + strong.hover.underline(@click="handleOpenUrl('https://www.lanzoux.com/b0bf2cfa/')" title="点击打开") 网盘 | (密码: strong.hover(@click="handleCopy('glqw')" title="点击复制") glqw | ) 下载新版本, @@ -63,7 +63,7 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV | 你可以去 material-btn(min @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" title="点击打开") 软件发布页 | 或 - material-btn(min @click="handleOpenUrl('https://www.lanzous.com/b906260/')" title="点击打开") 网盘 + material-btn(min @click="handleOpenUrl('https://www.lanzoux.com/b0bf2cfa/')" title="点击打开") 网盘 | (密码: strong.hover(@click="handleCopy('glqw')" title="点击复制") glqw | )下载新版本, @@ -84,7 +84,7 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV | 检查方法:打开 material-btn(min @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" title="点击打开") 软件发布页 | 或 - material-btn(min @click="handleOpenUrl('https://www.lanzous.com/b906260/')" title="点击打开") 网盘 + material-btn(min @click="handleOpenUrl('https://www.lanzoux.com/b0bf2cfa/')" title="点击打开") 网盘 | (密码: strong.hover(@click="handleCopy('glqw')" title="点击复制") glqw | )查看它们的 @@ -117,7 +117,7 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV | 手动更新可以去  strong.hover.underline(@click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" title="点击打开") 软件发布页 |  或  - strong.hover.underline(@click="handleOpenUrl('https://www.lanzous.com/b906260/')" title="点击打开") 网盘 + strong.hover.underline(@click="handleOpenUrl('https://www.lanzoux.com/b0bf2cfa/')" title="点击打开") 网盘 | (密码: strong.hover(@click="handleCopy('glqw')" title="点击复制") glqw | ) 下载, diff --git a/src/renderer/components/material/listAddModal.vue b/src/renderer/components/material/listAddModal.vue index 99c27f65..9386f88d 100644 --- a/src/renderer/components/material/listAddModal.vue +++ b/src/renderer/components/material/listAddModal.vue @@ -61,7 +61,7 @@ export default { this.defaultList, this.loveList, ...this.userList, - ].filter(l => l.id != this.excludeListId) + ].filter(l => l.id != this.excludeListId.includes(l.id)) }, spaceNum() { return this.lists.length < 2 ? 0 : (3 - this.lists.length % 3 - 1) @@ -91,7 +91,7 @@ export default { this.newListName = event.target.value = '' this.isEditing = false if (!name) return - this.createUserList(name) + this.createUserList({ name }) }, }, } diff --git a/src/renderer/components/material/listAddMultipleModal.vue b/src/renderer/components/material/listAddMultipleModal.vue index 32a4be14..2c9c370d 100644 --- a/src/renderer/components/material/listAddMultipleModal.vue +++ b/src/renderer/components/material/listAddMultipleModal.vue @@ -62,7 +62,7 @@ export default { this.defaultList, this.loveList, ...this.userList, - ].filter(l => l.id != this.excludeListId) + ].filter(l => l.id != this.excludeListId.includes(l.id)) }, spaceNum() { return this.lists.length < 2 ? 0 : (3 - this.lists.length % 3 - 1) @@ -92,7 +92,7 @@ export default { this.newListName = event.target.value = '' this.isEditing = false if (!name) return - this.createUserList(name) + this.createUserList({ name }) }, }, } diff --git a/src/renderer/event/index.js b/src/renderer/event/index.js index 495d4085..d818b955 100644 --- a/src/renderer/event/index.js +++ b/src/renderer/event/index.js @@ -1,21 +1,22 @@ import Vue from 'vue' import keyBind from '../utils/keyBind' -import { rendererOn, rendererSend, NAMES } from '../../common/ipc' +import { rendererOn, rendererSend, NAMES, rendererInvoke } from '../../common/ipc' import { base as baseName } from './names' -import Store from 'electron-store' import { common as hotKeyNamesCommon } from '../../common/hotKey' const eventHub = window.eventHub = new Vue() -const electronStore_hotKey = window.electronStore_hotKey = new Store({ - name: 'hotKey', -}) - window.isEditingHotKey = false -const appHotKeyConfig = window.appHotKeyConfig = { - local: electronStore_hotKey.get('local'), - global: electronStore_hotKey.get('global'), +let appHotKeyConfig = { + local: {}, + global: {}, } +rendererInvoke(NAMES.mainWindow.get_hot_key).then(({ local, global }) => { + appHotKeyConfig = window.appHotKeyConfig = { + local, + global, + } +}) eventHub.$on(baseName.bindKey, () => { keyBind.bindKey((key, type, event, keys) => { diff --git a/src/renderer/lang/en-us/core/player.json b/src/renderer/lang/en-us/core/player.json index dff70093..d21ff4ad 100644 --- a/src/renderer/lang/en-us/core/player.json +++ b/src/renderer/lang/en-us/core/player.json @@ -17,5 +17,13 @@ "hide_detail": "Hide detail page", "name": "Name: ", "singer": "Artist: ", - "album": "Album: " + "album": "Album: ", + "add_music_to": "Add the current song to...", + "desktop_lyric_on": "Open Desktop Lyrics", + "desktop_lyric_off": "Close Desktop Lyrics", + "play_toggle_mode_list_loop": "List Loop", + "play_toggle_mode_random": "List Random", + "play_toggle_mode_list": "Play in order", + "play_toggle_mode_single_loop": "Single Loop", + "play_toggle_mode_off": "Disable" } diff --git a/src/renderer/lang/en-us/view/setting.json b/src/renderer/lang/en-us/view/setting.json index 116b46c4..0b6a455f 100644 --- a/src/renderer/lang/en-us/view/setting.json +++ b/src/renderer/lang/en-us/view/setting.json @@ -1,6 +1,7 @@ { "basic": "General", "basic_theme": "Theme", + "basic_show_animation": "Show switching animation", "basic_animation_title": "Animation effect of the pop-up layer", "basic_animation": "Random pop-up animation", "basic_source_title": "Choose a music source", @@ -35,6 +36,7 @@ "play_toggle_random": "Playlist shuffle", "play_toggle_list": "Play in order", "play_toggle_single_loop": "Single repeat", + "play_lyric_transition": "Show lyrics translation", "play_quality_title": "The 320k quality is preferred for playing", "play_quality": "Prefer High Quality 320k", "play_task_bar_title": "Show playing progress on the taskbar", @@ -48,6 +50,7 @@ "desktop_lyric_enable": "Display lyrics", "desktop_lyric_lock": "Lock lyrics", "desktop_lyric_always_on_top": "Make the lyrics always above other windows", + "desktop_lyric_lock_screen": "It is not allowed to drag the lyrics window out of the main screen", "search": "Search", "search_hot_title": "Select whether to show popular searches", diff --git a/src/renderer/lang/en-us/view/song_list.json b/src/renderer/lang/en-us/view/song_list.json index e7993d13..8c600d5b 100644 --- a/src/renderer/lang/en-us/view/song_list.json +++ b/src/renderer/lang/en-us/view/song_list.json @@ -6,5 +6,7 @@ "input_text": "Enter songlist link or songlist ID", "tip_1": "Cross-source playlists are not supported, please confirm whether the playlist to be opened corresponds to the current playlist source", "tip_2": "If you encounter a link to a playlist that cannot be opened, welcome feedback", - "tip_3": "Kugou source does not support opening with playlist ID, but supports Kugou code opening" + "tip_3": "Kugou source does not support opening with playlist ID, but supports Kugou code opening", + "play_all": "Play", + "add_all": "Collect" } diff --git a/src/renderer/lang/zh-cn/core/player.json b/src/renderer/lang/zh-cn/core/player.json index 36936ad5..8a32b56c 100644 --- a/src/renderer/lang/zh-cn/core/player.json +++ b/src/renderer/lang/zh-cn/core/player.json @@ -17,5 +17,13 @@ "hide_detail": "隐藏详情页", "name": "歌曲名:", "singer": "艺术家:", - "album": "专辑名:" + "album": "专辑名:", + "add_music_to": "添加当前歌曲到...", + "desktop_lyric_on": "开启桌面歌词", + "desktop_lyric_off": "关闭桌面歌词", + "play_toggle_mode_list_loop": "列表循环", + "play_toggle_mode_random": "列表随机", + "play_toggle_mode_list": "顺序播放", + "play_toggle_mode_single_loop": "单曲循环", + "play_toggle_mode_off": "禁用" } diff --git a/src/renderer/lang/zh-cn/view/setting.json b/src/renderer/lang/zh-cn/view/setting.json index 3983fcb3..8d4e2f80 100644 --- a/src/renderer/lang/zh-cn/view/setting.json +++ b/src/renderer/lang/zh-cn/view/setting.json @@ -3,6 +3,7 @@ "basic_theme": "主题颜色", "basic_animation_title": "弹出层的动画效果", "basic_animation": "弹出层随机动画", + "basic_show_animation": "显示切换动画", "basic_source_title": "选择音乐来源", "basic_source_test": "测试接口(几乎软件的所有功能都可用)", "basic_source_temp": "临时接口(软件的某些功能不可用,建议测试接口不可用再使用本接口)", @@ -35,6 +36,7 @@ "play_toggle_random": "列表随机", "play_toggle_list": "顺序播放", "play_toggle_single_loop": "单曲循环", + "play_lyric_transition": "显示歌词翻译", "play_quality_title": "启用时将优先播放320K品质的歌曲", "play_quality": "优先播放高品质音乐", "play_task_bar_title": "在任务栏上显示当前歌曲播放进度", @@ -48,6 +50,7 @@ "desktop_lyric_enable": "显示歌词", "desktop_lyric_lock": "锁定歌词", "desktop_lyric_always_on_top": "使歌词总是在其他窗口之上", + "desktop_lyric_lock_screen": "不允许歌词窗口拖出主屏幕之外", "search": "搜索设置", "search_hot_title": "是否显示热门搜索", diff --git a/src/renderer/lang/zh-cn/view/song_list.json b/src/renderer/lang/zh-cn/view/song_list.json index aca6a947..bbbadf6b 100644 --- a/src/renderer/lang/zh-cn/view/song_list.json +++ b/src/renderer/lang/zh-cn/view/song_list.json @@ -6,5 +6,7 @@ "input_text": "输入歌单链接或歌单ID", "tip_1": "不支持跨源打开歌单,请确认要打开的歌单与当前歌单源是否对应", "tip_2": "若遇到无法打开的歌单链接,欢迎反馈", - "tip_3": "酷狗源不支持用歌单ID打开,但支持酷狗码打开" + "tip_3": "酷狗源不支持用歌单ID打开,但支持酷狗码打开", + "play_all": "播放", + "add_all": "收藏" } diff --git a/src/renderer/lang/zh-tw/core/player.json b/src/renderer/lang/zh-tw/core/player.json index 513ea11f..573f766c 100644 --- a/src/renderer/lang/zh-tw/core/player.json +++ b/src/renderer/lang/zh-tw/core/player.json @@ -17,5 +17,13 @@ "hide_detail": "隱藏詳情頁", "name": "歌曲名:", "singer": "藝術家:", - "album": "專輯名:" + "album": "專輯名:", + "add_music_to": "添加當前歌曲到...", + "desktop_lyric_on": "開啟桌面歌詞", + "desktop_lyric_off": "關閉桌面歌詞", + "play_toggle_mode_list_loop": "列表循環", + "play_toggle_mode_random": "列表隨機", + "play_toggle_mode_list": "順序播放", + "play_toggle_mode_single_loop": "單曲循環", + "play_toggle_mode_off": "禁用" } diff --git a/src/renderer/lang/zh-tw/view/setting.json b/src/renderer/lang/zh-tw/view/setting.json index 0f7ff439..f5eecd1e 100644 --- a/src/renderer/lang/zh-tw/view/setting.json +++ b/src/renderer/lang/zh-tw/view/setting.json @@ -3,6 +3,7 @@ "basic_theme": "主題顏色", "basic_animation_title": "彈出層的動畫效果", "basic_animation": "彈出層隨機動畫", + "basic_show_animation": "顯示切換動畫", "basic_source_title": "選擇音樂來源", "basic_source_test": "測試接口(幾乎軟件的所有功能都可用)", "basic_source_temp": "臨時接口(軟件的某些功能不可用,建議測試接口不可用再使用本接口)", @@ -34,6 +35,7 @@ "play_toggle_random": "列表隨機", "play_toggle_list": "順序播放", "play_toggle_single_loop": "單曲循環", + "play_lyric_transition": "顯示歌詞翻譯", "play_quality_title": "啟用時將優先播放320K品質的歌曲", "play_quality": "優先播放高品質音樂", "play_task_bar_title": "在任務欄上顯示當前歌曲播放進度", @@ -46,6 +48,7 @@ "desktop_lyric_enable": "顯示歌詞", "desktop_lyric_lock": "鎖定歌詞", "desktop_lyric_always_on_top": "使歌詞總是在其他窗口之上", + "desktop_lyric_lock_screen": "不允許歌詞窗口拖出主屏幕之外", "search": "搜索設置", "search_hot_title": "是否顯示熱門搜索", "search_hot": "熱門搜索", diff --git a/src/renderer/lang/zh-tw/view/song_list.json b/src/renderer/lang/zh-tw/view/song_list.json index d47eb2d0..a52e8aa9 100644 --- a/src/renderer/lang/zh-tw/view/song_list.json +++ b/src/renderer/lang/zh-tw/view/song_list.json @@ -6,5 +6,7 @@ "input_text": "輸入歌單鏈接或歌單ID", "tip_1": "不支持跨源打開歌單,請確認要打開的歌單與當前歌單源是否對應", "tip_2": "若遇到無法打開的歌單鏈接,歡迎反饋", - "tip_3": "酷狗源不支持用歌單ID打開,但支持酷狗碼打開" + "tip_3": "酷狗源不支持用歌單ID打開,但支持酷狗碼打開", + "play_all": "播放", + "add_all": "收藏" } diff --git a/src/renderer/main.js b/src/renderer/main.js index 18898ec9..bac4f660 100644 --- a/src/renderer/main.js +++ b/src/renderer/main.js @@ -16,14 +16,45 @@ import store from './store' import '../common/error' +import { getSetting } from './utils' +import languageList from '@/lang/languages.json' +import { rendererSend, NAMES } from '../common/ipc' + sync(store, router) Vue.config.productionTip = false -new Vue({ - router, - store, - i18n, - el: '#root', - render: h => h(App), + +getSetting().then(({ setting, version }) => { + // Set language automatically + if (!window.i18n.availableLocales.includes(setting.langId)) { + let langId = null + let locale = window.navigator.language.toLocaleLowerCase() + if (window.i18n.availableLocales.includes(locale)) { + langId = locale + } else { + for (const lang of languageList) { + if (lang.alternate == locale) { + langId = lang.locale + break + } + } + if (langId == null) langId = 'en-us' + } + setting.langId = langId + rendererSend(NAMES.mainWindow.set_app_setting, setting) + console.log('Set lang', setting.langId) + } + window.i18n.locale = setting.langId + store.commit('setSetting', setting) + store.commit('setSettingVersion', version) + + new Vue({ + router, + store, + i18n, + el: '#root', + render: h => h(App), + }) }) + diff --git a/src/renderer/store/modules/download.js b/src/renderer/store/modules/download.js index 8166686d..e62bbeac 100644 --- a/src/renderer/store/modules/download.js +++ b/src/renderer/store/modules/download.js @@ -177,7 +177,7 @@ const saveMeta = (downloadInfo, filePath, isEmbedPic, isEmbedLyric) => { : Promise.resolve(), isEmbedLyric ? downloadInfo.musicInfo.lrc - ? Promise.resolve(downloadInfo.musicInfo.lrc) + ? Promise.resolve({ lyric: downloadInfo.musicInfo.lrc, tlyric: downloadInfo.musicInfo.tlrc || '' }) : music[downloadInfo.musicInfo.source].getLyric(downloadInfo.musicInfo).promise.catch(err => { console.log(err) return null @@ -190,7 +190,7 @@ const saveMeta = (downloadInfo, filePath, isEmbedPic, isEmbedLyric) => { artist: downloadInfo.musicInfo.singer, album: downloadInfo.musicInfo.albumName, APIC: imgUrl, - lyrics, + lyrics: lyrics.lyric, }) }) } @@ -202,10 +202,10 @@ const saveMeta = (downloadInfo, filePath, isEmbedPic, isEmbedLyric) => { */ const downloadLyric = (downloadInfo, filePath) => { const promise = downloadInfo.musicInfo.lrc - ? Promise.resolve(downloadInfo.musicInfo.lrc) + ? Promise.resolve({ lyric: downloadInfo.musicInfo.lrc, tlyric: downloadInfo.musicInfo.tlrc || '' }) : music[downloadInfo.musicInfo.source].getLyric(downloadInfo.musicInfo).promise - promise.then(lrc => { - if (lrc) saveLrc(filePath.replace(/(mp3|flac|ape|wav)$/, 'lrc'), lrc) + promise.then(lrcs => { + if (lrcs.lyric) saveLrc(filePath.replace(/(mp3|flac|ape|wav)$/, 'lrc'), lrcs.lyric) }) } @@ -218,13 +218,13 @@ const refreshUrl = function(commit, downloadInfo) { if (!dl) return dl.refreshUrl(result.url) dl.start().catch(err => { - commit('onError', downloadInfo) + commit('onError', { downloadInfo, errorMsg: err.message }) commit('setStatusText', { downloadInfo, text: err.message }) this.dispatch('download/startTask') }) }).catch(err => { // console.log(err) - commit('onError', downloadInfo) + commit('onError', { downloadInfo, errorMsg: err.message }) commit('setStatusText', { downloadInfo, text: err.message }) this.dispatch('download/startTask') }) @@ -299,7 +299,7 @@ const actions = { try { await checkPath(rootState.setting.download.savePath) } catch (error) { - commit('onError', downloadInfo) + commit('onError', { downloadInfo, errorMsg: error.message }) commit('setStatusText', '检查下载目录出错: ' + error.message) await dispatch('startTask') return @@ -324,9 +324,14 @@ const actions = { console.log('on complate') }, onError(err) { + // console.log(err) + if (err.code == 'EPERM') { + commit('onError', { downloadInfo, errorMsg: '歌曲下载目录没有写入权限,请尝试更改歌曲保存路径' }) + return + } // console.log(tryNum[downloadInfo.key]) if (++tryNum[downloadInfo.key] > 2) { - commit('onError', downloadInfo) + commit('onError', { downloadInfo, errorMsg: err.message }) dispatch('startTask') return } @@ -375,7 +380,7 @@ const actions = { dls[downloadInfo.key] = download(options) }).catch(err => { // console.log(err.message) - commit('onError', downloadInfo) + commit('onError', { downloadInfo, errorMsg: err.message }) commit('setStatusText', { downloadInfo, text: err.message }) dispatch('startTask') }) @@ -437,7 +442,7 @@ const actions = { try { await dl.start() } catch (error) { - commit('onError', downloadInfo) + commit('onError', { downloadInfo, errorMsg: error.message }) commit('setStatusText', error.message) await dispatch('startTask') } @@ -448,7 +453,7 @@ const actions = { startTasks(store, list) { if (isRuningActionTask) return isRuningActionTask = true - return startTasks(store, [...list]).finally(() => { + return startTasks(store, list.filter(item => !(item.isComplate || item.status == state.downloadStatus.RUN || item.status == state.downloadStatus.WAITING))).finally(() => { isRuningActionTask = false }) }, @@ -522,9 +527,9 @@ const mutations = { downloadInfo.status = state.downloadStatus.COMPLETED downloadInfo.statusText = '下载完成' }, - onError(state, downloadInfo) { + onError(state, { downloadInfo, errorMsg }) { downloadInfo.status = state.downloadStatus.ERROR - downloadInfo.statusText = '任务出错' + downloadInfo.statusText = errorMsg || '任务出错' }, onStart(state, downloadInfo) { downloadInfo.status = state.downloadStatus.RUN diff --git a/src/renderer/store/modules/list.js b/src/renderer/store/modules/list.js index 98560d4a..04978ce3 100644 --- a/src/renderer/store/modules/list.js +++ b/src/renderer/store/modules/list.js @@ -28,6 +28,12 @@ const state = { list: [], location: 0, }, + tempList: { + id: 'temp', + name: '临时列表', + list: [], + location: 0, + }, userList: [], } @@ -140,15 +146,19 @@ const mutations = { if (!targetList) return Object.assign(targetList.list[index], data) }, - createUserList(state, name) { - let newList = { - name, - id: `userlist_${Date.now()}`, - list: [], - location: 0, + createUserList(state, { name, id = `userlist_${Date.now()}`, list = [] }) { + let newList = state.userList.find(item => item.id === id) + if (!newList) { + newList = { + name, + id, + list: [], + location: 0, + } + state.userList.push(newList) + allListUpdate(newList) } - state.userList.push(newList) - allListUpdate(newList) + this.commit('list/listAddMultiple', { id, list }) }, removeUserList(state, index) { let list = state.userList.splice(index, 1)[0] diff --git a/src/renderer/store/modules/player.js b/src/renderer/store/modules/player.js index e61ee64f..16fbc8a0 100644 --- a/src/renderer/store/modules/player.js +++ b/src/renderer/store/modules/player.js @@ -9,6 +9,7 @@ const state = { playIndex: -1, changePlay: false, isShowPlayerDetail: false, + playedList: [], } let urlRequest @@ -22,6 +23,7 @@ const getters = { changePlay: satte => satte.changePlay, playIndex: state => state.playIndex, isShowPlayerDetail: state => state.isShowPlayerDetail, + playedList: state => state.playedList, } // actions @@ -58,9 +60,9 @@ const actions = { getLrc({ commit, state }, musicInfo) { if (lrcRequest && lrcRequest.cancelHttp) lrcRequest.cancelHttp() lrcRequest = music[musicInfo.source].getLyric(musicInfo) - return lrcRequest.promise.then(lrc => { + return lrcRequest.promise.then(({ lyric, tlyric }) => { lrcRequest = null - commit('setLrc', { musicInfo, lrc }) + commit('setLrc', { musicInfo, lyric, tlyric }) }).catch(err => { lrcRequest = null return Promise.reject(err) @@ -78,12 +80,14 @@ const mutations = { datas.musicInfo.img = datas.url }, setLrc(state, datas) { - datas.musicInfo.lrc = datas.lrc + datas.musicInfo.lrc = datas.lyric + datas.musicInfo.tlrc = datas.tlyric }, setList(state, { list, index }) { state.listInfo = list state.playIndex = index state.changePlay = true + if (state.playedList.length) this.commit('player/clearPlayedList') }, setPlayIndex(state, index) { state.playIndex = index @@ -96,6 +100,16 @@ const mutations = { resetChangePlay(state) { state.changePlay = false }, + setPlayedList(state, item) { + if (state.playedList.includes(item)) return + state.playedList.push(item) + }, + removePlayedList(state, index) { + state.playedList.splice(index, 1) + }, + clearPlayedList(state) { + state.playedList = [] + }, visiblePlayerDetail(state, visible) { state.isShowPlayerDetail = visible }, diff --git a/src/renderer/store/modules/search.js b/src/renderer/store/modules/search.js index f8b06b80..ff3a3e2b 100644 --- a/src/renderer/store/modules/search.js +++ b/src/renderer/store/modules/search.js @@ -1,15 +1,5 @@ -import Store from 'electron-store' import music from '../../utils/music' -const electronStore_data = window.electronStore_data = new Store({ - name: 'data', -}) -let historyList = electronStore_data.get('searchHistoryList') -if (historyList == null) { - historyList = [] - electronStore_data.set('searchHistoryList', historyList) -} - const sources = [] const sourceList = {} const sourceMaxPage = {} @@ -103,7 +93,7 @@ const state = { allPage: 1, total: 0, sourceMaxPage, - historyList, + historyList: [], } // getters @@ -211,6 +201,9 @@ const mutations = { clearHistory(state) { state.historyList = [] }, + setHistory(state, list) { + state.historyList = list + }, } export default { diff --git a/src/renderer/store/modules/songList.js b/src/renderer/store/modules/songList.js index 1bf8dd93..731b04e1 100644 --- a/src/renderer/store/modules/songList.js +++ b/src/renderer/store/modules/songList.js @@ -79,37 +79,29 @@ const actions = { cache.has(key) ? Promise.resolve(cache.get(key)) : music[source].songList.getListDetail(id, page) - ).then(result => commit('setListDetail', { result, key, page })) + ).then(result => commit('setListDetail', { result, key, source, id, page })) }, -/* getListDetailAll({ state, rootState }, id) { + 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 - } + const loadData = (id, page) => { + let key = `sdetail__${source}__${id}__${page}` + return cache.has(key) ? Promise.resolve(cache.get(key)) : music[source].songList.getListDetail(id, page).then(result => { + cache.set(key, result) + return result + }) + } + return loadData(id, 1).then(result => { + if (result.total <= result.limit) return result.list 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])) + ? loadData(id, ++loadPage).then(result => result.list) + : loadData(id, ++loadPage).then(result1 => loadDetail(loadPage).then(result2 => [...result1.list, ...result2])) } - return loadDetail().then(result2 => { - console.log(result2) - }) + return loadDetail().then(result2 => [...result.list, ...result2]) }) - }, */ + }, } // mitations @@ -129,8 +121,10 @@ const mutations = { state.list.key = key cache.set(key, result) }, - setListDetail(state, { result, key, page }) { + setListDetail(state, { result, key, source, id, page }) { state.listDetail.list = result.list + state.listDetail.id = id + state.listDetail.source = source state.listDetail.total = result.total state.listDetail.limit = result.limit state.listDetail.page = page @@ -153,6 +147,8 @@ const mutations = { }, clearListDetail(state) { state.listDetail = { + id: null, + source: null, list: [], desc: null, total: 0, diff --git a/src/renderer/store/mutations.js b/src/renderer/store/mutations.js index 1d9648b4..8e23cb76 100644 --- a/src/renderer/store/mutations.js +++ b/src/renderer/store/mutations.js @@ -9,12 +9,12 @@ export default { setSetting(state, val) { state.setting = val }, - setAgreePact(state) { - state.setting.isAgreePact = true - }, setSettingVersion(state, val) { state.settingVersion = val }, + setAgreePact(state) { + state.setting.isAgreePact = true + }, setLeaderboard(state, { tabId, source }) { if (tabId != null) state.setting.leaderboard.tabId = tabId if (source != null) state.setting.leaderboard.source = source @@ -49,6 +49,12 @@ export default { state.setting.player.volume = val } }, + setPlayNextMode(state, val) { + state.setting.player.togglePlayMethod = val + }, + setVisibleDesktopLyric(state, val) { + state.setting.desktopLyric.enable = val + }, setMediaDeviceId(state, val) { state.setting.player.mediaDeviceId = val }, diff --git a/src/renderer/store/state.js b/src/renderer/store/state.js index 23a968c5..96b7ad03 100644 --- a/src/renderer/store/state.js +++ b/src/renderer/store/state.js @@ -1,67 +1,9 @@ - // const isDev = process.env.NODE_ENV === 'development' -import Store from 'electron-store' import { windowSizeList } from '../../common/config' import { version } from '../../../package.json' -import { rendererSend, rendererInvoke, NAMES } from '../../common/ipc' -import languageList from '@/lang/languages.json' -import path from 'path' -import { openDirInExplorer } from '../utils' - - -const electronStore_config = window.electronStore_config = new Store({ - name: 'config', -}) -let setting = electronStore_config.get('setting') -let settingVersion = electronStore_config.get('version') process.versions.app = version -// Set language automatically -if (!window.i18n.availableLocales.includes(setting.langId)) { - let langId = null - let locale = window.navigator.language.toLocaleLowerCase() - if (window.i18n.availableLocales.includes(locale)) { - langId = locale - } else { - for (const lang of languageList) { - if (lang.alternate == locale) { - langId = lang.locale - break - } - } - if (langId == null) langId = 'en-us' - } - setting.langId = langId - electronStore_config.set('setting', setting) - rendererSend(NAMES.mainWindow.set_app_setting, setting) - console.log('Set lang', setting.langId) -} - -window.i18n.locale = setting.langId - -try { - window.electronStore_list = new Store({ - name: 'playList', - clearInvalidConfig: false, - }) -} catch (error) { - rendererInvoke(NAMES.mainWindow.get_data_path).then(dataPath => { - let filePath = path.join(dataPath, 'playList.json.bak') - rendererInvoke(NAMES.mainWindow.show_dialog, { - type: 'error', - message: window.i18n.t('store.state.load_list_file_error_title'), - detail: window.i18n.t('store.state.load_list_file_error_detail', { - path: filePath, - detail: error.message, - }), - }).then(() => openDirInExplorer(filePath)) - }) - window.electronStore_list = new Store({ - name: 'playList', - }) -} - export default { themes: [ @@ -144,8 +86,8 @@ export default { downloadProgress: null, }, userInfo: null, - setting, - settingVersion, + setting: null, + settingVersion: null, windowSizeList, } diff --git a/src/renderer/utils/download/Downloader.js b/src/renderer/utils/download/Downloader.js index bfe89ca2..1dc2e7fb 100644 --- a/src/renderer/utils/download/Downloader.js +++ b/src/renderer/utils/download/Downloader.js @@ -172,7 +172,7 @@ class Task extends EventEmitter { this.chunkInfo.startByte = 0 this.resumeLastChunk = null this.progress.downloaded = 0 - if (unlinkErr) this.__handleError(unlinkErr) + if (unlinkErr && unlinkErr.code !== 'ENOENT') this.__handleError(unlinkErr) }) }) } diff --git a/src/renderer/utils/index.js b/src/renderer/utils/index.js index d9faabdd..3b30ed43 100644 --- a/src/renderer/utils/index.js +++ b/src/renderer/utils/index.js @@ -1,4 +1,5 @@ import fs from 'fs' +import path from 'path' import { shell, clipboard } from 'electron' import crypto from 'crypto' import { rendererSend, rendererInvoke, NAMES } from '../../common/ipc' @@ -341,3 +342,22 @@ export const getProxyInfo = () => window.globalObj.proxy.enable export const assertApiSupport = source => window.globalObj.qualityList[source] != undefined + +export const getSetting = () => rendererInvoke(NAMES.mainWindow.get_setting) +export const saveSetting = () => rendererInvoke(NAMES.mainWindow.set_app_setting) + +export const getPlayList = () => rendererInvoke(NAMES.mainWindow.get_playlist).catch(error => { + rendererInvoke(NAMES.mainWindow.get_data_path).then(dataPath => { + let filePath = path.join(dataPath, 'playList.json.bak') + rendererInvoke(NAMES.mainWindow.show_dialog, { + type: 'error', + message: window.i18n.t('store.state.load_list_file_error_title'), + detail: window.i18n.t('store.state.load_list_file_error_detail', { + path: filePath, + detail: error.message, + }), + }).then(() => openDirInExplorer(filePath)) + }) + return rendererInvoke(NAMES.mainWindow.get_playlist, true) +}) + diff --git a/src/renderer/utils/music/api-source-info.js b/src/renderer/utils/music/api-source-info.js index 2d5b1cfb..74379655 100644 --- a/src/renderer/utils/music/api-source-info.js +++ b/src/renderer/utils/music/api-source-info.js @@ -12,7 +12,7 @@ module.exports = [ wy: ['128k'], mg: ['128k'], xm: ['128k'], - bd: ['128k'], + // bd: ['128k'], }, }, { diff --git a/src/renderer/utils/music/bd/index.js b/src/renderer/utils/music/bd/index.js index c83fe67e..de780c90 100644 --- a/src/renderer/utils/music/bd/index.js +++ b/src/renderer/utils/music/bd/index.js @@ -21,7 +21,7 @@ const bd = { }, getLyric(songInfo) { const requestObj = this.getMusicInfo(songInfo) - requestObj.promise = requestObj.promise.then(info => httpFetch(info.lrclink).promise.then(resp => resp.body)) + requestObj.promise = requestObj.promise.then(info => httpFetch(info.lrclink).promise.then(resp => ({ lyric: resp.body, tlyric: '' }))) return requestObj }, // getLyric(songInfo) { diff --git a/src/renderer/utils/music/index.js b/src/renderer/utils/music/index.js index 59ecc964..ae882ab5 100644 --- a/src/renderer/utils/music/index.js +++ b/src/renderer/utils/music/index.js @@ -34,10 +34,10 @@ const sources = { name: '虾米音乐', id: 'xm', }, - { - name: '百度音乐', - id: 'bd', - }, + // { + // name: '百度音乐', + // id: 'bd', + // }, ], kw, kg, diff --git a/src/renderer/utils/music/kg/lyric.js b/src/renderer/utils/music/kg/lyric.js index dab3fc00..52b66612 100644 --- a/src/renderer/utils/music/kg/lyric.js +++ b/src/renderer/utils/music/kg/lyric.js @@ -1,4 +1,41 @@ import { httpFetch } from '../../request' +import { decodeLyric } from './util' + +const parseLyric = str => { + str = str.replace(/(?:<\d+,\d+,\d+>|\r)/g, '') + let trans = str.match(/\[language:([\w=\\/+]+)\]/) + let tlyric + if (trans) { + str = str.replace(/\[language:[\w=\\/+]+\]\n/, '') + let json = JSON.parse(Buffer.from(trans[1], 'base64').toString()) + for (const item of json.content) { + if (item.type == 1) { + tlyric = item.lyricContent + break + } + } + } + let i = 0 + let lyric = str.replace(/\[((\d+),\d+)\].*/g, str => { + let result = str.match(/\[((\d+),\d+)\].*/) + let time = parseInt(result[2]) + let ms = time % 1000 + time /= 1000 + let h = parseInt(time / 3600).toString().padStart(2, '0') + time %= 3600 + let m = parseInt(time / 60).toString().padStart(2, '0') + time %= 60 + let s = parseInt(time).toString().padStart(2, '0') + time = `${h}:${m}:${s}.${ms}` + if (tlyric) tlyric[i] = `[${time}]${tlyric[i++][0]}` + return str.replace(result[1], time) + }) + tlyric = tlyric ? tlyric.join('\n') : '' + return { + lyric, + tlyric, + } +} export default { getIntv(interval) { @@ -11,8 +48,30 @@ export default { } 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`, { + // 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 { + // lyric: body, + // tlyric: '', + // } + // }) + // return requestObj + // }, + searchLyric(name, hash, time, tryNum = 0) { + let requestObj = httpFetch(`http://lyrics.kugou.com/search?ver=1&man=yes&client=pc&keyword=${encodeURIComponent(name)}&hash=${hash}&timelength=${time}`, { headers: { 'KG-RC': 1, 'KG-THash': 'expand_search_manager.cpp:852736169:451', @@ -22,11 +81,49 @@ export default { requestObj.promise = requestObj.promise.then(({ body, statusCode }) => { if (statusCode !== 200) { if (tryNum > 5) return Promise.reject('歌词获取失败') - let tryRequestObj = this.getLyric(songInfo, ++tryNum) + let tryRequestObj = this.searchLyric(name, hash, time, ++tryNum) requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj) return tryRequestObj.promise } - return body + if (body.candidates.length) { + let info = body.candidates[0] + return { id: info.id, accessKey: info.accesskey } + } + return null + }) + return requestObj + }, + getLyricDownload(id, accessKey, tryNum = 0) { + let requestObj = httpFetch(`http://lyrics.kugou.com/download?ver=1&client=pc&id=${id}&accesskey=${accessKey}&fmt=krc&charset=utf8`, { + 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(id, accessKey, ++tryNum) + requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj) + return tryRequestObj.promise + } + + return decodeLyric(body.content).then(result => parseLyric(result)) + }) + return requestObj + }, + getLyric(songInfo, tryNum = 0) { + let requestObj = this.searchLyric(songInfo.name, songInfo.hash, songInfo._interval || this.getIntv(songInfo.interval)) + + requestObj.promise = requestObj.promise.then(result => { + if (!result) return { lyric: '', tlyric: '' } + + let requestObj2 = this.getLyricDownload(result.id, result.accessKey) + + requestObj.cancelHttp = requestObj2.cancelHttp.bind(requestObj2) + + return requestObj2.promise }) return requestObj }, diff --git a/src/renderer/utils/music/kg/util.js b/src/renderer/utils/music/kg/util.js new file mode 100644 index 00000000..73b5e743 --- /dev/null +++ b/src/renderer/utils/music/kg/util.js @@ -0,0 +1,19 @@ +import { inflate } from 'zlib' + +// https://github.com/lyswhut/lx-music-desktop/issues/296#issuecomment-683285784 +const enc_key = Buffer.from([0x40, 0x47, 0x61, 0x77, 0x5e, 0x32, 0x74, 0x47, 0x51, 0x36, 0x31, 0x2d, 0xce, 0xd2, 0x6e, 0x69], 'binary') +export const decodeLyric = str => new Promise((resolve, reject) => { + if (!str.length) return + const buf_str = Buffer.from(str, 'base64').slice(4) + for (let i = 0, len = buf_str.length; i < len; i++) { + buf_str[i] = buf_str[i] ^ enc_key[i % 16] + } + inflate(buf_str, (err, result) => { + if (err) return reject(err) + resolve(result.toString()) + }) +}) + +// s.content[0].lyricContent.forEach(([str]) => { +// console.log(str) +// }) diff --git a/src/renderer/utils/music/kw/lyric.js b/src/renderer/utils/music/kw/lyric.js index 29f6a1d7..91fd52f3 100644 --- a/src/renderer/utils/music/kw/lyric.js +++ b/src/renderer/utils/music/kw/lyric.js @@ -17,7 +17,10 @@ export default { return requestObj.promise.then(({ statusCode, body, raw }) => { if (statusCode != 200) return Promise.reject(new Error(JSON.stringify(body))) return decodeLyric({ lrcBase64: raw.toString('base64'), isGetLyricx }).then(base64Data => { - return Buffer.from(base64Data, 'base64').toString() + return { + lyric: Buffer.from(base64Data, 'base64').toString(), + tlyric: '', + } }) }) }) diff --git a/src/renderer/utils/music/mg/lyric.js b/src/renderer/utils/music/mg/lyric.js index 330aa10d..cd6a49ef 100644 --- a/src/renderer/utils/music/mg/lyric.js +++ b/src/renderer/utils/music/mg/lyric.js @@ -12,7 +12,10 @@ export default { requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj) return tryRequestObj.promise } - return body + return { + lyric: body, + tlyric: '', + } }) return requestObj } else { @@ -28,7 +31,10 @@ export default { requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj) return tryRequestObj.promise } - return body.lyric + return { + lyric: body.lyric, + tlyric: '', + } }) return requestObj } diff --git a/src/renderer/utils/music/tx/lyric.js b/src/renderer/utils/music/tx/lyric.js index d86233ba..a371a6e7 100644 --- a/src/renderer/utils/music/tx/lyric.js +++ b/src/renderer/utils/music/tx/lyric.js @@ -6,13 +6,17 @@ export default { matchLrc: /.+"lyric":"([\w=+/]*)".+/, }, getLyric(songmid) { - const requestObj = httpFetch(`https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?songmid=${songmid}&g_tk=2001461048&loginUin=0&hostUin=0&format=jsonp&inCharset=utf8&outCharset=utf-8&platform=yqq`, { + const requestObj = httpFetch(`https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?songmid=${songmid}&g_tk=5381&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&platform=yqq`, { headers: { Referer: 'https://y.qq.com/portal/player.html', }, }) requestObj.promise = requestObj.promise.then(({ body }) => { - return decodeName(b64DecodeUnicode(body.replace(this.regexps.matchLrc, '$1'))) + if (body.code != 0) return Promise.reject(new Error('获取歌词失败')) + return { + lyric: decodeName(b64DecodeUnicode(body.lyric)), + tlyric: decodeName(b64DecodeUnicode(body.trans)), + } }) return requestObj }, diff --git a/src/renderer/utils/music/wy/lyric.js b/src/renderer/utils/music/wy/lyric.js index 819b3931..6ae95d34 100644 --- a/src/renderer/utils/music/wy/lyric.js +++ b/src/renderer/utils/music/wy/lyric.js @@ -1,20 +1,27 @@ import { httpFetch } from '../../request' -import { weapi } from './utils/crypto' +import { linuxapi } from './utils/crypto' export default songmid => { - const requestObj = httpFetch('http://music.163.com/weapi/song/lyric?csrf_token=', { + const requestObj = httpFetch('https://music.163.com/api/linux/forward', { method: 'post', - headers: { - 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36', - Referer: 'https://music.163.com/song?id=' + songmid, - origin: 'https://music.163.com', - }, - form: weapi({ id: songmid, lv: -1, tv: -1, csrf_token: '' }), + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36', + form: linuxapi({ + method: 'POST', + url: 'https://music.163.com/api/song/lyric', + params: { + id: songmid, + lv: -1, + kv: -1, + tv: -1, + }, + }), }) requestObj.promise = requestObj.promise.then(({ body }) => { - // console.log(body) if (body.code !== 200) return Promise.reject('获取歌词失败') - return body.lrc.lyric + return { + lyric: body.lrc.lyric, + tlyric: body.tlyric.lyric, + } }) return requestObj } diff --git a/src/renderer/utils/music/xm/leaderboard.js b/src/renderer/utils/music/xm/leaderboard.js index fa28b59f..34dca4b1 100644 --- a/src/renderer/utils/music/xm/leaderboard.js +++ b/src/renderer/utils/music/xm/leaderboard.js @@ -78,7 +78,7 @@ export default { return arr.join('、') }, filterData(rawList) { - console.log(rawList) + // console.log(rawList) let ids = new Set() const list = [] rawList.forEach(songData => { diff --git a/src/renderer/utils/music/xm/lyric.js b/src/renderer/utils/music/xm/lyric.js index 4f83c57f..d1e85d79 100644 --- a/src/renderer/utils/music/xm/lyric.js +++ b/src/renderer/utils/music/xm/lyric.js @@ -1,6 +1,23 @@ import { httpGet, httpFetch } from '../../request' import { xmRequest } from './util' +const parseLyric = str => { + str = str.replace(/(?:<\d+>|\r)/g, '') + let tlyric = [] + let lyric = str.replace(/\[[\d:.]+\].*?\n\[x-trans\].*/g, s => { + // console.log(s) + let [lrc, tlrc] = s.split('\n') + tlrc = tlrc.replace('[x-trans]', lrc.replace(/^(\[[\d:.]+\]).*$/, '$1')) + tlyric.push(tlrc) + return lrc + }) + tlyric = tlyric.join('\n') + return { + lyric, + tlyric, + } +} + export default { failTime: 0, expireTime: 60 * 1000 * 1000, @@ -13,7 +30,10 @@ export default { requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj) return tryRequestObj.promise } - return body + return url.endsWith('.xtrc') ? parseLyric(body) : { + lyric: body, + tlyric: '', + } }) return requestObj }, @@ -27,7 +47,10 @@ export default { }, }, function(err, resp, body) { if (err || resp.statusCode !== 200) return this.getLyricFile(url, ++retryNum).then(resolve).catch(reject) - return resolve(body) + return resolve(url.endsWith('.xtrc') ? parseLyric(body) : { + lyric: body, + tlyric: '', + }) }) }) }, @@ -47,8 +70,12 @@ export default { return tryRequestObj.promise } if (!body.result.data.lyrics.length) return Promise.reject(new Error('未找到歌词')) - let lrc = body.result.data.lyrics.find(lyric => /\.lrc$/.test(lyric.lyricUrl)) - return lrc ? lrc.content : Promise.reject(new Error('未找到歌词')) + let lrc = body.result.data.lyrics.find(lyric => /\.(trc|lrc)$/.test(lyric.lyricUrl)) + return lrc + ? lrc.lyricUrl.endsWith('.trc') + ? parseLyric(lrc.content) + : { lyric: lrc.content, tlyric: '' } + : Promise.reject(new Error('未找到歌词')) }) return requestObj }, @@ -74,7 +101,7 @@ export default { return requestObj }, getLyric(songInfo) { - if (songInfo.lrcUrl && /\.lrc$/.test(songInfo.lrcUrl)) return this.getLyricFile_1(songInfo.lrcUrl) + if (songInfo.lrcUrl && /\.(xtrc|lrc)$/.test(songInfo.lrcUrl)) return this.getLyricFile_1(songInfo.lrcUrl) return Date.now() - this.failTime > this.expireTime ? this.getLyricUrl_1(songInfo) : this.getLyricUrl_2(songInfo) }, } diff --git a/src/renderer/views/Download.vue b/src/renderer/views/Download.vue index 5d7c74ab..77a327df 100644 --- a/src/renderer/views/Download.vue +++ b/src/renderer/views/Download.vue @@ -29,7 +29,6 @@ div(:class="$style.download") :start-btn="!item.isComplate && item.status != downloadStatus.WAITING && (item.status != downloadStatus.RUN)" :pause-btn="!item.isComplate && (item.status == downloadStatus.RUN || item.status == downloadStatus.WAITING)" :list-add-btn="false" :play-btn="item.status == downloadStatus.COMPLETED" :search-btn="item.status == downloadStatus.ERROR" @btn-click="handleListBtnClick") - //- material-flow-btn(:show="isShowEditBtn" :play-btn="false" :download-btn="false" :add-btn="false" :start-btn="true" :pause-btn="true" @btn-click="handleFlowBtnClick") material-menu(:menus="listItemMenu" :location="listMenu.menuLocation" item-name="name" :isShow="listMenu.isShowItemMenu" @menu-click="handleListItemMenuClick") div(:class="$style.noItem" v-else) @@ -47,7 +46,6 @@ export default { clickTime: window.performance.now(), clickIndex: -1, selectdData: [], - // isShowEditBtn: false, isShowDownloadMultiple: false, tabId: 'all', keyEvent: { @@ -166,14 +164,6 @@ export default { }, }, watch: { - selectdData(n) { - const len = n.length - if (len) { - this.isShowEditBtn = true - } else { - this.isShowEditBtn = false - } - }, list() { this.removeAllSelect() }, diff --git a/src/renderer/views/List.vue b/src/renderer/views/List.vue index 4a42968e..79b75cba 100644 --- a/src/renderer/views/List.vue +++ b/src/renderer/views/List.vue @@ -643,7 +643,7 @@ export default { this.listsData.isShowNewList = false }) - this.createUserList(name) + this.createUserList({ name }) }, handleShowNewList() { this.listsData.isShowNewList = true diff --git a/src/renderer/views/Setting.vue b/src/renderer/views/Setting.vue index 8940cb05..b28b0829 100644 --- a/src/renderer/views/Setting.vue +++ b/src/renderer/views/Setting.vue @@ -10,6 +10,11 @@ div.scroll(:class="$style.setting") span label {{$t('store.state.theme_' + theme.class)}} + dd + h3 {{$t('view.setting.basic_show_animation')}} + div + material-checkbox(id="setting_show_animate" v-model="current_setting.isShowAnimation" :label="$t('view.setting.is_show')") + dd(:title="$t('view.setting.basic_animation_title')") h3 {{$t('view.setting.basic_animation')}} div @@ -58,6 +63,10 @@ div.scroll(:class="$style.setting") div material-checkbox(:id="`setting_player_togglePlay_${item.value}`" :class="$style.gapLeft" :value="item.value" :key="item.value" v-model="current_setting.player.togglePlayMethod" v-for="item in togglePlayMethods" :label="item.name") + dd + h3 {{$t('view.setting.play_lyric_transition')}} + div + material-checkbox(id="setting_player_lyric_transition" v-model="current_setting.player.isShowLyricTransition" :label="$t('view.setting.is_show')") dd(:title="$t('view.setting.play_quality_title')") h3 {{$t('view.setting.play_quality')}} div @@ -82,6 +91,8 @@ div.scroll(:class="$style.setting") material-checkbox(id="setting_desktop_lyric_lock" v-model="current_setting.desktopLyric.isLock" :label="$t('view.setting.desktop_lyric_lock')") div(:class="$style.gapTop") material-checkbox(id="setting_desktop_lyric_alwaysOnTop" v-model="current_setting.desktopLyric.isAlwaysOnTop" :label="$t('view.setting.desktop_lyric_always_on_top')") + div(:class="$style.gapTop") + material-checkbox(id="setting_desktop_lyric_lockScreen" v-model="current_setting.desktopLyric.isLockScreen" :label="$t('view.setting.desktop_lyric_lock_screen')") dt {{$t('view.setting.search')}} dd(:title="$t('view.setting.search_hot_title')") h3 {{$t('view.setting.search_hot')}} @@ -228,7 +239,7 @@ div.scroll(:class="$style.setting") span.hover.underline(:title="$t('view.setting.click_open')" @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop#readme')") https://github.com/lyswhut/lx-music-desktop p.small | 最新版网盘下载地址(网盘内有Windows、MAC版): - span.hover.underline(:title="$t('view.setting.click_open')" @click="handleOpenUrl('https://t-s.lanzous.com/b0bf2cfa')") 网盘地址 + span.hover.underline(:title="$t('view.setting.click_open')" @click="handleOpenUrl('https://www.lanzoux.com/b0bf2cfa/')") 网盘地址 |   密码: span.hover(:title="$t('view.setting.click_copy')" @click="clipboardWriteText('glqw')") glqw p.small @@ -448,6 +459,7 @@ export default { langId: 'cns', themeId: 0, sourceId: 0, + isShowAnimation: true, randomAnimate: true, isAgreePact: false, controlBtnPosition: 'left', @@ -587,6 +599,15 @@ export default { 'setting.player.mediaDeviceId'(n) { this.current_setting.player.mediaDeviceId = n }, + 'setting.player.isMute'(n) { + this.current_setting.player.isMute = n + }, + 'setting.desktopLyric.enable'(n) { + this.current_setting.desktopLyric.enable = n + }, + 'setting.player.togglePlayMethod'(n) { + this.current_setting.player.togglePlayMethod = n + }, 'current_setting.player.isShowTaskProgess'(n) { if (n) return this.$nextTick(() => { diff --git a/src/renderer/views/SongList.vue b/src/renderer/views/SongList.vue index 6d5ff906..8a547035 100644 --- a/src/renderer/views/SongList.vue +++ b/src/renderer/views/SongList.vue @@ -10,11 +10,9 @@ h3(:title="listDetail.info.name || selectListInfo.name") {{listDetail.info.name || selectListInfo.name}} p(:title="listDetail.info.desc || selectListInfo.desc") {{listDetail.info.desc || selectListInfo.desc}} div(:class="$style.songListHeaderRight") - //- material-btn(:class="$style.closeDetailButton" :disabled="detailLoading" @click="addSongListDetail") 添加 - //- |   - //- material-btn(:class="$style.closeDetailButton" :disabled="detailLoading" @click="playSongListDetail") 播放 - //- |   - material-btn(:class="$style.closeDetailButton" @click="hideListDetail") {{$t('view.song_list.back')}} + material-btn(:class="$style.headerRightBtn" :disabled="detailLoading" @click="playSongListDetail") {{$t('view.song_list.play_all')}} + material-btn(:class="$style.headerRightBtn" :disabled="detailLoading" @click="addSongListDetail") {{$t('view.song_list.add_all')}} + material-btn(:class="$style.headerRightBtn" @click="hideListDetail") {{$t('view.song_list.back')}} material-song-list(v-model="selectedData" @action="handleSongListAction" :source="source" :page="listDetail.page" :limit="listDetail.limit" :total="listDetail.total" :noItem="isGetDetailFailed ? $t('view.song_list.loding_list_fail') : $t('view.song_list.loding_list')" :list="listDetail.list") transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut") @@ -84,7 +82,7 @@ export default { listWidth: 645, isGetDetailFailed: false, isInitedTagListWidth: false, - // detailLoading: true, + detailLoading: false, } }, computed: { @@ -97,7 +95,7 @@ export default { switch (this.source) { case 'wy': case 'kw': - case 'bd': + // case 'bd': case 'tx': case 'mg': case 'kg': @@ -179,11 +177,13 @@ export default { }, methods: { ...mapMutations(['setSongList']), - ...mapActions('songList', ['getTags', 'getList', 'getListDetail']), + ...mapActions('songList', ['getTags', 'getList', 'getListDetail', 'getListDetailAll']), ...mapMutations('songList', ['setVisibleListDetail', 'setSelectListInfo']), ...mapActions('download', ['createDownload', 'createDownloadMultiple']), - ...mapMutations('list', ['listAdd', 'listAddMultiple']), - ...mapMutations('player', ['setList']), + ...mapMutations('list', ['listAdd', 'listAddMultiple', 'createUserList']), + ...mapMutations('player', { + setPlayList: 'setList', + }), listenEvent() { window.eventHub.$on('key_backspace_down', this.handle_key_backspace_down) }, @@ -273,7 +273,7 @@ export default { s => s.songmid === targetSong.songmid, ) if (targetIndex > -1) { - this.setList({ + this.setPlayList({ list: this.defaultList, index: targetIndex, }) @@ -402,15 +402,28 @@ export default { assertApiSupport(source) { return assertApiSupport(source) }, - /* addSongListDetail() { - // this.detailLoading = true - // this.getListDetailAll(this.selectListInfo.id).then(() => { - // this.detailLoading = false - // }) + async fetchList() { + this.detailLoading = true + const list = await this.getListDetailAll(this.selectListInfo.id) + this.detailLoading = false + return list + }, + async addSongListDetail() { + if (!this.listDetail.info.name) return + const list = await this.fetchList() + this.createUserList({ name: this.listDetail.info.name, id: `${this.listDetail.source}__${this.listDetail.id}`, list }) + }, + async playSongListDetail() { + if (!this.listDetail.info.name) return + const list = await this.fetchList() + this.setPlayList({ + list: { + list, + id: null, + }, + index: 0, + }) }, - playSongListDetail() { - - }, */ }, } @@ -517,6 +530,17 @@ export default { align-items: center; padding-right: 15px; } +.header-right-btn { + border-radius: 0; + &:first-child { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + } + &:last-child { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + } +} .song-list-detail-content { position: absolute;